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 d389568a8ab..15b9f9d4595 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -585,30 +585,30 @@ public void visitVariableExpression(final VariableExpression vexp) { if (enclosingClosure != null) { switch (name) { - case "delegate": - DelegationMetadata dm = getDelegationMetadata(enclosingClosure.getClosureExpression()); - if (dm != null) { - storeType(vexp, dm.getType()); - return; - } - // falls through - case "owner": - if (typeCheckingContext.getEnclosingClosureStack().size() > 1) { - storeType(vexp, CLOSURE_TYPE); - return; - } - // falls through - case "thisObject": - storeType(vexp, typeCheckingContext.getEnclosingClassNode()); - return; - case "parameterTypes": - storeType(vexp, CLASS_Type.makeArray()); + case "delegate": + DelegationMetadata dm = getDelegationMetadata(enclosingClosure.getClosureExpression()); + if (dm != null) { + vexp.putNodeMetaData(INFERRED_TYPE, dm.getType()); return; - case "maximumNumberOfParameters": - case "resolveStrategy": - case "directive": - storeType(vexp, int_TYPE); + } + // falls through + case "owner": + if (typeCheckingContext.getEnclosingClosureStack().size() > 1) { + vexp.putNodeMetaData(INFERRED_TYPE, CLOSURE_TYPE.getPlainNodeReference()); return; + } + // falls through + case "thisObject": + vexp.putNodeMetaData(INFERRED_TYPE, makeThis()); + return; + case "parameterTypes": + vexp.putNodeMetaData(INFERRED_TYPE, CLASS_Type.getPlainNodeReference().makeArray()); + return; + case "maximumNumberOfParameters": + case "resolveStrategy": + case "directive": + vexp.putNodeMetaData(INFERRED_TYPE, int_TYPE); + return; } } @@ -3553,7 +3553,7 @@ public void visitMethodCallExpression(final MethodCallExpression call) { if (mn.isEmpty() && call.isImplicitThis() && isThisExpression(objectExpression) && typeCheckingContext.getEnclosingClosure() != null) { mn = CLOSURE_TYPE.getDeclaredMethods(name); if (!mn.isEmpty()) { - chosenReceiver = Receiver.make(CLOSURE_TYPE); + receiver = CLOSURE_TYPE.getPlainNodeReference(); objectExpression.removeNodeMetaData(INFERRED_TYPE); } } @@ -3585,7 +3585,27 @@ public void visitMethodCallExpression(final MethodCallExpression call) { } ClassNode returnType = getType(directMethodCallCandidate); - if (isUsingGenericsOrIsArrayUsingGenerics(returnType)) { + // GROOVY-5470, GROOVY-6091, GROOVY-9604, GROOVY-11399: + if (call.isImplicitThis() && isThisExpression(objectExpression) && typeCheckingContext.getEnclosingClosure() != null + && (directMethodCallCandidate == GET_DELEGATE || directMethodCallCandidate == GET_OWNER || directMethodCallCandidate == GET_THISOBJECT)) { + switch (name) { + case "getDelegate": + DelegationMetadata dm = getDelegationMetadata(typeCheckingContext.getEnclosingClosure().getClosureExpression()); + if (dm != null) { + returnType = dm.getType(); + break; + } + // falls through + case "getOwner": + if (typeCheckingContext.getEnclosingClosureStack().size() > 1) { + returnType = CLOSURE_TYPE.getPlainNodeReference(); + break; + } + // falls through + case "getThisObject": + returnType = makeThis(); + } + } else if (isUsingGenericsOrIsArrayUsingGenerics(returnType)) { visitMethodCallArguments(chosenReceiver.getType(), argumentList, true, directMethodCallCandidate); for (Expression argument : argumentList.getExpressions()) { if (argument instanceof ClosureExpression) { @@ -3599,15 +3619,6 @@ public void visitMethodCallExpression(final MethodCallExpression call) { ClassNode irtg = inferReturnTypeGenerics(chosenReceiver.getType(), directMethodCallCandidate, callArguments, call.getGenericsTypes()); returnType = (irtg != null && implementsInterfaceOrIsSubclassOf(irtg, returnType) ? irtg : returnType); } - // GROOVY-6091: use of "delegate" or "getDelegate()" does not make use of @DelegatesTo metadata - if (directMethodCallCandidate == GET_DELEGATE && typeCheckingContext.getEnclosingClosure() != null) { - DelegationMetadata md = getDelegationMetadata(typeCheckingContext.getEnclosingClosure().getClosureExpression()); - if (md != null) { - returnType = md.getType(); - } else { - returnType = typeCheckingContext.getEnclosingClassNode(); - } - } Parameter[] parameters = directMethodCallCandidate.getParameters(); // GROOVY-7106, GROOVY-7274, GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, GROOVY-9915, et al. if (chosenReceiver.getType().getGenericsTypes() != null && !directMethodCallCandidate.isStatic() && !(directMethodCallCandidate instanceof ExtensionMethodNode)) { @@ -5164,9 +5175,6 @@ protected ClassNode getType(final ASTNode exp) { return ((ConstructorCallExpression) exp).getType(); } if (exp instanceof MethodNode) { - if ((exp == GET_DELEGATE || exp == GET_OWNER || exp == GET_THISOBJECT) && typeCheckingContext.getEnclosingClosure() != null) { - return typeCheckingContext.getEnclosingClassNode(); - } ClassNode ret = getInferredReturnType(exp); return ret != null ? ret : ((MethodNode) exp).getReturnType(); } diff --git a/src/test/groovy/transform/stc/BugsSTCTest.groovy b/src/test/groovy/transform/stc/BugsSTCTest.groovy index fc2a253ef09..2b5c4b44669 100644 --- a/src/test/groovy/transform/stc/BugsSTCTest.groovy +++ b/src/test/groovy/transform/stc/BugsSTCTest.groovy @@ -219,90 +219,6 @@ class BugsSTCTest extends StaticTypeCheckingTestCase { ''' } - void testClosureThisObjectDelegateOwnerProperty() { - assertScript ''' - class C { - void m() { - C that = this; - - { -> - @ASTTest(phase=INSTRUCTION_SELECTION, value={ - assert node.getNodeMetaData(INFERRED_TYPE)?.name == 'C' - }) - def ref = thisObject - assert ref == that - }(); - - { -> - @ASTTest(phase=INSTRUCTION_SELECTION, value={ - assert node.getNodeMetaData(INFERRED_TYPE)?.name == 'C' - }) - def ref = delegate - assert ref == that - }(); - - { -> - @ASTTest(phase=INSTRUCTION_SELECTION, value={ - assert node.getNodeMetaData(INFERRED_TYPE)?.name == 'C' - }) - def ref = owner - assert ref == that - }(); - } - } - new C().m() - ''' - } - - void testClosureThisObjectDelegateOwnerAccessor() { - assertScript ''' - class C { - void m() { - C that = this; - - { -> - @ASTTest(phase=INSTRUCTION_SELECTION, value={ - assert node.getNodeMetaData(INFERRED_TYPE)?.name == 'C' - }) - def ref = getThisObject() - assert ref == that - }(); - - { -> - @ASTTest(phase=INSTRUCTION_SELECTION, value={ - assert node.getNodeMetaData(INFERRED_TYPE)?.name == 'C' - }) - def ref = getDelegate() - assert ref == that - }(); - - { -> - @ASTTest(phase=INSTRUCTION_SELECTION, value={ - assert node.getNodeMetaData(INFERRED_TYPE)?.name == 'C' - }) - def ref = getOwner() - assert ref == that - }(); - } - } - new C().m() - ''' - } - - // GROOVY-9604 - void testClosureResolveStrategy() { - assertScript ''' - class C { - def m() { - return { -> - resolveStrategy + getResolveStrategy() - }(); - } - } - assert new C().m() == 0 - ''' - } - // GROOVY-5616 void testAssignToGroovyObject() { assertScript ''' @@ -382,16 +298,6 @@ class BugsSTCTest extends StaticTypeCheckingTestCase { execute()''' } - // GROOVY-5874 (pt.1) - void testClosureSharedVariableInBinExp() { - shouldFailWithMessages ''' - def sum = 0 - def cl1 = { sum = sum + 1 } - def cl2 = { sum = new Date() } - - ''', 'A closure shared variable [sum] has been assigned with various types' - } - // GROOVY-5870 void testShouldNotThrowErrorIfTryingToCastToInterface() { assertScript ''' diff --git a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy index 00583880a68..49b21210185 100644 --- a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy +++ b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy @@ -39,11 +39,14 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase { ''' } + // GROOVY-11394 + @NotYetImplemented void testCallClosure3() { - shouldFail MissingMethodException, ''' + shouldFailWithMessages ''' def c = { -> } c("") - ''' + ''', + 'Cannot call closure that accepts [] with [java.lang.String]' } void testCallClosure4() { @@ -127,8 +130,6 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase { ''' } - // - void testClosureReturnTypeInference1() { assertScript ''' def c = { int a, int b -> return a + b } @@ -186,59 +187,8 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase { ''' } - // GROOVY-10082, GROOVY-10277 - @NotYetImplemented - void testClosureReturnTypeInference6() { - assertScript ''' - class A {} - class B extends A {} - Closure c = { -> new B() } - - def result = c() - assert result instanceof A - assert result instanceof B - ''' - shouldFailWithMessages ''' - Closure c = { -> 42 } - ''', - 'Cannot return value of type int for closure expecting java.lang.String' - } - - // GROOVY-10091, GROOVY-10277 - @NotYetImplemented - void testClosureReturnTypeInference7() { - shouldFailWithMessages ''' - class A {} - class B extends A {} - class X extends A {} - class Y extends A {} - - Closure> c - c = { -> return new B() } - c = { -> return new X() } - c = { -> return new Y() } - ''', - 'Cannot return value of type X for closure expecting A' - } - - // GROOVY-10792 - @NotYetImplemented - void testClosureReturnTypeInference8() { - shouldFailWithMessages ''' - void proc(Closure c) { - boolean result = c().booleanValue() - assert !result - } - def list = [] - proc { - list - } - ''', - 'Cannot return value of type java.util.List for closure expecting java.lang.Boolean' - } - // GROOVY-8202 - void testClosureReturnTypeInference9() { + void testClosureReturnTypeInference6() { assertScript ''' void proc() { } @@ -291,14 +241,14 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase { } // GROOVY-5145 - void testCollect() { + void testCollect1() { assertScript ''' List strings = [1,2,3].collect { it.toString() } ''' } // GROOVY-5145 - void testCollectWithSubclass() { + void testCollect2() { assertScript ''' class StringClosure extends Closure { StringClosure() { super(null,null) } @@ -308,36 +258,6 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase { ''' } - // GROOVY-7701 - void testWithDelegateVsOwnerField() { - assertScript ''' - class Foo { - List type - } - - class Bar { - int type = 10 - - @Lazy - List something = { -> - List tmp = [] - def foo = new Foo() - foo.with { - type = ['String'] - // ^^^^ should be Foo.type, not Bar.type - } - tmp.add(foo) - tmp - }() - } - - def bar = new Bar() - assert bar.type == 10 - assert bar.something*.type == [['String']] - assert bar.type == 10 - ''' - } - void testClosureSharedVariable1() { assertScript ''' def x = '123'; @@ -355,8 +275,18 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase { 'Cannot find matching method (java.io.Serializable or java.lang.Comparable',')#charAt(int)' } - // GROOVY-9516 + // GROOVY-5874 void testClosureSharedVariable3() { + shouldFailWithMessages ''' + def sum = 0 + def cl1 = { sum = sum + 1 } + def cl2 = { sum = new Date() } + ''', + 'A closure shared variable [sum] has been assigned with various types' + } + + // GROOVY-9516 + void testClosureSharedVariable4() { shouldFailWithMessages ''' class A {} class B extends A { def m() {} } @@ -373,7 +303,7 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase { } // GROOVY-10356 - void testClosureSharedVariable4() { + void testClosureSharedVariable5() { assertScript ''' interface A { void m() @@ -387,7 +317,7 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase { } // GROOVY-10052 - void testClosureSharedVariable5() { + void testClosureSharedVariable6() { assertScript ''' String x def f = { -> @@ -399,7 +329,7 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase { } // GROOVY-10052 - void testClosureSharedVariable6() { + void testClosureSharedVariable7() { assertScript ''' def x def f = { -> @@ -754,6 +684,36 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase { ''' } + // GROOVY-7701 + void testOwnerVersusDelegate() { + assertScript ''' + class Foo { + List type + } + + class Bar { + int type = 10 + + @Lazy + List something = { -> + List tmp = [] + def foo = new Foo() + foo.with { + type = ['String'] + // ^^^^ should be Foo.type, not Bar.type + } + tmp.add(foo) + tmp + }() + } + + def bar = new Bar() + assert bar.type == 10 + assert bar.something*.type == [['String']] + assert bar.type == 10 + ''' + } + // GROOVY-9089 void testOwnerVersusDelegateFromNestedClosure() { String declarations = ''' @@ -772,7 +732,6 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase { new B().with(block) } ''' - assertScript declarations + ''' outer { inner { @@ -782,7 +741,6 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase { } } ''' - assertScript declarations + ''' outer { inner { @@ -805,9 +763,125 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase { ''' } + // GROOVY-5470, GROOVY-6091, GROOVY-11399 + void testClosureThisObjectDelegateOwnerAccessor() { + for (meth in ['getThisObject()', 'getDelegate()', 'getOwner()']) { + assertScript """ + class C { + void m() { + C that = this; + { -> + @ASTTest(phase=INSTRUCTION_SELECTION, value={ + def type = node.getNodeMetaData(INFERRED_TYPE)?.toString(false) + assert type/*of $meth*/ == 'C' + }) + Object ref = $meth + assert ref == that + }() + } + } + new C().m() + """ + assertScript """ + class C { + static m() { + { -> + @ASTTest(phase=INSTRUCTION_SELECTION, value={ + def type = node.getNodeMetaData(INFERRED_TYPE)?.toString(false) + assert type/*of $meth*/ == 'java.lang.Class ' + }) + Object ref = $meth + assert ref == C + }() + } + } + C.m() + """ + } + for (meth in ['getDelegate()', 'getOwner()']) { + assertScript """ + def that + that = { -> + { -> + @ASTTest(phase=INSTRUCTION_SELECTION, value={ + def type = node.getNodeMetaData(INFERRED_TYPE) + assert type/*of $meth*/ == CLOSURE_TYPE + }) + Object ref = $meth + assert ref == that + }() + } + that.call() + """ + } + assertScript ''' + void test(Closure c) { + { -> + @ASTTest(phase=INSTRUCTION_SELECTION, value={ + def type = node.getNodeMetaData(INFERRED_TYPE) + assert type == OBJECT_TYPE + }) + def x = c.getThisObject() + }() + } + test({->}) + ''' + } + + // GROOVY-5470, GROOVY-6091, GROOVY-11399 + void testClosureThisObjectDelegateOwnerProperty() { + for (prop in ['thisObject', 'delegate', 'owner']) { + assertScript """ + class C { + void m() { + C that = this; + { -> + @ASTTest(phase=INSTRUCTION_SELECTION, value={ + def type = node.getNodeMetaData(INFERRED_TYPE)?.toString(false) + assert type/*of $prop*/ == 'C' + }) + Object ref = $prop + assert ref == that + }() + } + } + new C().m() + """ + assertScript """ + class C { + static m() { + { -> + @ASTTest(phase=INSTRUCTION_SELECTION, value={ + def type = node.getNodeMetaData(INFERRED_TYPE)?.toString(false) + assert type/*of $prop*/ == 'java.lang.Class ' + }) + Object ref = $prop + assert ref == C + }() + } + } + C.m() + """ + } + assertScript ''' + def that + that = { -> + { -> + @ASTTest(phase=INSTRUCTION_SELECTION, value={ + def type = node.getNodeMetaData(INFERRED_TYPE) + assert type == CLOSURE_TYPE + }) + Object ref = owner + assert ref == that + }() + } + that.call() + ''' + } + // GROOVY-9652 void testDelegatePropertyAndCharCompareOptimization() { - ['String', 'Character', 'char'].each { type -> + for (type in ['String', 'Character', 'char']) { assertScript """ class Node { String name @@ -885,4 +959,18 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase { assert result == 'foo' ''' } + + // GROOVY-9604 + void testClosureResolveStrategy() { + assertScript ''' + class C { + def m() { + return { -> + resolveStrategy + getResolveStrategy() + }() + } + } + assert new C().m() == 0 + ''' + } }