Skip to content

Instantly share code, notes, and snippets.

@ldaley
Last active March 5, 2018 14:59
Show Gist options
  • Save ldaley/5429603 to your computer and use it in GitHub Desktop.
Save ldaley/5429603 to your computer and use it in GitHub Desktop.
Groovy @DelegatesTo and type tokens.
Handler<T> handler(@DelegatesTo.Target(asType = true) Class<T> type, @DelegatesTo Closure<?> configurer) {
return new Handler() {
void handle(T object) {
configurer.delegate = object
configure.call(delegate)
}
}
}
@melix
Copy link

melix commented Apr 22, 2013

so you would call it like this?

def handler = handler(Foo) { bar() }
handler.handle(new Foo())

and expect bar() to be recognized as being called on a Foo?

If so, the problem I see with this solution is that it's very specific to the "Class" case, where at compile time, I can determine that "Foo" is Class<Foo>. But was if you have instead List<T>? It happens to be the same "problem" but it wouldn't find a solution.

I could think of something that would allow you to help the compiler at this point, but it's very advanced, probably too much for most of developers:

Handler<T> handler(Class<T> type, @DelegatesTo(type={ parameters[0].type }) Closure<?> configurer) {
    ...
}

This would solve your issue and be a general solution, but it can become very tricky (think of the List case with an actual type using a subclass where the generic type is fixed).

@ldaley
Copy link
Author

ldaley commented Apr 22, 2013

In your example, there's no way to know which parameter parameters is. What about…

void someMethod(@DelegatesTo.Target(asType = true, typeIndex = 1) Map<K, V> map, @DelegatesTo Closure<?> closure) 

So V is the delegate type. typeIndex would default to 0.

(think of the List case with an actual type using a subclass where the generic type is fixed).

I'm not smart enough to understand this :S

For me though, supporting the type token pattern is essential. We'll definitely need it in Gradle. I think it's acceptable to have special support for this pattern. Acceptable, but not ideal of course.

If you had support for this, I think you could maybe work around other scenarios by adding a type token…

void someMethod(@DelegatesTo.Target(asType = true) Class<V>, Map<K, ? super V> map, @DelegatesTo Closure<?> closure) 

@melix
Copy link

melix commented Apr 22, 2013

parameters refer to the actual arguments when you call handler(Foo) { closure code } so parameters[0] refers to the Foo parameter, and the type checker nows its "flow type" (note that writing this makes me think you would need to do something like:

(parameters[0] instanceof ClassExpression)?parameters[0].type:getType(parameters[0])

With your second example, I understand that {{asType}} doesn't mean "the type that the Class represents", but the actual generic type argument. That could require complex mappings, but that's in theory doable I think... I would prefer another name than "asType", though, such as "genericType".

For example, imagine you have:

class MyMap<E> extends HashMap<String, E> {}
class MySubMap extends MyMap<Date> {}

Then you want to map it to:

void someMethod(@DelegatesTo.Target(genericTypeIndex=1) Map<K,V> map, @DelegatesTo Closure<?> closure)

You would have to map Date to E, then E to V. That's something the type checker does internally, although it touches code that is known to have bugs (due to the complex representation of generic types in Groovy itself).

@ldaley
Copy link
Author

ldaley commented Apr 22, 2013

(parameters[0] instanceof ClassExpression)?parameters[0].type:getType(parameters[0])

That seems doomed for user error. Let's see if we can avoid having to use imperative code.

I understand that {{asType}} doesn't mean "the type that the Class represents"

It did originally, because I was just thinking about type tokens. That is, asType would only work on a Class param (anything else would be an error). In that context, I think the name makes sense. But know that we are talking about something bigger, I agree that the name doesn't make sense.

You would have to map Date to E, then E to V.

I wish I could contribute here, but it's beyond my knowledge.

It seems like:

void someMethod(@DelegatesTo.Target(genericTypeIndex=1) Map<K,V> map, @DelegatesTo Closure<?> closure)

Would cover a lot of use cases if it could be made to work.

@melix
Copy link

melix commented Apr 22, 2013

Agreed. I'll make some experiments around this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment