This is an automated email from the ASF dual-hosted git repository. emilles pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push: new d9d2a830f5 GROOVY-11168: STC: `@DelegatesTo(type=` type parameter resolution d9d2a830f5 is described below commit d9d2a830f54792ba0bc4abcaa12cfa2535993648 Author: Eric Milles <eric.mil...@thomsonreuters.com> AuthorDate: Wed Sep 20 14:44:11 2023 -0500 GROOVY-11168: STC: `@DelegatesTo(type=` type parameter resolution --- .../transform/stc/StaticTypeCheckingVisitor.java | 16 +- .../groovy/transform/stc/DelegatesToSTCTest.groovy | 508 +++++++++------------ 2 files changed, 236 insertions(+), 288 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index 72b3635bef..0afd3f2765 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -241,7 +241,6 @@ import static org.codehaus.groovy.ast.tools.WideningCategories.isNumberCategory; import static org.codehaus.groovy.ast.tools.WideningCategories.lowestUpperBound; import static org.codehaus.groovy.runtime.ArrayGroovyMethods.asBoolean; import static org.codehaus.groovy.runtime.ArrayGroovyMethods.init; -import static org.codehaus.groovy.runtime.DefaultGroovyMethods.last; import static org.codehaus.groovy.syntax.Types.ASSIGN; import static org.codehaus.groovy.syntax.Types.COMPARE_EQUAL; import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_EQUAL; @@ -3292,7 +3291,7 @@ out: if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0 * of failure. */ private void resolveGenericsFromTypeHint(final ClassNode receiver, final Expression arguments, final MethodNode selectedMethod, final ClassNode[] signature) { - ClassNode returnType = new ClassNode("ClForInference$" + UNIQUE_LONG.incrementAndGet(), 0, OBJECT_TYPE).getPlainNodeReference(); + ClassNode returnType = new ClassNode("ClForInference$" + UNIQUE_LONG.incrementAndGet(), 0, null).getPlainNodeReference(); returnType.setGenericsTypes(Arrays.stream(signature).map(ClassNode::asGenericsType).toArray(GenericsType[]::new)); MethodNode methodNode = selectedMethod instanceof ExtensionMethodNode ? ((ExtensionMethodNode) selectedMethod).getExtensionMethodNode() : selectedMethod; @@ -3314,11 +3313,12 @@ out: if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0 methodNode.setGenericsTypes(selectedMethod.getGenericsTypes()); } - GenericsType[] typeArguments = null; // GROOVY-7789 - Expression emc = typeCheckingContext.getEnclosingMethodCall(); - if (emc instanceof MethodCallExpression) { - MethodCallExpression call = (MethodCallExpression) emc; - if (arguments == call.getArguments()) typeArguments = call.getGenericsTypes(); + GenericsType[] typeArguments = null; + Expression emc = typeCheckingContext.getEnclosingMethodCall(); // GROOVY-7789, GROOVY-11168 + if (emc instanceof MethodCallExpression) { MethodCallExpression call = (MethodCallExpression) emc; + if (arguments == call.getArguments() || InvocationWriter.makeArgumentList(arguments).getExpressions().stream().anyMatch(arg -> + arg instanceof ClosureExpression && DefaultGroovyMethods.contains(InvocationWriter.makeArgumentList(call.getArguments()), arg))) + typeArguments = call.getGenericsTypes(); } returnType = inferReturnTypeGenerics(receiver, methodNode, arguments, typeArguments); @@ -4061,7 +4061,7 @@ out: if (mn.size() != 1) { private static boolean maybeFallsThrough(Statement statement) { if (statement.isEmpty()) return true; if (statement instanceof BlockStatement) - statement = last(((BlockStatement) statement).getStatements()); + statement = DefaultGroovyMethods.last(((BlockStatement) statement).getStatements()); // end break, continue, return or throw if (statement instanceof BreakStatement || statement instanceof ContinueStatement diff --git a/src/test/groovy/transform/stc/DelegatesToSTCTest.groovy b/src/test/groovy/transform/stc/DelegatesToSTCTest.groovy index 92f55fbf19..960343bca5 100644 --- a/src/test/groovy/transform/stc/DelegatesToSTCTest.groovy +++ b/src/test/groovy/transform/stc/DelegatesToSTCTest.groovy @@ -18,12 +18,21 @@ */ package groovy.transform.stc +import org.codehaus.groovy.control.customizers.ImportCustomizer + /** * Units tests aimed at testing the behavior of {@link DelegatesTo} in combination * with static type checking. */ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { + @Override + void configure() { + config.addCompilationCustomizers( + new ImportCustomizer().addStaticStars('groovy.lang.Closure') + ) + } + void testShouldChooseMethodFromOwner() { assertScript ''' class Delegate { @@ -60,9 +69,9 @@ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { } class Owner { int foo() { 1 } - int doIt(@DelegatesTo(strategy=Closure.DELEGATE_FIRST, value=Delegate) Closure cl) { + int doIt(@DelegatesTo(value=Delegate, strategy=DELEGATE_FIRST) Closure cl) { cl.delegate = new Delegate() - cl.resolveStrategy = Closure.DELEGATE_FIRST + cl.resolveStrategy = DELEGATE_FIRST cl() as int } int test() { @@ -94,9 +103,9 @@ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { ExecSpec spec = new ExecSpec() - void exec(ExecSpec spec, @DelegatesTo(value=ExecSpec, strategy=Closure.DELEGATE_FIRST) Closure param) { - param.delegate = spec - param() + void exec(ExecSpec spec, @DelegatesTo(value=ExecSpec, strategy=DELEGATE_FIRST) Closure cl) { + cl.delegate = spec + cl() } exec(spec) { @@ -111,7 +120,7 @@ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { class Xml { boolean called = false void bar() { called = true } - void foo(@DelegatesTo(Xml)Closure cl) { cl.delegate=this;cl() } + void foo(@DelegatesTo(Xml) Closure cl) { cl.delegate=this;cl() } } def mylist = [1] def xml = new Xml() @@ -216,65 +225,57 @@ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { void testShouldDelegateToParameter() { assertScript ''' - class Foo { - boolean called = false - def foo() { called = true } - } + class Foo { + boolean called = false + def foo() { called = true } + } - def with(@DelegatesTo.Target Object target, @DelegatesTo Closure arg) { - arg.delegate = target - arg() - } + def with(@DelegatesTo.Target Object target, @DelegatesTo Closure cl) { + cl.delegate = target + cl() + } - def test() { def obj = new Foo() with(obj) { foo() } assert obj.called - } - test() ''' } void testShouldDelegateToParameterUsingExplicitId() { assertScript ''' - class Foo { - boolean called = false - def foo() { called = true } - } + class Foo { + boolean called = false + def foo() { called = true } + } - def with(@DelegatesTo.Target('target') Object target, @DelegatesTo(target='target') Closure arg) { - arg.delegate = target - arg() - } + def with(@DelegatesTo.Target('target') Object target, @DelegatesTo(target='target') Closure cl) { + cl.delegate = target + cl() + } - def test() { def obj = new Foo() with(obj) { foo() } assert obj.called - } - test() ''' } void testShouldFailDelegateToParameterUsingWrongId() { shouldFailWithMessages ''' - class Foo { - boolean called = false - def foo() { called = true } - } + class Foo { + boolean called = false + def foo() { called = true } + } - def with(@DelegatesTo.Target('target') Object target, @DelegatesTo(target='wrongTarget') Closure arg) { - arg.delegate = target - arg() - } + def with(@DelegatesTo.Target('target') Object target, @DelegatesTo(target='wrongTarget') Closure cl) { + cl.delegate = target + cl() + } - def test() { def obj = new Foo() with(obj) { foo() } - assert obj.called - } - test() - ''', 'Not enough arguments found for a @DelegatesTo method call', 'Cannot find matching method' + ''', + 'Not enough arguments found for a @DelegatesTo method call', + 'Cannot find matching method' } void testShouldFailDelegateToParameterIfNoTargetSpecified() { @@ -284,15 +285,16 @@ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { def bar() { called = true } } - def m(Object o, @DelegatesTo Closure c) { - c.delegate = o - c.call() + def m(Object o, @DelegatesTo Closure cl) { + cl.delegate = o + cl() } def foo = new Foo() m(foo) { -> bar() } - assert foo.called - ''', 'Not enough arguments found for a @DelegatesTo method call', '@ line 6, column 29', 'Cannot find matching method' + ''', + 'Not enough arguments found for a @DelegatesTo method call', '@ line 6, column 29', + 'Cannot find matching method' } void testDelegatesToWithSetter() { @@ -361,7 +363,8 @@ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { y = 5 } assert b.value() == 5 - ''', '[Static type checking] - The variable [y] is undeclared.' + ''', + '[Static type checking] - The variable [y] is undeclared.' } void testDelegatesToWithSetterUsedAsPropertyAndWith() { @@ -392,11 +395,11 @@ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { // GROOVY-6022 void testDelegatesToVariadicParameter() { assertScript ''' - def m(@DelegatesTo.Target target, @DelegatesTo(strategy=Closure.DELEGATE_FIRST) Closure... a) { - for (Closure c : a) { - c.resolveStrategy = Closure.DELEGATE_FIRST - c.delegate = target - c() + def m(@DelegatesTo.Target target, @DelegatesTo(strategy=DELEGATE_FIRST) Closure... closures) { + for (Closure closure : closures) { + closure.resolveStrategy = DELEGATE_FIRST + closure.delegate = target + closure() } } @@ -408,9 +411,9 @@ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { int x } - void m(@DelegatesTo.Target Item item, @DelegatesTo(strategy=Closure.DELEGATE_FIRST) Closure... closures) { + void m(@DelegatesTo.Target Item item, @DelegatesTo(strategy=DELEGATE_FIRST) Closure... closures) { for (closure in closures) { - closure.resolveStrategy = Closure.DELEGATE_FIRST + closure.resolveStrategy = DELEGATE_FIRST closure.delegate = item closure.call() } @@ -424,8 +427,8 @@ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { int x } - void m(@DelegatesTo.Target Item item, @DelegatesTo(strategy=Closure.DELEGATE_FIRST) Closure... closures) { - for (@DelegatesTo(strategy=Closure.DELEGATE_FIRST) Closure closure : closures) { + void m(@DelegatesTo.Target Item item, @DelegatesTo(strategy=DELEGATE_FIRST) Closure... closures) { + for (@DelegatesTo(strategy=DELEGATE_FIRST) Closure closure : closures) { item.with(closure) } } @@ -462,155 +465,125 @@ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { // GROOVY-6021 void testShouldEnsureLastIsRecognizedAndCompiledProperly() { assertScript ''' - def with(@DelegatesTo.Target Object target, @DelegatesTo(strategy = Closure.DELEGATE_FIRST) Closure arg) { - arg.delegate = target - arg.setResolveStrategy(Closure.DELEGATE_FIRST) - arg() + def with(@DelegatesTo.Target Object target, @DelegatesTo(strategy = DELEGATE_FIRST) Closure cl) { + cl.resolveStrategy = DELEGATE_FIRST + cl.delegate = target + cl() } - def test() { - def obj = [1, 2] - with(obj) { - print(last()) //error is here - } + def obj = [1, 2] + with(obj) { + print(last()) //error is here } - - test() ''' } // GROOVY-6091 void testExplicitUseOfDelegateProperty() { assertScript ''' - def with(@DelegatesTo.Target Object target, @DelegatesTo(strategy = Closure.DELEGATE_FIRST) Closure block) { - block.resolveStrategy = Closure.DELEGATE_FIRST - block.delegate = target - block.call() + def with(@DelegatesTo.Target Object target, @DelegatesTo(strategy = DELEGATE_FIRST) Closure cl) { + cl.resolveStrategy = DELEGATE_FIRST + cl.delegate = target + cl.call() } - def test() { - def list = [1, 2] - with(list) { - delegate.last() // error is here - } + def list = [1, 2] + assert 2 == with(list) { + delegate.last() // error is here } - - assert test() == 2 ''' } // GROOVY-6091 void testExplicitUseOfDelegateMethod() { assertScript ''' - def with(@DelegatesTo.Target Object target, @DelegatesTo(strategy = Closure.DELEGATE_FIRST) Closure block) { - block.resolveStrategy = Closure.DELEGATE_FIRST - block.delegate = target - block.call() + def with(@DelegatesTo.Target Object target, @DelegatesTo(strategy=DELEGATE_FIRST) Closure cl) { + cl.resolveStrategy = DELEGATE_FIRST + cl.delegate = target + cl.call() } - def test() { - def list = [1, 2] - with(list) { -> - getDelegate().last() // error is here - } + def list = [1, 2] + assert 2 == with(list) { -> + getDelegate().last() // error is here } - - assert test() == 2 ''' } void testDelegatesToGenericTypeArgument() { assertScript ''' - public <T> Object map(@DelegatesTo.Target List<T> target, @DelegatesTo(genericTypeIndex=0) Closure arg) { - arg.delegate = target.join('') - arg() - } - def test() { - def result - map(['f','o','o']) { - result = toUpperCase() - } + public <T> Object map(@DelegatesTo.Target List<T> target, @DelegatesTo(genericTypeIndex=0) Closure cl) { + cl.delegate = target.join('') + cl() + } - result + def result + map(['f','o','o']) { + result = toUpperCase() } - assert 'FOO'==test() + assert result == 'FOO' ''' } void testDelegatesToGenericTypeArgumentAndActualArgumentNotUsingGenerics() { - assertScript '''import groovy.transform.InheritConstructors - @InheritConstructors + assertScript ''' + @groovy.transform.InheritConstructors class MyList extends LinkedList<String> {} - public <T> Object map(@DelegatesTo.Target List<T> target, @DelegatesTo(genericTypeIndex=0) Closure arg) { - arg.delegate = target.join('') - arg() + public <T> Object map(@DelegatesTo.Target List<T> target, @DelegatesTo(genericTypeIndex=0) Closure cl) { + cl.delegate = target.join('') + cl() } - def test() { - def result - def mylist = new MyList(['f','o','o']) - map(mylist) { - result = toUpperCase() - } - result + def result + def mylist = new MyList(['f','o','o']) + map(mylist) { + result = toUpperCase() } - assert 'FOO'==test() + assert result == 'FOO' ''' } void testDelegatesToGenericTypeArgumentWithMissingGenerics() { shouldFailWithMessages ''' - public Object map(@DelegatesTo.Target List target, @DelegatesTo(genericTypeIndex=0) Closure arg) { - arg.delegate = target.join('') - arg() - } - def test() { - def result - map(['f','o','o']) { - result = toUpperCase() - } + public Object map(@DelegatesTo.Target List target, @DelegatesTo(genericTypeIndex=0) Closure cl) { + cl.delegate = target.join('') + cl() + } - result + map(['f','o','o']) { + toUpperCase() } - assert 'FOO'==test() - ''', 'Cannot use @DelegatesTo(genericTypeIndex=0) with a type that doesn\'t use generics', 'Cannot find matching method' + ''', + 'Cannot use @DelegatesTo(genericTypeIndex=0) with a type that doesn\'t use generics', 'Cannot find matching method' } void testDelegatesToGenericTypeArgumentOutOfBounds() { shouldFailWithMessages ''' - public <T> Object map(@DelegatesTo.Target List<T> target, @DelegatesTo(genericTypeIndex=1) Closure arg) { - arg.delegate = target.join('') - arg() - } - def test() { - def result - map(['f','o','o']) { - result = toUpperCase() - } + def <T> Object map(@DelegatesTo.Target List<T> target, @DelegatesTo(genericTypeIndex=1) Closure cl) { + cl.delegate = target.join('') + cl() + } - result + map(['f','o','o']) { + toUpperCase() } - assert 'FOO'==test() - ''', 'Index of generic type @DelegatesTo(genericTypeIndex=1) greater than those of the selected type', 'Cannot find matching method' + ''', + 'Index of generic type @DelegatesTo(genericTypeIndex=1) greater than those of the selected type', 'Cannot find matching method' } void testDelegatesToGenericTypeArgumentWithNegativeIndex() { shouldFailWithMessages ''' - public <T> Object map(@DelegatesTo.Target List<T> target, @DelegatesTo(genericTypeIndex=-1) Closure arg) { - arg.delegate = target.join('') - arg() - } - def test() { - def result - map(['f','o','o']) { - result = toUpperCase() - } + public <T> Object map(@DelegatesTo.Target List<T> target, @DelegatesTo(genericTypeIndex=-1) Closure cl) { + cl.delegate = target.join('') + cl() + } - result + map(['f','o','o']) { + toUpperCase() } - assert 'FOO'==test() - ''', 'Index of generic type @DelegatesTo(genericTypeIndex=-1) lower than those of the selected type', 'Cannot find matching method' + ''', + 'Index of generic type @DelegatesTo(genericTypeIndex=-1) lower than those of the selected type', 'Cannot find matching method' } void testDelegatesToGenericTypeArgumentUsingMap() { @@ -631,58 +604,54 @@ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { void testDelegatesToGenericTypeArgumentUsingMapAndWrongIndex() { shouldFailWithMessages ''' - public <K,V> void transform(@DelegatesTo.Target Map<K, V> map, @DelegatesTo(genericTypeIndex = 0) Closure<?> closure) { + public <K,V> void transform(@DelegatesTo.Target Map<K, V> map, @DelegatesTo(genericTypeIndex = 0) Closure<?> cl) { map.keySet().each { - closure.delegate = map[it] - map[it] = closure() + cl.delegate = map[it] + map[it] = cl() } } + def map = [1: 'a', 2: 'b', 3: 'c'] transform(map) { toUpperCase() } - assert map == [1: 'A', 2: 'B', 3: 'C'] - ''', 'Cannot find matching method' + ''', + 'Cannot find matching method' } // GROOVY-6165 void testDelegatesToGenericArgumentTypeAndTypo() { - - String code = '''import groovy.transform.* - - @TupleConstructor - class Person { String name } - - public <T> List<T> names( - @DelegatesTo.Target List<T> list, - @DelegatesTo(genericTypeIndex = 0) Closure modify) { - list.collect { - modify.delegate = it - modify() - } - } - - def test(List<Person> persons) { - def names = names(persons) { - getname().toUpperCase() - } - assert names == ['GUILLAUME', 'CEDRIC'] - } - - test([new Person('Guillaume'), new Person('Cedric')]) - ''' - - String msg = 'Cannot find matching method' - /* * Because the Parrot parser provides more accurate node position information, * org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor.addError will not be interfered by wrong node position. * * 1) TestScripttestDelegatesToGenericArgumentTypeAndTypo0.groovy: 17: [Static type checking] - Cannot find matching method TestScripttestDelegatesToGenericArgumentTypeAndTypo0#getname(). Please check if the declared type is correct and if the method exists. * 2) TestScripttestDelegatesToGenericArgumentTypeAndTypo0.groovy: 17: [Static type checking] - Cannot find matching method java.lang.Object#toUpperCase(). Please check if the declared type is correct and if the method exists. - * */ - shouldFailWithMessages code, msg, msg + shouldFailWithMessages ''' + @groovy.transform.TupleConstructor + class Person { String name } + + def <T> List<T> names( + @DelegatesTo.Target List<T> list, + @DelegatesTo(genericTypeIndex = 0) Closure cl) { + list.collect { + cl.delegate = it + cl() + } + } + + def test(List<Person> persons) { + def names = names(persons) { + getname().toUpperCase() + } + assert names == ['GUILLAUME', 'CEDRIC'] + } + + test([new Person('Guillaume'), new Person('Cedric')]) + ''', + 'Cannot find matching method', + 'Cannot find matching method' } // GROOVY-6323, GROOVY-6325, GROOVY-6332 @@ -693,153 +662,115 @@ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { String model } - class MyCarMain { - MyCar configureCar(@DelegatesTo(MyCar) Closure closure) { - def car = new MyCar() - closure.delegate = car - closure.resolveStrategy = Closure.DELEGATE_FIRST - closure() - car - } - static void main(String[] args) { - def main = new MyCarMain() - def car = main.configureCar { - brand = "BMW" - model = brand + " X5" - } - assert car.model == "BMW X5" - } + MyCar configureCar(@DelegatesTo(value=MyCar, strategy=DELEGATE_FIRST) Closure cl) { + new MyCar().tap(cl) + } + + def car = configureCar { + brand = 'BMW' + model = "$brand X5" } - MyCarMain.main() + assert car.model == 'BMW X5' ''' assertScript ''' class MyCar { private String _brand private String _model - String getBrand() { return _brand } - - void setBrand(String brand) { - _brand = brand - } - String getModel() { return _model } - + void setBrand(String brand) { + _brand = brand + } void setModel(String model) { _model = model } } - class MyCarMain { - MyCar configureCar(@DelegatesTo(value = MyCar, strategy = Closure.DELEGATE_FIRST) Closure closure) { - def car = new MyCar() - closure.delegate = car - closure.resolveStrategy = Closure.DELEGATE_FIRST - closure() - car - } + MyCar configureCar(@DelegatesTo(value=MyCar, strategy=DELEGATE_FIRST) Closure cl) { + new MyCar().tap(cl) + } - static void main(String[] args) { - def main = new MyCarMain() - def car = main.configureCar { - brand = "BMW" - model = brand - } - assert car.model == "BMW" - } + def car = configureCar { + brand = 'BMW' + model = brand } - MyCarMain.main() + assert car.model == 'BMW' ''' assertScript ''' class Car { - private String _brand - String getBrand() { _brand } - void setBrand(String brand) { _brand = brand } + private String _brand + String getBrand() { _brand } + void setBrand(String brand) { _brand = brand } } class Builder { - def <T> T configure(@DelegatesTo.Target Class<T> target, @DelegatesTo(genericTypeIndex=0) Closure cl) { - def obj = target.newInstance() - cl.delegate = obj - cl.resolveStrategy = Closure.DELEGATE_FIRST - cl.call() - obj - } - } - - class Main { - void run() { - def builder = new Builder() - def car = builder.configure(Car) { - brand = brand + def <T> T configure(@DelegatesTo.Target Class<T> target, @DelegatesTo(genericTypeIndex=0) Closure cl) { + def obj = target.newInstance() + cl.delegate = obj + cl.resolveStrategy = DELEGATE_FIRST + cl.call() + obj } - } } - new Main().run() + def builder = new Builder() + def car = builder.configure(Car) { + brand = brand + } ''' } // GROOVY-5998 void testSubscriptOperatorOnPropertiesWithBuilder() { assertScript ''' - import static groovy.lang.Closure.* - class DatasourceBuilder { Map<String,String> attrs = [:] } - void datasource(@DelegatesTo(strategy = DELEGATE_FIRST, value = DatasourceBuilder) Closure c) {} - - void foo() { - datasource { - attrs['some'] = 'foo' - } + void datasource(@DelegatesTo(value=DatasourceBuilder, strategy=DELEGATE_FIRST) Closure cl) { } - foo() + datasource { + attrs['some'] = 'foo' + } ''' } - void testDelegatesToNestedGenericType() { + void testDelegatesToWithType1() { assertScript ''' - trait Configurable<ConfigObject> { - ConfigObject configObject - - void configure(Closure<Void> configSpec) { - configSpec.resolveStrategy = Closure.DELEGATE_FIRST - configSpec.delegate = configObject - configSpec() + trait Configurable<Type> { + Type configObject + void configure(@DelegatesTo(type='Type') Closure<Void> spec) { + spec.resolveStrategy = DELEGATE_FIRST + spec.delegate = configObject + spec.call() } } - public <T,U extends Configurable<T>> U configure(Class<U> clazz, @DelegatesTo(type="T") Closure configSpec) { - Configurable<T> obj = (Configurable<T>) clazz.newInstance() - obj.configure(configSpec) - obj - } + class ModuleConfig { String name, version } class Module implements Configurable<ModuleConfig> { String value - - Module(){ + Module() { configObject = new ModuleConfig() - } - - @Override - void configure(Closure<Void> configSpec) { + } + @Override + void configure(Closure<Void> configSpec) { Configurable.super.configure(configSpec) value = "${configObject.name}-${configObject.version}" - } + } } - class ModuleConfig { - String name - String version + + def <T,U extends Configurable<T>> U configure(Class<U> clazz, @DelegatesTo(type='T') Closure spec) { + Configurable<T> obj = (Configurable<T>) clazz.newInstance() + obj.configure(spec) + obj } + def module = configure(Module) { name = 'test' version = '1.0' @@ -848,12 +779,28 @@ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { ''' } + // GROOVY-11168 void testDelegatesToWithType2() { assertScript ''' - public <T> boolean evalAsSet(List<T> list, @DelegatesTo(type="Set<T>") Closure<Boolean> cl) { - cl.delegate = list as Set + def <T> T m(@DelegatesTo(type='T', strategy=DELEGATE_FIRST) Closure cl) { + 'WORKS'.with(cl) + } + + String result + this.<String>m { + result = toLowerCase() + } + assert result == 'works' + ''' + } + + void testDelegatesToWithType3() { + assertScript ''' + def <T> boolean evalAsSet(List<T> list, @DelegatesTo(type='Set<T>') Closure<Boolean> cl) { + cl.delegate = list.toSet() cl() } + assert evalAsSet([1,1,2,3]) { size() == 3 } @@ -864,8 +811,8 @@ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { void testErrorForMismatchedClosureResolveStrategy() { shouldFailWithMessages ''' class Foo { - def build(Closure x) { // resolve strategy OWNER_FIRST - this.with(x) // resolve strategy DELEGATE_FIRST + def build(Closure cl) { // resolve strategy OWNER_FIRST + this.with(cl) // resolve strategy DELEGATE_FIRST } def propertyMissing(String name) { 'something' @@ -882,6 +829,7 @@ class DelegatesToSTCTest extends StaticTypeCheckingTestCase { } new Bar().baz() - ''', 'Closure parameter with resolve strategy OWNER_FIRST passed to method with resolve strategy DELEGATE_FIRST' + ''', + 'Closure parameter with resolve strategy OWNER_FIRST passed to method with resolve strategy DELEGATE_FIRST' } }