This is an automated email from the ASF dual-hosted git repository. emilles pushed a commit to branch GROOVY-9499 in repository https://gitbox.apache.org/repos/asf/groovy.git
commit ad95958a0141e4b2ff65aceaeeba204bd72d1069 Author: Eric Milles <eric.mil...@thomsonreuters.com> AuthorDate: Thu May 28 17:56:48 2020 -0500 GROOVY-9499: AIC as argument to this/super constructor call Groovy 2.5 backport --- .../classgen/InnerClassCompletionVisitor.java | 4 +- .../groovy/classgen/InnerClassVisitor.java | 128 +-- .../groovy/classgen/InnerClassVisitorHelper.java | 6 +- .../org/codehaus/groovy/classgen/Verifier.java | 15 +- src/test/gls/innerClass/InnerClassTest.groovy | 926 +++++++++++++++------ 5 files changed, 772 insertions(+), 307 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java b/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java index 452ac65..91e3eb1 100644 --- a/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java +++ b/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java @@ -160,7 +160,7 @@ public class InnerClassCompletionVisitor extends InnerClassVisitorHelper impleme private void getThis(MethodVisitor mv, String classInternalName, String outerClassDescriptor, String innerClassInternalName) { mv.visitVarInsn(ALOAD, 0); - if (CLOSURE_TYPE.equals(thisField.getType())) { + if (thisField != null && CLOSURE_TYPE.equals(thisField.getType())) { mv.visitFieldInsn(GETFIELD, classInternalName, "this$0", CLOSURE_DESCRIPTOR); mv.visitMethodInsn(INVOKEVIRTUAL, CLOSURE_INTERNAL_NAME, "getThisObject", "()Ljava/lang/Object;", false); mv.visitTypeInsn(CHECKCAST, innerClassInternalName); @@ -168,7 +168,7 @@ public class InnerClassCompletionVisitor extends InnerClassVisitorHelper impleme mv.visitFieldInsn(GETFIELD, classInternalName, "this$0", outerClassDescriptor); } } - + private void addDefaultMethods(InnerClassNode node) { final boolean isStatic = isStatic(node); diff --git a/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitor.java b/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitor.java index d2d570f..33f07f1 100644 --- a/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitor.java +++ b/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitor.java @@ -20,7 +20,10 @@ package org.codehaus.groovy.classgen; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.CodeVisitorSupport; +import org.codehaus.groovy.ast.ConstructorNode; import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.GroovyCodeVisitor; import org.codehaus.groovy.ast.InnerClassNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; @@ -45,14 +48,11 @@ import java.util.List; public class InnerClassVisitor extends InnerClassVisitorHelper implements Opcodes { - private final SourceUnit sourceUnit; private ClassNode classNode; - private static final int PUBLIC_SYNTHETIC = Opcodes.ACC_PUBLIC + Opcodes.ACC_SYNTHETIC; - private FieldNode thisField = null; - private MethodNode currentMethod; private FieldNode currentField; - private boolean processingObjInitStatements = false; - private boolean inClosure = false; + private MethodNode currentMethod; + private final SourceUnit sourceUnit; + private boolean inClosure, processingObjInitStatements; public InnerClassVisitor(CompilationUnit cu, SourceUnit su) { sourceUnit = su; @@ -65,13 +65,12 @@ public class InnerClassVisitor extends InnerClassVisitorHelper implements Opcode @Override public void visitClass(ClassNode node) { - this.classNode = node; - thisField = null; + classNode = node; InnerClassNode innerClass = null; if (!node.isEnum() && !node.isInterface() && node instanceof InnerClassNode) { innerClass = (InnerClassNode) node; - if (!isStatic(innerClass) && innerClass.getVariableScope() == null) { - thisField = innerClass.addField("this$0", PUBLIC_SYNTHETIC, node.getOuterClass().getPlainNodeReference(), null); + if (innerClass.getVariableScope() == null && (innerClass.getModifiers() & ACC_STATIC) == 0) { + innerClass.addField("this$0", ACC_FINAL | ACC_SYNTHETIC, node.getOuterClass().getPlainNodeReference(), null); } } @@ -85,7 +84,7 @@ public class InnerClassVisitor extends InnerClassVisitorHelper implements Opcode node.setUnresolvedSuperClass(ClassHelper.OBJECT_TYPE); } } - + @Override public void visitClosureExpression(ClosureExpression expression) { boolean inClosureOld = inClosure; @@ -103,7 +102,7 @@ public class InnerClassVisitor extends InnerClassVisitorHelper implements Opcode @Override protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) { - this.currentMethod = node; + currentMethod = node; visitAnnotations(node); visitClassCodeContainer(node.getCode()); // GROOVY-5681: initial expressions should be visited too! @@ -113,14 +112,14 @@ public class InnerClassVisitor extends InnerClassVisitorHelper implements Opcode } visitAnnotations(param); } - this.currentMethod = null; + currentMethod = null; } @Override public void visitField(FieldNode node) { - this.currentField = node; + currentField = node; super.visitField(node); - this.currentField = null; + currentField = null; } @Override @@ -143,9 +142,8 @@ public class InnerClassVisitor extends InnerClassVisitorHelper implements Opcode InnerClassNode innerClass = (InnerClassNode) call.getType(); ClassNode outerClass = innerClass.getOuterClass(); ClassNode superClass = innerClass.getSuperClass(); - if (superClass instanceof InnerClassNode - && !superClass.isInterface() - && !(superClass.isStaticClass()||((superClass.getModifiers()&ACC_STATIC)==ACC_STATIC))) { + if (!superClass.isInterface() && superClass.getOuterClass() != null + && !(superClass.isStaticClass() || (superClass.getModifiers() & ACC_STATIC) != 0)) { insertThis0ToSuperCall(call, innerClass); } if (!innerClass.getDeclaredConstructors().isEmpty()) return; @@ -154,26 +152,25 @@ public class InnerClassVisitor extends InnerClassVisitorHelper implements Opcode VariableScope scope = innerClass.getVariableScope(); if (scope == null) return; + boolean isStatic = !inClosure && isStatic(innerClass, scope, call); + // expressions = constructor call arguments List<Expression> expressions = ((TupleExpression) call.getArguments()).getExpressions(); // block = init code for the constructor we produce BlockStatement block = new BlockStatement(); // parameters = parameters of the constructor - final int additionalParamCount = 1 + scope.getReferencedLocalVariablesCount(); + int additionalParamCount = (isStatic ? 0 : 1) + scope.getReferencedLocalVariablesCount(); List<Parameter> parameters = new ArrayList<Parameter>(expressions.size() + additionalParamCount); // superCallArguments = arguments for the super call == the constructor call arguments List<Expression> superCallArguments = new ArrayList<Expression>(expressions.size()); - // first we add a super() call for all expressions given in the + // first we add a super() call for all expressions given in the // constructor call expression - int pCount = additionalParamCount; - for (Expression expr : expressions) { - pCount++; - // add one parameter for each expression in the - // constructor call - Parameter param = new Parameter(ClassHelper.OBJECT_TYPE, "p" + pCount); + for (int i = 0, n = expressions.size(); i < n; i += 1) { + // add one parameter for each expression in the constructor call + Parameter param = new Parameter(ClassHelper.OBJECT_TYPE, "p" + additionalParamCount + i); parameters.add(param); - // add to super call + // add the corresponsing argument to the super constructor call superCallArguments.add(new VariableExpression(param)); } @@ -185,23 +182,24 @@ public class InnerClassVisitor extends InnerClassVisitorHelper implements Opcode block.addStatement(new ExpressionStatement(cce)); - // we need to add "this" to access unknown methods/properties - // this is saved in a field named this$0 - pCount = 0; - expressions.add(pCount, VariableExpression.THIS_EXPRESSION); - boolean isStatic = isStaticThis(innerClass,scope); - ClassNode outerClassType = getClassNode(outerClass, isStatic); - if (!isStatic && inClosure) outerClassType = ClassHelper.CLOSURE_TYPE; - outerClassType = outerClassType.getPlainNodeReference(); - Parameter thisParameter = new Parameter(outerClassType, "p" + pCount); - parameters.add(pCount, thisParameter); + int pCount = 0; + if (!isStatic) { + // need to pass "this" to access unknown methods/properties + expressions.add(pCount, VariableExpression.THIS_EXPRESSION); + + ClassNode enclosingType = (inClosure ? ClassHelper.CLOSURE_TYPE : outerClass).getPlainNodeReference(); + Parameter thisParameter = new Parameter(enclosingType, "p" + pCount); + parameters.add(pCount, thisParameter); - thisField = innerClass.addField("this$0", PUBLIC_SYNTHETIC, outerClassType, null); - addFieldInit(thisParameter, thisField, block); + // "this" reference is saved in a field named "this$0" + FieldNode thisField = innerClass.addField("this$0", ACC_FINAL | ACC_SYNTHETIC, enclosingType, null); + addFieldInit(thisParameter, thisField, block); + + pCount += 1; + } // for each shared variable we add a reference and save it as field for (Iterator it = scope.getReferencedLocalVariablesIterator(); it.hasNext();) { - pCount++; org.codehaus.groovy.ast.Variable var = (org.codehaus.groovy.ast.Variable) it.next(); VariableExpression ve = new VariableExpression(var); ve.setClosureSharedVariable(true); @@ -215,25 +213,51 @@ public class InnerClassVisitor extends InnerClassVisitorHelper implements Opcode final VariableExpression initial = new VariableExpression(p); initial.setSynthetic(true); initial.setUseReferenceDirectly(true); - final FieldNode pField = innerClass.addFieldFirst(ve.getName(), PUBLIC_SYNTHETIC,rawReferenceType, initial); + final FieldNode pField = innerClass.addFieldFirst(ve.getName(), ACC_PUBLIC | ACC_SYNTHETIC, rawReferenceType, initial); pField.setHolder(true); pField.setOriginType(ClassHelper.getWrapper(var.getOriginType())); + pCount += 1; } innerClass.addConstructor(ACC_SYNTHETIC, parameters.toArray(Parameter.EMPTY_ARRAY), ClassNode.EMPTY_ARRAY, block); } - private boolean isStaticThis(InnerClassNode innerClass, VariableScope scope) { - if (inClosure) return false; - boolean ret = innerClass.isStaticClass(); - if ( innerClass.getEnclosingMethod()!=null) { - ret = ret || innerClass.getEnclosingMethod().isStatic(); - } else if (currentField!=null) { - ret = ret || currentField.isStatic(); - } else if (currentMethod!=null && "<clinit>".equals(currentMethod.getName())) { - ret = true; + private boolean isStatic(InnerClassNode innerClass, VariableScope scope, final ConstructorCallExpression call) { + boolean isStatic = innerClass.isStaticClass(); + if (!isStatic) { + if (currentMethod != null) { + if (currentMethod instanceof ConstructorNode) { + ConstructorNode ctor = (ConstructorNode) currentMethod; + final boolean[] precedesSuperOrThisCall = new boolean[1]; + + GroovyCodeVisitor visitor = new CodeVisitorSupport() { + @Override + public void visitConstructorCallExpression(ConstructorCallExpression cce) { + if (cce == call) { + precedesSuperOrThisCall[0] = true; + } else { + super.visitConstructorCallExpression(cce); + } + } + }; + if (ctor.firstStatementIsSpecialConstructorCall()) { + currentMethod.getFirstStatement().visit(visitor); + } + for (Parameter param : ctor.getParameters()) { + if (param.hasInitialExpression()) { + param.getInitialExpression().visit(visitor); + } + } + + isStatic = precedesSuperOrThisCall[0]; + } else { + isStatic = currentMethod.isStatic(); + } + } else if (currentField != null) { + isStatic = currentField.isStatic(); + } } - return ret; + return isStatic; } // this is the counterpart of addThisReference(). To non-static inner classes, outer this should be @@ -252,7 +276,7 @@ public class InnerClassVisitor extends InnerClassVisitorHelper implements Opcode // if constructor call is not in static context, return if (isInStaticContext) { - // constructor call is in static context and the inner class is non-static - 1st arg is supposed to be + // constructor call is in static context and the inner class is non-static - 1st arg is supposed to be // passed as enclosing "this" instance // Expression args = call.getArguments(); diff --git a/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java b/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java index 9452768..4b67f23 100644 --- a/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java +++ b/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java @@ -24,7 +24,6 @@ import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.InnerClassNode; import org.codehaus.groovy.ast.Parameter; -import org.codehaus.groovy.ast.VariableScope; import org.codehaus.groovy.ast.expr.ArgumentListExpression; import org.codehaus.groovy.ast.expr.BinaryExpression; import org.codehaus.groovy.ast.expr.ConstantExpression; @@ -46,6 +45,7 @@ import java.util.ArrayList; import java.util.List; public abstract class InnerClassVisitorHelper extends ClassCodeVisitorSupport { + protected static void setPropertyGetterDispatcher(BlockStatement block, Expression thiz, Parameter[] parameters) { List<ConstantExpression> gStringStrings = new ArrayList<ConstantExpression>(); gStringStrings.add(new ConstantExpression("")); @@ -102,9 +102,7 @@ public abstract class InnerClassVisitorHelper extends ClassCodeVisitorSupport { } protected static boolean isStatic(InnerClassNode node) { - VariableScope scope = node.getVariableScope(); - if (scope != null) return scope.getParent().isInStaticContext(); - return (node.getModifiers() & Opcodes.ACC_STATIC) != 0; + return node.getDeclaredField("this$0") == null; } protected static ClassNode getClassNode(ClassNode node, boolean isStatic) { diff --git a/src/main/java/org/codehaus/groovy/classgen/Verifier.java b/src/main/java/org/codehaus/groovy/classgen/Verifier.java index 0ed2678..ab69fd8 100644 --- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java +++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java @@ -57,7 +57,6 @@ import org.codehaus.groovy.ast.stmt.ExpressionStatement; import org.codehaus.groovy.ast.stmt.ReturnStatement; import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.ast.tools.GenericsUtils; -import org.codehaus.groovy.ast.tools.PropertyNodeUtils; import org.codehaus.groovy.classgen.asm.BytecodeHelper; import org.codehaus.groovy.classgen.asm.MopWriter; import org.codehaus.groovy.classgen.asm.OptimizingStatementWriter.ClassNodeSkip; @@ -894,8 +893,20 @@ public class Verifier implements GroovyClassVisitor, Opcodes { } protected void addConstructor(Parameter[] newParams, ConstructorNode ctor, Statement code, ClassNode node) { - ConstructorNode genConstructor = node.addConstructor(ctor.getModifiers(), newParams, ctor.getExceptions(), code); + final ConstructorNode genConstructor = node.addConstructor(ctor.getModifiers(), newParams, ctor.getExceptions(), code); + genConstructor.putNodeMetaData(DEFAULT_PARAMETER_GENERATED, Boolean.TRUE); markAsGenerated(node, genConstructor); + + // set anon. inner enclosing method reference + code.visit(new CodeVisitorSupport() { + @Override + public void visitConstructorCallExpression(ConstructorCallExpression call) { + if (call.isUsingAnonymousInnerClass()) { + call.getType().setEnclosingMethod(genConstructor); + } + super.visitConstructorCallExpression(call); + } + }); } /** diff --git a/src/test/gls/innerClass/InnerClassTest.groovy b/src/test/gls/innerClass/InnerClassTest.groovy index 8e622ee..b6b5e8e 100644 --- a/src/test/gls/innerClass/InnerClassTest.groovy +++ b/src/test/gls/innerClass/InnerClassTest.groovy @@ -18,12 +18,20 @@ */ package gls.innerClass -import gls.CompilableTestSupport +import groovy.transform.CompileStatic +import groovy.transform.NotYetImplemented +import org.codehaus.groovy.control.CompilationFailedException +import org.junit.Test -class InnerClassTest extends CompilableTestSupport { +import static groovy.test.GroovyAssert.assertScript +import static groovy.test.GroovyAssert.shouldFail +@CompileStatic +final class InnerClassTest { + + @Test void testTimerAIC() { - assertScript """ + assertScript ''' import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -31,54 +39,98 @@ class InnerClassTest extends CompilableTestSupport { Timer timer = new Timer() timer.schedule(new TimerTask() { + @Override void run() { called.countDown() } }, 0) assert called.await(10, TimeUnit.SECONDS) - """ + ''' } - void testAICReferenceInClosure() { - assertScript """ - def y = [true] + @Test + void testAccessLocalVariableFromClosureInAIC() { + assertScript ''' + def x = [true] def o = new Object() { - def foo() { - def c = { - assert y[0] + def m() { + def c = { -> + assert x[0] + } + c() } - c() - } } - o.foo() - """ + o.m() + ''' + + shouldFail ''' + def x = [false] + def o = new Object() { + def m() { + def c = { -> + assert x[0] + } + c() + } + } + o.m() + ''' } - void testExtendsObjectAndAccessAFinalVariableInScope() { - assertScript """ + @Test + void testAccessFinalLocalVariableFromMethodInAIC() { + assertScript ''' final String objName = "My name is Guillaume" assert new Object() { String toString() { objName } }.toString() == objName - """ + ''' + } + + @Test // GROOVY-9499 + void testAccessStaticMethodFromAICInSuperCtorCall() { + assertScript ''' + class One { + One(ref) { + HASH_CODE = ref.hashCode() + } + public static int HASH_CODE + } + + class Two extends One { + Two() { + super(new Object() { // AIC before special ctor call completes + int hashCode() { + hash() // should be able to call static method safely + } + }) + } + static int hash() { 42 } + } + + def obj = new Two() + assert One.HASH_CODE == 42 + ''' } - void testExtendsObjectAndReferenceAMethodParameterWithinAGString() { - assertScript """ + @Test + void testAccessMethodParameterFromGStringInAICMethod() { + assertScript ''' Object makeObj0(String name) { - new Object() { + new Object() { String toString() { "My name is \${name}" } - } + } } assert makeObj0("Guillaume").toString() == "My name is Guillaume" - """ + ''' } - void testExtendsObjectAndReferenceAGStringPropertyDependingOnAMethodParameter() { - assertScript """ + @Test + void testAccessMethodParameterFromGStringInAICProperty() { + assertScript ''' Object makeObj1(String name) { new Object() { String objName = "My name is \${name}" @@ -88,11 +140,12 @@ class InnerClassTest extends CompilableTestSupport { } assert makeObj1("Guillaume").toString() == "My name is Guillaume" - """ + ''' } + @Test void testUsageOfInitializerBlockWithinAnAIC() { - assertScript """ + assertScript ''' Object makeObj2(String name) { new Object() { String objName @@ -108,56 +161,74 @@ class InnerClassTest extends CompilableTestSupport { } assert makeObj2("Guillaume").toString() == "My name is Guillaume" - """ + ''' } + @Test void testStaticInnerClass() { - assertScript """ - import java.lang.reflect.Modifier - + assertScript ''' + import static java.lang.reflect.Modifier.* + class A { - static class B{} + static class B {} } - def x = new A.B() - assert x != null - - def mods = A.B.modifiers - assert Modifier.isPublic(mods) - """ + def b = new A.B() + assert b != null + + int modifiers = A.B.modifiers + assert isPublic(modifiers) + ''' + } - assertScript """ + @Test + void testStaticInnerClass2() { + assertScript ''' class A { static class B{} } assert A.declaredClasses.length==1 assert A.declaredClasses[0]==A.B - """ + ''' } - void testNonStaticInnerClass_FAILS() { - if (notYetImplemented()) return + @Test + void testNonStaticInnerClass() { + assertScript ''' + class A { + class B { + final String foo = 'foo' + } + } + def b = new A.B(new A()) + assert b.foo == 'foo' + ''' + } - shouldNotCompile """ + @Test @NotYetImplemented + void testNonStaticInnerClass2() { + shouldFail CompilationFailedException, ''' class A { class B {} } - def x = new A.B() - """ + def x = new A.B() // requires reference to A + ''' } + @Test void testAnonymousInnerClass() { - assertScript """ + assertScript ''' class Foo {} def x = new Foo(){ def bar() { 1 } } assert x.bar() == 1 - """ + ''' } + @Test void testLocalVariable() { - assertScript """ + assertScript ''' class Foo {} final val = 2 def x = new Foo() { @@ -165,20 +236,22 @@ class InnerClassTest extends CompilableTestSupport { } assert x.bar() == val assert x.bar() == 2 - """ + ''' } + @Test void testConstructor() { - shouldNotCompile """ + shouldFail CompilationFailedException, ''' class Foo {} def x = new Foo() { Foo() {} } - """ + ''' } + @Test void testUsageOfOuterField() { - assertScript """ + assertScript ''' interface Run { def run() } @@ -198,9 +271,12 @@ class InnerClassTest extends CompilableTestSupport { assert foo.foo() == 1 foo.x(2) assert foo.foo() == 2 - """ + ''' + } - assertScript """ + @Test + void testUsageOfOuterField2() { + assertScript ''' interface Run { def run() } @@ -219,9 +295,12 @@ class InnerClassTest extends CompilableTestSupport { assert Foo.foo() == 1 Foo.x(2) assert Foo.foo() == 2 - """ + ''' + } - assertScript """ + @Test + void testUsageOfOuterField3() { + assertScript ''' interface X { def m() } @@ -240,9 +319,11 @@ class InnerClassTest extends CompilableTestSupport { } def a = new A() assert "pm" == a.foo() - """ + ''' + } - //GROOVY-6141 + @Test // GROOVY-6141 + void testUsageOfOuterField4() { assertScript ''' class A { def x = 1 @@ -271,8 +352,49 @@ class InnerClassTest extends CompilableTestSupport { ''' } - // GROOVY-9501 - void testUsageOfOuterField2() { + @Test // GROOVY-9189 + void testUsageOfOuterField5() { + assertScript ''' + interface Run { + def run() + } + class Foo { + private static x = 1 + + static foo(def runner = new Run() { + def run() { return x } + }) { + runner.run() + } + + static x(y) { x = y } + } + assert Foo.foo() == 1 + Foo.x(2) + assert Foo.foo() == 2 + ''' + } + + @Test // GROOVY-9168 + void testUsageOfOuterField6() { + assertScript ''' + class A { + // AIC in this position can use static properties: + A(Runnable action = new Runnable() { void run() { answer = 42 }}) { + this.action = action + } + Runnable action + static int answer + } + + def a = new A() + a.action.run(); + assert a.answer == 42 + ''' + } + + @Test // GROOVY-9501 + void testUsageOfOuterField7() { assertScript ''' class Main extends Outer { static main(args) { @@ -280,13 +402,16 @@ class InnerClassTest extends CompilableTestSupport { assert Outer.Inner.error == null } } + abstract class Outer { private static volatile boolean flag + void newThread() { Thread thread = new Inner() thread.start() thread.join() } + private final class Inner extends Thread { @Override void run() { @@ -304,8 +429,8 @@ class InnerClassTest extends CompilableTestSupport { ''' } - // inner class is static instead of final - void testUsageOfOuterField3() { + @Test // inner class is static instead of final + void testUsageOfOuterField8() { assertScript ''' class Main extends Outer { static main(args) { @@ -313,13 +438,16 @@ class InnerClassTest extends CompilableTestSupport { assert Outer.Inner.error == null } } + abstract class Outer { private static volatile boolean flag + void newThread() { Thread thread = new Inner() thread.start() thread.join() } + private static class Inner extends Thread { @Override void run() { @@ -337,8 +465,8 @@ class InnerClassTest extends CompilableTestSupport { ''' } - // GROOVY-9569 - void testUsageOfOuterField4() { + @Test // GROOVY-9569 + void testUsageOfOuterField9() { assertScript ''' class Main extends Outer { static main(args) { @@ -346,14 +474,17 @@ class InnerClassTest extends CompilableTestSupport { assert Outer.Inner.error == null } } + @groovy.transform.CompileStatic abstract class Outer { private static volatile boolean flag + void newThread() { Thread thread = new Inner() thread.start() thread.join() } + private static class Inner extends Thread { @Override void run() { @@ -371,45 +502,161 @@ class InnerClassTest extends CompilableTestSupport { ''' } - void testUsageOfOuterFieldOverridden_FAILS() { - if (notYetImplemented()) return + @Test + void testUsageOfOuterField10() { + assertScript ''' + class Outer { + static final String OUTER_CONSTANT = 'Constant Value' + + class Inner { + String access() { + return OUTER_CONSTANT + } + } + + void testInnerClassAccessOuterConst() { + def inner = new Inner() + assert inner.access() == OUTER_CONSTANT + } + } + + def outer = new Outer() + outer.testInnerClassAccessOuterConst() + ''' + } + + @Test // GROOVY-5259 + void testUsageOfOuterField11() { + assertScript ''' + class Base { + Base(String string) { + } + } + + class Outer { + static final String OUTER_CONSTANT = 'Constant Value' + + class Inner extends Base { + Inner() { + super(OUTER_CONSTANT) // "this" is not initialized yet + } + + String access() { + return OUTER_CONSTANT + } + } + + void testInnerClassAccessOuterConst() { + def inner = new Inner() + assert inner.access() == OUTER_CONSTANT + } + } + + def outer = new Outer() + outer.testInnerClassAccessOuterConst() + ''' + } + + @Test + void testUsageOfOuterSuperField() { + assertScript ''' + class InnerBase { + InnerBase(String string) { + } + } + + class OuterBase { + protected static final String OUTER_CONSTANT = 'Constant Value' + } + + class Outer extends OuterBase { + + class Inner extends InnerBase { + Inner() { + super(OUTER_CONSTANT) + } + + String access() { + return OUTER_CONSTANT + } + } + + void testInnerClassAccessOuterConst() { + def inner = new Inner() + assert inner.access() == OUTER_CONSTANT + } + } + + def outer = new Outer() + outer.testInnerClassAccessOuterConst() + ''' + } + + @Test + void testUsageOfOuterField_WrongCallToSuper() { + shouldFail ''' + class Outer { + protected static final String OUTER_CONSTANT = 'Constant Value' + + class Inner { + Inner() { + // there is no Object#<init>(String) method, but it throws a VerifyError for uninitialized this + super(OUTER_CONSTANT) + } - assertScript """ + String access() { + return OUTER_CONSTANT + } + } + + void testInnerClassAccessOuterConst() { + def inner = new Inner() + inner.access() + } + } + + def outer = new Outer() + outer.testInnerClassAccessOuterConst() + ''' + } + + @Test + void testUsageOfOuterFieldOverridden() { + assertScript ''' interface Run { def run() } class Foo { private x = 1 + def foo() { - def runner = new Run(){ - def run() { return x } + def runner = new Run() { + def run() { return x } // <-- dynamic variable } runner.run() } - void setX(y) { x=y } + + void setX(val) { x = val } } class Bar extends Foo { - def x = "string" + def x = 'string' // hides 'foo.@x' and overrides 'foo.setX(val)' } def bar = new Bar() - assert bar.foo() == 1 - bar.x(2) - assert bar.foo() == 2 - bar.x = "new string" - assert bar.foo() == 2 - """ - - //TODO: static part - + assert bar.foo() == 'string' + bar.x = 'new string' + assert bar.foo() == 'new string' + ''' } + @Test void testUsageOfOuterMethod() { - assertScript """ + assertScript ''' interface Run { def run() } class Foo { - private x(){1} + private x() { return 1 } + def foo() { def runner = new Run(){ def run() { return x() } @@ -419,14 +666,17 @@ class InnerClassTest extends CompilableTestSupport { } def foo = new Foo() assert foo.foo() == 1 - """ + ''' + } - assertScript """ + @Test + void testUsageOfOuterMethod2() { + assertScript ''' interface Run { def run() } class Foo { - private static x() {1} + private static x() { return 1 } def foo() { def runner = new Run() { @@ -437,108 +687,99 @@ class InnerClassTest extends CompilableTestSupport { } def foo = new Foo() assert foo.foo() == 1 - """ + ''' } - // GROOVY-9501 - void testUsageOfOuterField7() { + @Test + void testUsageOfOuterMethod3() { assertScript ''' - class Main extends Outer { - static main(args) { - newInstance().newThread() - assert Outer.Inner.error == null - } + interface Run { + def run() } + class Foo { + private static x() { return 1 } - abstract class Outer { - private static volatile boolean flag - - void newThread() { - Thread thread = new Inner() - thread.start() - thread.join() - } - - private final class Inner extends Thread { - @Override - void run() { - try { - if (!flag) { - // do work - } - } catch (e) { - error = e - } - } - public static error + def foo(def runner = new Run() { + def run() { return x() } + }) { + runner.run() } } + def foo = new Foo() + assert foo.foo() == 1 ''' } - // inner class is static instead of final - void testUsageOfOuterField8() { + @Test // GROOVY-9189 + void testUsageOfOuterMethod4() { assertScript ''' - class Main extends Outer { - static main(args) { - newInstance().newThread() - assert Outer.Inner.error == null - } + interface Run { + def run() } + class Foo { + private static x() { return 1 } - abstract class Outer { - private static volatile boolean flag - - void newThread() { - Thread thread = new Inner() - thread.start() - thread.join() + static def foo(def runner = new Run() { + def run() { return x() } + }) { + runner.run() } + } + def foo = new Foo() + assert foo.foo() == 1 + ''' + } - private static class Inner extends Thread { - @Override - void run() { - try { - if (!flag) { - // do work - } - } catch (e) { - error = e - } - } - public static error + @Test // GROOVY-9168 + void testUsageOfOuterMethod5() { + assertScript ''' + class A { + // AIC in this position can use static methods: + A(Runnable action = new Runnable() { void run() { setAnswer(42) }}) { + this.action = action } + Runnable action + static int answer } + + def a = new A() + a.action.run(); + assert a.answer == 42 ''' } + @Test void testUsageOfOuterMethodOverridden() { - assertScript """ + assertScript ''' interface Run { def run() } class Foo { - private x(){1} + private x() { return 1 } + def foo() { - def runner = new Run(){ + def runner = new Run() { def run() { return x() } } runner.run() } } - class Bar extends Foo{ - def x() { 2 } + class Bar extends Foo { + def x() { return 2 } } def bar = new Bar() assert bar.foo() == 1 - """ + ''' + } - assertScript """ + @Test + void testUsageOfOuterMethodOverridden2() { + assertScript ''' interface Run { def run() } class Foo { - private static x() { 1 } + private static x() { return 1 } static foo() { def runner = new Run() { @@ -548,31 +789,33 @@ class InnerClassTest extends CompilableTestSupport { } } class Bar extends Foo { - static x() { 2 } + static x() { return 2 } } def bar = new Bar() assert bar.foo() == 1 - """ + ''' } + @Test void testClassOutputOrdering() { // this does actually not do much, but before this // change the inner class was tried to be executed - // because a class ordering bug. The main method - // makes the Foo class executeable, but Foo$Bar is + // because a class ordering bug. The main method + // makes the Foo class executeable, but Foo$Bar is // not. So if Foo$Bar is returned, asserScript will // fail. If Foo is returned, asserScript will not // fail. - assertScript """ + assertScript ''' class Foo { static class Bar{} static main(args){} } - """ + ''' } + @Test void testInnerClassDotThisUsage() { - assertScript """ + assertScript ''' class A{ int x = 0; class B{ @@ -591,9 +834,12 @@ class InnerClassTest extends CompilableTestSupport { c.foo() assert a.x == 1 assert b.y == 4 - """ + ''' + } - assertScript """ + @Test + void testInnerClassDotThisUsage2() { + assertScript ''' interface X { def m() } @@ -611,41 +857,60 @@ class InnerClassTest extends CompilableTestSupport { class B extends A {} def b = new B() assert b.foo() instanceof B - """ + ''' } + @Test // GROOVY-4028 void testImplicitThisPassingWithNamedArguments() { - def oc = new MyOuterClass4028() - assert oc.foo().propMap.size() == 2 + assertScript ''' + class Outer { + def inner() { + new Inner(fName: 'Roshan', lName: 'Dawrani') + } + class Inner { + Map props + Inner(Map props) { + this.props = props + } + } + } + def outer = new Outer() + def inner = outer.inner() + assert inner.props.size() == 2 + ''' } + @Test void testThis0() { - assertScript """ -class A { - static def field = 10 - void main (a) { - new C ().r () - } - - class C { - def r () { - 4.times { - new B(it).u (it) - } - } - } - - class B { - def s - B (s) { this.s = s} - def u (i) { println i + s + field } - }}""" + assertScript ''' + class A { + static def field = 10 + void main (a) { + new C ().r () + } + + class C { + def r () { + 4.times { + new B(it).u (it) + } + } + } + + class B { + def s + B (s) { this.s = s} + def u (i) { println i + s + field } + } + } + ''' } + @Test void testReferencedVariableInAIC() { - assertScript """ - interface X{} - + assertScript ''' + interface X {} + final double delta = 0.1 (0 ..< 1).collect { n -> new X () { @@ -654,10 +919,14 @@ class A { } } } - """ - assertScript """ - interface X{} - + ''' + } + + @Test + void testReferencedVariableInAIC2() { + assertScript ''' + interface X {} + final double delta1 = 0.1 final double delta2 = 0.1 (0 ..< 1).collect { n -> @@ -667,10 +936,10 @@ class A { } } } - """ + ''' } - // GROOVY-5989 + @Test // GROOVY-5989 void testReferenceToOuterClassNestedInterface() { assertScript ''' interface Koo { class Inner { } } @@ -683,56 +952,103 @@ class A { ''' } - // GROOVY-5679 - // GROOVY-5681 + @Test // GROOVY-5679, GROOVY-5681 void testEnclosingMethodIsSet() { - new GroovyShell().evaluate '''import groovy.transform.ASTTest - import static org.codehaus.groovy.control.CompilePhase.* - import org.codehaus.groovy.ast.InnerClassNode - import org.codehaus.groovy.ast.expr.ConstructorCallExpression -import org.codehaus.groovy.classgen.Verifier - - class A { - int x - - /*@ASTTest(phase=SEMANTIC_ANALYSIS, value={ - def cce = lookup('inner')[0].expression - def icn = cce.type - assert icn instanceof InnerClassNode - assert icn.enclosingMethod == node - }) - A() { inner: new Runnable() { void run() {} } } + assertScript ''' + import groovy.transform.ASTTest + import org.codehaus.groovy.ast.expr.* + import static org.codehaus.groovy.classgen.Verifier.* + import static org.codehaus.groovy.control.CompilePhase.* - @ASTTest(phase=SEMANTIC_ANALYSIS, value={ - def cce = lookup('inner')[0].expression - def icn = cce.type - assert icn instanceof InnerClassNode - assert icn.enclosingMethod == node - }) - void foo() { inner: new Runnable() { void run() {} } }*/ + class A { + @ASTTest(phase=CLASS_GENERATION, value={ + def initialExpression = node.parameters[0].getNodeMetaData(INITIAL_EXPRESSION) + assert initialExpression instanceof ConstructorCallExpression + def icn = initialExpression.type + assert icn instanceof org.codehaus.groovy.ast.InnerClassNode + assert icn.enclosingMethod != null + assert icn.enclosingMethod.name == 'bar' + assert icn.enclosingMethod.parameters.length == 0 // ensure the enclosing method is bar(), not bar(Object) + }) + void bar(action = new Runnable() { void run() { x = 123 }}) { + action.run() + } + int x + } + def a = new A() + a.bar() + assert a.x == 123 + ''' + } + + @Test @NotYetImplemented // GROOVY-9151 + void testEnclosingMethodIsSet2() { + assertScript ''' + import groovy.transform.ASTTest + import org.codehaus.groovy.ast.expr.* + import static org.codehaus.groovy.classgen.Verifier.* + import static org.codehaus.groovy.control.CompilePhase.* @ASTTest(phase=CLASS_GENERATION, value={ - def initialExpression = node.parameters[0].getNodeMetaData(Verifier.INITIAL_EXPRESSION) - assert initialExpression instanceof ConstructorCallExpression - def icn = initialExpression.type - assert icn instanceof InnerClassNode - assert icn.enclosingMethod != null - assert icn.enclosingMethod.name == 'bar' - assert icn.enclosingMethod.parameters.length == 0 // ensure the enclosing method is bar(), not bar(Object) + def init = node.parameters[0].getNodeMetaData(INITIAL_EXPRESSION) + assert init instanceof MapExpression + assert init.mapEntryExpressions[0].valueExpression instanceof ConstructorCallExpression + def type = init.mapEntryExpressions[0].valueExpression.type + + assert type.enclosingMethod != null + assert type.enclosingMethod.name == 'bar' + assert type.enclosingMethod.parameters.length == 0 // ensure the enclosing method is bar(), not bar(Map) }) - void bar(action=new Runnable() { void run() { x = 123 }}) { - action.run() + void bar(Map args = [action: new Runnable() { void run() { result = 123 }}]) { + args.action.run() } - } - def a = new A() - a.bar() - assert a.x == 123 + bar() ''' } + @Test // GROOVY-5681, GROOVY-9151 + void testEnclosingMethodIsSet3() { + assertScript ''' + import groovy.transform.ASTTest + import org.codehaus.groovy.ast.expr.* + import org.codehaus.groovy.ast.stmt.* + import static org.codehaus.groovy.classgen.Verifier.* + import static org.codehaus.groovy.control.CompilePhase.* + + @ASTTest(phase=CLASS_GENERATION, value={ + def init = node.parameters[0].getNodeMetaData(INITIAL_EXPRESSION) + assert init instanceof ConstructorCallExpression + assert init.type.enclosingMethod != null + assert init.type.enclosingMethod.name == 'bar' + assert init.type.enclosingMethod.parameters.length == 0 // ensure the enclosing method is bar(), not bar(Runnable) + + assert init.type.getMethods('run')[0].code instanceof BlockStatement + assert init.type.getMethods('run')[0].code.statements[0] instanceof ExpressionStatement + assert init.type.getMethods('run')[0].code.statements[0].expression instanceof DeclarationExpression + + init = init.type.getMethods('run')[0].code.statements[0].expression.rightExpression + assert init instanceof ConstructorCallExpression + assert init.isUsingAnonymousInnerClass() + assert init.type.enclosingMethod != null + assert init.type.enclosingMethod.name == 'run' + assert init.type.enclosingMethod.parameters.length == 0 + }) + void bar(Runnable runner = new Runnable() { + @Override void run() { + def comparator = new Comparator<Integer>() { + int compare(Integer one, Integer two) { + } + } + } + }) { + args.action.run() + } + ''' + } + + @Test // GROOVY-6810 void testThisReferenceForAICInOpenBlock() { - // GROOVY-6810 assertScript ''' import java.security.AccessController import java.security.PrivilegedAction @@ -763,8 +1079,10 @@ import org.codehaus.groovy.classgen.Verifier def t = new Test() injectVariables(t, ['p': 'q']) ''' + } - //GROOVY-4896 + @Test // GROOVY-4896 + void testThisReferenceForAICInOpenBlock2() { assertScript ''' def doSomethingUsingLocal(){ logExceptions { @@ -828,8 +1146,8 @@ import org.codehaus.groovy.classgen.Verifier ''' } - void testAICextendingAbstractInnerClass() { - //GROOVY-5582 + @Test // GROOVY-5582 + void testAICExtendingAbstractInnerClass() { assertScript ''' class Outer { int outer() { 1 } @@ -847,8 +1165,8 @@ import org.codehaus.groovy.classgen.Verifier ''' } + @Test // GROOVY-6831 void testNestedPropertyHandling() { - // GROOVY-6831 assertScript ''' class Outer { private static List items = [] @@ -870,8 +1188,8 @@ import org.codehaus.groovy.classgen.Verifier ''' } + @Test // GROOVY-7312 void testInnerClassOfInterfaceIsStatic() { - //GROOVY-7312 assertScript ''' import java.lang.reflect.Modifier interface Baz { @@ -882,8 +1200,8 @@ import org.codehaus.groovy.classgen.Verifier ''' } - void testInnerClassOfInterfaceIsStaticVariant() { - //GROOVY-7312 + @Test // GROOVY-7312 + void testInnerClassOfInterfaceIsStatic2() { assertScript ''' import java.lang.reflect.Modifier import groovy.transform.ASTTest @@ -900,7 +1218,7 @@ import org.codehaus.groovy.classgen.Verifier ''' } - //GROOVY-8914 + @Test // GROOVY-8914 void testNestedClassInheritingFromNestedClass() { // control assert new Outer8914.Nested() @@ -912,8 +1230,134 @@ import org.codehaus.groovy.classgen.Verifier assert new OuterReferencingPrecompiled.Nested() ''' } + + @Test // GROOVY-6809 + void testReferenceToUninitializedThis() { + def err = shouldFail ''' + class Test { + static main(args) { + def a = new A() + } + + static class A { + A() { + def b = new B() + } + + class B extends A { + B() { + super(A.this) + } + } + } + } + ''' + + assert err =~ / Could not find matching constructor for: Test.A\(Test.A\)/ + } + + @Test // GROOVY-6809 + void testReferenceToUninitializedThis2() { + assertScript ''' + class A { + A() { + this(new Runnable() { + @Override + void run() { + } + }) + } + + private A(Runnable action) { + } + } + + new A() + ''' + } + + @Test // GROOVY-6809 + void testReferenceToUninitializedThis3() { + assertScript ''' + class A { + A(x) { + } + } + class B extends A { + B() { + super(new Object() {}) + } + } + + new B() + ''' + } + + @Test @NotYetImplemented // GROOVY-9168 + void testReferenceToUninitializedThis4() { + def err = shouldFail ''' + class Outer { + class Inner { + } + Outer(Inner inner) { + } + Outer() { + this(new Inner()) + } + } + new Outer() + ''' + + assert err =~ / Cannot reference 'this' before supertype constructor has been called. / + } + + @Test @NotYetImplemented // GROOVY-9168 + void testReferenceToUninitializedThis5() { + def err = shouldFail ''' + class Outer { + class Inner { + } + Outer(Inner inner = new Inner()) { + } + } + new Outer() + ''' + + assert err =~ / Cannot reference 'this' before supertype constructor has been called. / + } + + @Test // GROOVY-9168 + void testReferenceToUninitializedThis6() { + assertScript ''' + import groovy.transform.ASTTest + import java.util.concurrent.Callable + import org.codehaus.groovy.ast.expr.* + import static org.codehaus.groovy.classgen.Verifier.* + import static org.codehaus.groovy.control.CompilePhase.* + + class A { + @ASTTest(phase=CLASS_GENERATION, value={ + def init = node.parameters[0].getNodeMetaData(INITIAL_EXPRESSION) + assert init instanceof ConstructorCallExpression + assert init.isUsingAnonymousInnerClass() + assert init.type.enclosingMethod != null + assert init.type.enclosingMethod.name == '<init>' + assert init.type.enclosingMethod.parameters.length == 0 // ensure the enclosing method is A(), not A(Runnable) + }) + A(Callable action = new Callable() { def call() { return 42 }}) { + this.action = action + } + Callable action + } + + def a = new A() + assert a.action.call() == 42 + ''' + } } +//------------------------------------------------------------------------------ + class Parent8914 { static class Nested {} } @@ -921,15 +1365,3 @@ class Parent8914 { class Outer8914 { static class Nested extends Parent8914.Nested {} } - -class MyOuterClass4028 { - def foo() { - new MyInnerClass4028(fName: 'Roshan', lName: 'Dawrani') - } - class MyInnerClass4028 { - Map propMap - def MyInnerClass4028(Map propMap) { - this.propMap = propMap - } - } -}