GROOVY-8466: Support native lambda in static compilation mode(closes #654)
Project: http://git-wip-us.apache.org/repos/asf/groovy/repo Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/302be69d Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/302be69d Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/302be69d Branch: refs/heads/master Commit: 302be69d1733ba556042938c8f175d9000f59d76 Parents: 7f6c92a Author: danielsun1106 <realblue...@hotmail.com> Authored: Sun Feb 18 02:32:14 2018 +0800 Committer: danielsun1106 <realblue...@hotmail.com> Committed: Sun Feb 18 02:32:14 2018 +0800 ---------------------------------------------------------------------- .../org/codehaus/groovy/ast/ClassHelper.java | 17 +- .../codehaus/groovy/ast/GroovyCodeVisitor.java | 5 + .../java/org/codehaus/groovy/ast/Parameter.java | 4 + .../groovy/ast/expr/LambdaExpression.java | 5 + .../groovy/classgen/AsmClassGenerator.java | 7 + .../groovy/classgen/GeneratorContext.java | 14 +- .../groovy/classgen/asm/ClosureWriter.java | 120 ++-- .../classgen/asm/DelegatingController.java | 7 +- .../groovy/classgen/asm/InvocationWriter.java | 41 ++ .../groovy/classgen/asm/LambdaWriter.java | 36 + .../groovy/classgen/asm/WriterController.java | 6 + .../classgen/asm/sc/StaticInvocationWriter.java | 4 + ...ypesBinaryExpressionMultiTypeDispatcher.java | 14 +- .../asm/sc/StaticTypesLambdaWriter.java | 443 ++++++++++++ .../asm/sc/StaticTypesWriterController.java | 11 + .../groovy/runtime/GeneratedLambda.java | 28 + .../groovy/runtime/ProxyGeneratorAdapter.java | 2 +- .../stc/StaticTypeCheckingVisitor.java | 45 +- .../groovy/transform/stc/StaticTypesMarker.java | 4 +- src/test/gls/generics/GenericsTestBase.java | 2 +- src/test/groovy/transform/stc/LambdaTest.groovy | 717 +++++++++++++++++++ .../parser/antlr4/GroovyParserTest.groovy | 1 + 22 files changed, 1470 insertions(+), 63 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/ast/ClassHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java index 3626242..d8ce0d7 100644 --- a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java +++ b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java @@ -30,6 +30,7 @@ import groovy.lang.Reference; import groovy.lang.Script; import org.codehaus.groovy.classgen.asm.util.TypeDescriptionUtil; import org.codehaus.groovy.runtime.GeneratedClosure; +import org.codehaus.groovy.runtime.GeneratedLambda; import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport; import org.codehaus.groovy.transform.trait.Traits; import org.codehaus.groovy.util.ManagedConcurrentMap; @@ -64,7 +65,7 @@ public class ClassHelper { Character.class, Byte.class, Short.class, Integer.class, Long.class, Double.class, Float.class, BigDecimal.class, BigInteger.class, Number.class, Void.class, Reference.class, Class.class, MetaClass.class, - Iterator.class, GeneratedClosure.class, GroovyObjectSupport.class + Iterator.class, GeneratedClosure.class, GeneratedLambda.class, GroovyObjectSupport.class }; private static final String[] primitiveClassNames = new String[]{ @@ -74,7 +75,8 @@ public class ClassHelper { public static final ClassNode DYNAMIC_TYPE = makeCached(Object.class), OBJECT_TYPE = DYNAMIC_TYPE, - VOID_TYPE = makeCached(Void.TYPE), CLOSURE_TYPE = makeCached(Closure.class), + VOID_TYPE = makeCached(Void.TYPE), + CLOSURE_TYPE = makeCached(Closure.class), GSTRING_TYPE = makeCached(GString.class), LIST_TYPE = makeWithoutCaching(List.class), MAP_TYPE = makeWithoutCaching(Map.class), RANGE_TYPE = makeCached(Range.class), PATTERN_TYPE = makeCached(Pattern.class), STRING_TYPE = makeCached(String.class), @@ -100,9 +102,12 @@ public class ClassHelper { Annotation_TYPE = makeCached(Annotation.class), ELEMENT_TYPE_TYPE = makeCached(ElementType.class), + FunctionalInterface_Type = ClassHelper.makeCached(FunctionalInterface.class), + // uncached constants. CLASS_Type = makeWithoutCaching(Class.class), COMPARABLE_TYPE = makeWithoutCaching(Comparable.class), GENERATED_CLOSURE_Type = makeWithoutCaching(GeneratedClosure.class), + GENERATED_LAMBDA_TYPE = makeWithoutCaching(GeneratedLambda.class), GROOVY_OBJECT_SUPPORT_TYPE = makeWithoutCaching(GroovyObjectSupport.class), GROOVY_OBJECT_TYPE = makeWithoutCaching(GroovyObject.class), GROOVY_INTERCEPTABLE_TYPE = makeWithoutCaching(GroovyInterceptable.class); @@ -118,7 +123,7 @@ public class ClassHelper { Double_TYPE, Float_TYPE, BigDecimal_TYPE, BigInteger_TYPE, Number_TYPE, void_WRAPPER_TYPE, REFERENCE_TYPE, CLASS_Type, METACLASS_TYPE, - Iterator_TYPE, GENERATED_CLOSURE_Type, GROOVY_OBJECT_SUPPORT_TYPE, + Iterator_TYPE, GENERATED_CLOSURE_Type, GENERATED_LAMBDA_TYPE, GROOVY_OBJECT_SUPPORT_TYPE, GROOVY_OBJECT_TYPE, GROOVY_INTERCEPTABLE_TYPE, Enum_Type, Annotation_TYPE }; @@ -383,6 +388,12 @@ public class ClassHelper { return findSAM(type) != null; } + public static boolean isFunctionalInterface(ClassNode type) { + // Functional interface must be an interface at first, or the following exception will occur: + // java.lang.invoke.LambdaConversionException: Functional interface SamCallable is not an interface + return type.isInterface() && isSAMType(type); + } + /** * Returns the single abstract method of a class node, if it is a SAM type, or null otherwise. * http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/ast/GroovyCodeVisitor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/GroovyCodeVisitor.java b/src/main/java/org/codehaus/groovy/ast/GroovyCodeVisitor.java index 12787c0..db793cc 100644 --- a/src/main/java/org/codehaus/groovy/ast/GroovyCodeVisitor.java +++ b/src/main/java/org/codehaus/groovy/ast/GroovyCodeVisitor.java @@ -34,6 +34,7 @@ import org.codehaus.groovy.ast.expr.DeclarationExpression; import org.codehaus.groovy.ast.expr.ElvisOperatorExpression; import org.codehaus.groovy.ast.expr.FieldExpression; import org.codehaus.groovy.ast.expr.GStringExpression; +import org.codehaus.groovy.ast.expr.LambdaExpression; import org.codehaus.groovy.ast.expr.ListExpression; import org.codehaus.groovy.ast.expr.MapEntryExpression; import org.codehaus.groovy.ast.expr.MapExpression; @@ -138,6 +139,10 @@ public interface GroovyCodeVisitor { void visitClosureExpression(ClosureExpression expression); + default void visitLambdaExpression(LambdaExpression expression) { + visitClosureExpression(expression); + } + void visitTupleExpression(TupleExpression expression); void visitMapExpression(MapExpression expression); http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/ast/Parameter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/Parameter.java b/src/main/java/org/codehaus/groovy/ast/Parameter.java index 1b22128..b2ddb09 100644 --- a/src/main/java/org/codehaus/groovy/ast/Parameter.java +++ b/src/main/java/org/codehaus/groovy/ast/Parameter.java @@ -123,4 +123,8 @@ public class Parameter extends AnnotatedNode implements Variable { public void setModifiers(int modifiers) { this.modifiers = modifiers; } + + public Expression getDefaultValue() { + return defaultValue; + } } http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/ast/expr/LambdaExpression.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/expr/LambdaExpression.java b/src/main/java/org/codehaus/groovy/ast/expr/LambdaExpression.java index f4f1001..ab30513 100644 --- a/src/main/java/org/codehaus/groovy/ast/expr/LambdaExpression.java +++ b/src/main/java/org/codehaus/groovy/ast/expr/LambdaExpression.java @@ -20,6 +20,7 @@ package org.codehaus.groovy.ast.expr; import org.codehaus.groovy.ast.AstToTextHelper; +import org.codehaus.groovy.ast.GroovyCodeVisitor; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.stmt.Statement; @@ -35,6 +36,10 @@ public class LambdaExpression extends ClosureExpression { super(parameters, code); } + public void visit(GroovyCodeVisitor visitor) { + visitor.visitLambdaExpression(this); + } + @Override public String getText() { String paramText = AstToTextHelper.getParametersText(this.getParameters()); http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java index 8123e83..bfb70f7 100644 --- a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java +++ b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java @@ -55,6 +55,7 @@ import org.codehaus.groovy.ast.expr.EmptyExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.FieldExpression; import org.codehaus.groovy.ast.expr.GStringExpression; +import org.codehaus.groovy.ast.expr.LambdaExpression; import org.codehaus.groovy.ast.expr.ListExpression; import org.codehaus.groovy.ast.expr.MapEntryExpression; import org.codehaus.groovy.ast.expr.MapExpression; @@ -717,6 +718,11 @@ public class AsmClassGenerator extends ClassGenerator { controller.getClosureWriter().writeClosure(expression); } + @Override + public void visitLambdaExpression(LambdaExpression expression) { + controller.getLambdaWriter().writeLambda(expression); + } + /** * Loads either this object or if we're inside a closure then load the top level owner */ @@ -2228,4 +2234,5 @@ public class AsmClassGenerator extends ClassGenerator { mn.getUnit().addGeneratedInnerClass((InnerClassNode)innerClass); return innerClasses.add(innerClass); } + } http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/classgen/GeneratorContext.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/GeneratorContext.java b/src/main/java/org/codehaus/groovy/classgen/GeneratorContext.java index 8b8a510..dd318bd 100644 --- a/src/main/java/org/codehaus/groovy/classgen/GeneratorContext.java +++ b/src/main/java/org/codehaus/groovy/classgen/GeneratorContext.java @@ -34,6 +34,7 @@ public class GeneratorContext { private int innerClassIdx = 1; private int closureClassIdx = 1; + private int lambdaClassIdx = 1; private final CompileUnit compileUnit; public GeneratorContext(CompileUnit compileUnit) { @@ -54,6 +55,14 @@ public class GeneratorContext { } public String getNextClosureInnerName(ClassNode owner, ClassNode enclosingClass, MethodNode enclosingMethod) { + return getNextInnerName(owner, enclosingClass, enclosingMethod, "closure"); + } + + public String getNextLambdaInnerName(ClassNode owner, ClassNode enclosingClass, MethodNode enclosingMethod) { + return getNextInnerName(owner, enclosingClass, enclosingMethod, "lambda"); + } + + private String getNextInnerName(ClassNode owner, ClassNode enclosingClass, MethodNode enclosingMethod, String classifier) { String methodName = ""; if (enclosingMethod != null) { methodName = enclosingMethod.getName(); @@ -61,10 +70,11 @@ public class GeneratorContext { if (enclosingClass.isDerivedFrom(ClassHelper.CLOSURE_TYPE)) { methodName = ""; } else { - methodName = "_"+encodeAsValidClassName(methodName); + methodName = "_" + encodeAsValidClassName(methodName); } } - return methodName + "_closure" + closureClassIdx++; + + return methodName + "_" + classifier + closureClassIdx++; } http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/classgen/asm/ClosureWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/ClosureWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/ClosureWriter.java index af305ba..7c44599 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/ClosureWriter.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/ClosureWriter.java @@ -19,7 +19,6 @@ package org.codehaus.groovy.classgen.asm; import org.codehaus.groovy.GroovyBugError; -import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.CodeVisitorSupport; @@ -65,6 +64,9 @@ import static org.objectweb.asm.Opcodes.NEW; public class ClosureWriter { + public static final String OUTER_INSTANCE = "_outerInstance"; + public static final String THIS_OBJECT = "_thisObject"; + protected interface UseExistingReference {} private final Map<Expression,ClassNode> closureClassMap; @@ -184,9 +186,8 @@ public class ClosureWriter { protected ClassNode createClosureClass(ClosureExpression expression, int mods) { ClassNode classNode = controller.getClassNode(); ClassNode outerClass = controller.getOutermostClass(); - MethodNode methodNode = controller.getMethodNode(); - String name = classNode.getName() + "$" - + controller.getContext().getNextClosureInnerName(outerClass, classNode, methodNode); // add a more informative name +// MethodNode methodNode = controller.getMethodNode(); + String name = genClosureClassName(); boolean staticMethodOrInStaticClass = controller.isStaticMethod() || classNode.isStaticClass(); Parameter[] parameters = expression.getParameters(); @@ -249,23 +250,31 @@ public class ClosureWriter { } // let's make the constructor - BlockStatement block = new BlockStatement(); - // this block does not get a source position, because we don't - // want this synthetic constructor to show up in corbertura reports - VariableExpression outer = new VariableExpression("_outerInstance"); - outer.setSourcePosition(expression); - block.getVariableScope().putReferencedLocalVariable(outer); - VariableExpression thisObject = new VariableExpression("_thisObject"); - thisObject.setSourcePosition(expression); - block.getVariableScope().putReferencedLocalVariable(thisObject); - TupleExpression conArgs = new TupleExpression(outer, thisObject); - block.addStatement( - new ExpressionStatement( - new ConstructorCallExpression( - ClassNode.SUPER, - conArgs))); + BlockStatement block = createBlockStatementForConstructor(expression); // let's assign all the parameter fields from the outer context + addFieldsAndGettersForLocalVariables(answer, localVariableParams); + + addConstructor(expression, localVariableParams, answer, block); + + correctAccessedVariable(answer,expression); + + return answer; + } + + protected ConstructorNode addConstructor(ClosureExpression expression, Parameter[] localVariableParams, InnerClassNode answer, BlockStatement block) { + Parameter[] params = new Parameter[2 + localVariableParams.length]; + params[0] = new Parameter(ClassHelper.OBJECT_TYPE, OUTER_INSTANCE); + params[1] = new Parameter(ClassHelper.OBJECT_TYPE, THIS_OBJECT); + System.arraycopy(localVariableParams, 0, params, 2, localVariableParams.length); + + ConstructorNode constructorNode = answer.addConstructor(ACC_PUBLIC, params, ClassNode.EMPTY_ARRAY, block); + constructorNode.setSourcePosition(expression); + + return constructorNode; + } + + protected void addFieldsAndGettersForLocalVariables(InnerClassNode answer, Parameter[] localVariableParams) { for (Parameter param : localVariableParams) { String paramName = param.getName(); ClassNode type = param.getType(); @@ -292,35 +301,58 @@ public class ClosureWriter { new ReturnStatement(fieldExp)); } } + } - Parameter[] params = new Parameter[2 + localVariableParams.length]; - params[0] = new Parameter(ClassHelper.OBJECT_TYPE, "_outerInstance"); - params[1] = new Parameter(ClassHelper.OBJECT_TYPE, "_thisObject"); - System.arraycopy(localVariableParams, 0, params, 2, localVariableParams.length); + protected BlockStatement createBlockStatementForConstructor(ClosureExpression expression) { + BlockStatement block = new BlockStatement(); + // this block does not get a source position, because we don't + // want this synthetic constructor to show up in corbertura reports + VariableExpression outer = new VariableExpression(OUTER_INSTANCE); + outer.setSourcePosition(expression); + block.getVariableScope().putReferencedLocalVariable(outer); + VariableExpression thisObject = new VariableExpression(THIS_OBJECT); + thisObject.setSourcePosition(expression); + block.getVariableScope().putReferencedLocalVariable(thisObject); + TupleExpression conArgs = new TupleExpression(outer, thisObject); + block.addStatement( + new ExpressionStatement( + new ConstructorCallExpression( + ClassNode.SUPER, + conArgs))); + return block; + } - ASTNode sn = answer.addConstructor(ACC_PUBLIC, params, ClassNode.EMPTY_ARRAY, block); - sn.setSourcePosition(expression); - - correctAccessedVariable(answer,expression); - - return answer; + private String genClosureClassName() { + ClassNode classNode = controller.getClassNode(); + ClassNode outerClass = controller.getOutermostClass(); + MethodNode methodNode = controller.getMethodNode(); + + return classNode.getName() + "$" + + controller.getContext().getNextClosureInnerName(outerClass, classNode, methodNode); + } + + protected static class CorrectAccessedVariableVisitor extends CodeVisitorSupport { + private InnerClassNode icn; + + public CorrectAccessedVariableVisitor(InnerClassNode icn) { + this.icn = icn; + } + + @Override + public void visitVariableExpression(VariableExpression expression) { + Variable v = expression.getAccessedVariable(); + if (v == null) return; + if (!(v instanceof FieldNode)) return; + String name = expression.getName(); + FieldNode fn = icn.getDeclaredField(name); + if (fn != null) { // only overwrite if we find something more specific + expression.setAccessedVariable(fn); + } + } } private static void correctAccessedVariable(final InnerClassNode closureClass, ClosureExpression ce) { - CodeVisitorSupport visitor = new CodeVisitorSupport() { - @Override - public void visitVariableExpression(VariableExpression expression) { - Variable v = expression.getAccessedVariable(); - if (v==null) return; - if (!(v instanceof FieldNode)) return; - String name = expression.getName(); - FieldNode fn = closureClass.getDeclaredField(name); - if (fn != null) { // only overwrite if we find something more specific - expression.setAccessedVariable(fn); - } - } - }; - visitor.visitClosureExpression(ce); + new CorrectAccessedVariableVisitor(closureClass).visitClosureExpression(ce); } /* @@ -330,7 +362,7 @@ public class ClosureWriter { * same method, in this case the constructor. A closure should not * have more than one constructor! */ - private static void removeInitialValues(Parameter[] params) { + protected static void removeInitialValues(Parameter[] params) { for (int i = 0; i < params.length; i++) { if (params[i].hasInitialExpression()) { Parameter p = new Parameter(params[i].getType(), params[i].getName()); http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/classgen/asm/DelegatingController.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/DelegatingController.java b/src/main/java/org/codehaus/groovy/classgen/asm/DelegatingController.java index 22acbaa..3553a82 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/DelegatingController.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/DelegatingController.java @@ -112,7 +112,12 @@ public class DelegatingController extends WriterController { @Override public ClosureWriter getClosureWriter() { return delegationController.getClosureWriter(); - } + } + + @Override + public LambdaWriter getLambdaWriter() { + return delegationController.getLambdaWriter(); + } @Override public CompileStack getCompileStack() { http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/classgen/asm/InvocationWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/InvocationWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/InvocationWriter.java index 10a82ed..6ab3fd9 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/InvocationWriter.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/InvocationWriter.java @@ -479,11 +479,37 @@ public class InvocationWriter { return methodName; } + private boolean isFunctionInterfaceCall(MethodCallExpression call) { + if (!"call".equals(call.getMethodAsString())) { + return false; + } + + Expression objectExpression = call.getObjectExpression(); + + if (null == objectExpression) { + return false; + } + + if (AsmClassGenerator.isThisExpression(objectExpression)) { + return false; + } + + if (ClassHelper.isFunctionalInterface(objectExpression.getType())) { + return true; + } + + return false; + } + public void writeInvokeMethod(MethodCallExpression call) { if (isClosureCall(call)) { // let's invoke the closure method invokeClosure(call.getArguments(), call.getMethodAsString()); } else { + if (isFunctionInterfaceCall(call)) { + call = transformToRealMethodCall(call); + } + boolean isSuperMethodCall = usesSuper(call); MethodCallerMultiAdapter adapter = invokeMethod; if (isSuperMethodCall && call.isSafe()) { @@ -498,6 +524,21 @@ public class InvocationWriter { } } + private MethodCallExpression transformToRealMethodCall(MethodCallExpression call) { + ClassNode type = call.getObjectExpression().getType(); + final MethodNode methodNode = ClassHelper.findSAM(type); + + call = (MethodCallExpression) call.transformExpression(expression -> { + if (!(expression instanceof ConstantExpression)) { + return expression; + } + + return new ConstantExpression(methodNode.getName()); + }); + call.setMethodTarget(methodNode); + return call; + } + private boolean isClosureCall(MethodCallExpression call) { // are we a local variable? // it should not be an explicitly "this" qualified method call http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/classgen/asm/LambdaWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/LambdaWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/LambdaWriter.java new file mode 100644 index 0000000..6c0b53d --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/LambdaWriter.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.codehaus.groovy.classgen.asm; + +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.expr.LambdaExpression; + +public class LambdaWriter extends ClosureWriter { + public LambdaWriter(WriterController wc) { + super(wc); + } + + public void writeLambda(LambdaExpression expression) { + super.writeClosure(expression); + } + + protected Parameter[] getLambdaSharedVariables(LambdaExpression expression) { + return super.getClosureSharedVariables(expression); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/classgen/asm/WriterController.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/WriterController.java b/src/main/java/org/codehaus/groovy/classgen/asm/WriterController.java index 3c9d843..fe7a21e 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/WriterController.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/WriterController.java @@ -55,6 +55,7 @@ public class WriterController { private CallSiteWriter callSiteWriter; private ClassVisitor cv; private ClosureWriter closureWriter; + private LambdaWriter lambdaWriter; private String internalClassName; private InvocationWriter invocationWriter; private BinaryExpressionHelper binaryExpHelper, fastPathBinaryExpHelper; @@ -120,6 +121,7 @@ public class WriterController { this.operandStack = new OperandStack(this); this.assertionWriter = new AssertionWriter(this); this.closureWriter = new ClosureWriter(this); + this.lambdaWriter = new LambdaWriter(this); this.internalBaseClassName = BytecodeHelper.getClassInternalName(classNode.getSuperClass()); this.acg = asmClassGenerator; this.sourceUnit = acg.getSourceUnit(); @@ -197,6 +199,10 @@ public class WriterController { return closureWriter; } + public LambdaWriter getLambdaWriter() { + return lambdaWriter; + } + public ClassVisitor getCv() { return cv; } http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticInvocationWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticInvocationWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticInvocationWriter.java index 1ca5ed5..9db7599 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticInvocationWriter.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticInvocationWriter.java @@ -75,6 +75,7 @@ import static org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE; import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE; import static org.codehaus.groovy.ast.ClassHelper.getWrapper; import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.PRIVATE_BRIDGE_METHODS; +import static org.codehaus.groovy.transform.stc.StaticTypesMarker.PARAMETER_TYPE; import static org.objectweb.asm.Opcodes.ACONST_NULL; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.CHECKCAST; @@ -438,6 +439,7 @@ public class StaticInvocationWriter extends InvocationWriter { // first parameters as usual for (int i = 0; i < para.length - 1; i++) { Expression expression = argumentList.get(i); + expression.putNodeMetaData(PARAMETER_TYPE, para[i].getType()); expression.visit(acg); if (!isNullConstant(expression)) { operandStack.doGroovyCast(para[i].getType()); @@ -463,6 +465,7 @@ public class StaticInvocationWriter extends InvocationWriter { } else if (argumentListSize == para.length) { for (int i = 0; i < argumentListSize; i++) { Expression expression = argumentList.get(i); + expression.putNodeMetaData(PARAMETER_TYPE, para[i].getType()); expression.visit(acg); if (!isNullConstant(expression)) { operandStack.doGroovyCast(para[i].getType()); @@ -494,6 +497,7 @@ public class StaticInvocationWriter extends InvocationWriter { } for (int i = 0; i < arguments.length; i++) { Expression expression = arguments[i]; + expression.putNodeMetaData(PARAMETER_TYPE, para[i].getType()); expression.visit(acg); if (!isNullConstant(expression)) { operandStack.doGroovyCast(para[i].getType()); http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesBinaryExpressionMultiTypeDispatcher.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesBinaryExpressionMultiTypeDispatcher.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesBinaryExpressionMultiTypeDispatcher.java index 9088d8b..c327ba1 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesBinaryExpressionMultiTypeDispatcher.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesBinaryExpressionMultiTypeDispatcher.java @@ -33,6 +33,7 @@ import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.ConstructorCallExpression; import org.codehaus.groovy.ast.expr.DeclarationExpression; import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.LambdaExpression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.expr.PropertyExpression; import org.codehaus.groovy.ast.expr.VariableExpression; @@ -71,6 +72,8 @@ import static org.codehaus.groovy.ast.ClassHelper.long_TYPE; import static org.codehaus.groovy.transform.sc.StaticCompilationVisitor.ARRAYLIST_ADD_METHOD; import static org.codehaus.groovy.transform.sc.StaticCompilationVisitor.ARRAYLIST_CLASSNODE; import static org.codehaus.groovy.transform.sc.StaticCompilationVisitor.ARRAYLIST_CONSTRUCTOR; +import static org.codehaus.groovy.transform.stc.StaticTypesMarker.INFERRED_LAMBDA_TYPE; +import static org.codehaus.groovy.transform.stc.StaticTypesMarker.INFERRED_TYPE; /** * A specialized version of the multi type binary expression dispatcher which is aware of static compilation. @@ -134,8 +137,8 @@ public class StaticTypesBinaryExpressionMultiTypeDispatcher extends BinaryExpres @Override public void evaluateEqual(final BinaryExpression expression, final boolean defineVariable) { + Expression leftExpression = expression.getLeftExpression(); if (!defineVariable) { - Expression leftExpression = expression.getLeftExpression(); if (leftExpression instanceof PropertyExpression) { PropertyExpression pexp = (PropertyExpression) leftExpression; if (makeSetProperty( @@ -147,10 +150,15 @@ public class StaticTypesBinaryExpressionMultiTypeDispatcher extends BinaryExpres pexp.isImplicitThis(), pexp instanceof AttributeExpression)) return; } + } else { + Expression rightExpression = expression.getRightExpression(); + if (rightExpression instanceof LambdaExpression) { + rightExpression.putNodeMetaData(INFERRED_LAMBDA_TYPE, leftExpression.getNodeMetaData(INFERRED_TYPE)); + } } // GROOVY-5620: Spread safe/Null safe operator on LHS is not supported - if (expression.getLeftExpression() instanceof PropertyExpression - && ((PropertyExpression) expression.getLeftExpression()).isSpreadSafe() + if (leftExpression instanceof PropertyExpression + && ((PropertyExpression) leftExpression).isSpreadSafe() && StaticTypeCheckingSupport.isAssignment(expression.getOperation().getType())) { // rewrite it so that it can be statically compiled transformSpreadOnLHS(expression); http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java new file mode 100644 index 0000000..ab45f9c --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java @@ -0,0 +1,443 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.codehaus.groovy.classgen.asm.sc; + +import org.codehaus.groovy.GroovyBugError; +import org.codehaus.groovy.ast.ClassCodeVisitorSupport; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.ConstructorNode; +import org.codehaus.groovy.ast.InnerClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.LambdaExpression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.classgen.asm.BytecodeHelper; +import org.codehaus.groovy.classgen.asm.BytecodeVariable; +import org.codehaus.groovy.classgen.asm.CompileStack; +import org.codehaus.groovy.classgen.asm.LambdaWriter; +import org.codehaus.groovy.classgen.asm.OperandStack; +import org.codehaus.groovy.classgen.asm.WriterController; +import org.codehaus.groovy.classgen.asm.WriterControllerFactory; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.transform.stc.StaticTypesMarker; +import org.objectweb.asm.Handle; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.codehaus.groovy.transform.stc.StaticTypesMarker.INFERRED_LAMBDA_TYPE; +import static org.codehaus.groovy.transform.stc.StaticTypesMarker.PARAMETER_TYPE; +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ACC_STATIC; +import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.DUP; +import static org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static org.objectweb.asm.Opcodes.NEW; + +/** + * Writer responsible for generating lambda classes in statically compiled mode. + */ +public class StaticTypesLambdaWriter extends LambdaWriter { + private static final String DO_CALL = "doCall"; + private static final String ORIGINAL_PARAMETERS_WITH_EXACT_TYPE = "__ORIGINAL_PARAMETERS_WITH_EXACT_TYPE"; + private static final String LAMBDA_SHARED_VARIABLES = "__LAMBDA_SHARED_VARIABLES"; + private static final String ENCLOSING_THIS = "__enclosing_this"; + private static final String LAMBDA_THIS = "__lambda_this"; + public static final String INIT = "<init>"; + public static final String IS_GENERATED_CONSTRUCTOR = "__IS_GENERATED_CONSTRUCTOR"; + public static final String LAMBDA_WRAPPER = "__lambda_wrapper"; + public static final String SAM_NAME = "__SAM_NAME"; + private StaticTypesClosureWriter staticTypesClosureWriter; + private WriterController controller; + private WriterControllerFactory factory; + private final Map<Expression,ClassNode> lambdaClassMap = new HashMap<>(); + + public StaticTypesLambdaWriter(WriterController wc) { + super(wc); + this.staticTypesClosureWriter = new StaticTypesClosureWriter(wc); + this.controller = wc; + this.factory = new WriterControllerFactory() { + public WriterController makeController(final WriterController normalController) { + return controller; + } + }; + } + + @Override + public void writeLambda(LambdaExpression expression) { + ClassNode lambdaType = getLambdaType(expression); + + if (!ClassHelper.isFunctionalInterface(lambdaType.redirect())) { + // if the parameter type is not real FunctionInterface, generate the default bytecode, which is actually a closure + super.writeLambda(expression); + return; + } + + MethodNode abstractMethodNode = ClassHelper.findSAM(lambdaType.redirect()); + String abstractMethodDesc = createMethodDescriptor(abstractMethodNode); + + ClassNode classNode = controller.getClassNode(); + boolean isInterface = classNode.isInterface(); + ClassNode lambdaWrapperClassNode = getOrAddLambdaClass(expression, ACC_PUBLIC | (isInterface ? ACC_STATIC : 0) | ACC_SYNTHETIC, abstractMethodNode); + MethodNode syntheticLambdaMethodNode = lambdaWrapperClassNode.getMethods(DO_CALL).get(0); + + newGroovyLambdaWrapperAndLoad(lambdaWrapperClassNode, syntheticLambdaMethodNode); + + loadEnclosingClassInstance(); + + + MethodVisitor mv = controller.getMethodVisitor(); + OperandStack operandStack = controller.getOperandStack(); + + mv.visitInvokeDynamicInsn( + abstractMethodNode.getName(), + createAbstractMethodDesc(lambdaType, lambdaWrapperClassNode), + createBootstrapMethod(isInterface), + createBootstrapMethodArguments(abstractMethodDesc, lambdaWrapperClassNode, syntheticLambdaMethodNode) + ); + operandStack.replace(lambdaType.redirect(), 2); + + if (null != expression.getNodeMetaData(INFERRED_LAMBDA_TYPE)) { + // FIXME declaring variable whose initial value is a lambda, e.g. `Function<Integer, String> f = (Integer e) -> 'a' + e` + // Groovy will `POP` automatically, use `DUP` to duplicate the element of operand stack: + /* + INVOKEDYNAMIC apply(LTest1$_p_lambda1;LTest1;)Ljava/util/function/Function; [ + // handle kind 0x6 : INVOKESTATIC + java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; + // arguments: + (Ljava/lang/Object;)Ljava/lang/Object;, + // handle kind 0x5 : INVOKEVIRTUAL + Test1$_p_lambda1.doCall(LTest1;Ljava/lang/Integer;)Ljava/lang/String;, + (Ljava/lang/Integer;)Ljava/lang/String; + ] + DUP <-------------- FIXME ADDED ON PURPOSE, WE SHOULD REMOVE IT AFTER FIND BETTER SOLUTION + ASTORE 0 + L2 + ALOAD 0 + POP <-------------- Since operand stack is not empty, the `POP`s are issued by `controller.getOperandStack().popDownTo(mark);` in the method `org.codehaus.groovy.classgen.asm.StatementWriter.writeExpressionStatement`, but when we try to `operandStack.pop();` instead of `mv.visitInsn(DUP);`, we will get AIOOBE... + POP + */ + + mv.visitInsn(DUP); + + /* + org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: + General error during class generation: size==0 + + java.lang.ArrayIndexOutOfBoundsException: size==0 + at org.codehaus.groovy.classgen.asm.OperandStack.getTopOperand(OperandStack.java:693) + at org.codehaus.groovy.classgen.asm.BinaryExpressionHelper.evaluateEqual(BinaryExpressionHelper.java:397) + at org.codehaus.groovy.classgen.asm.sc.StaticTypesBinaryExpressionMultiTypeDispatcher.evaluateEqual(StaticTypesBinaryExpressionMultiTypeDispatcher.java:179) + at org.codehaus.groovy.classgen.AsmClassGenerator.visitDeclarationExpression(AsmClassGenerator.java:694) + at org.codehaus.groovy.ast.expr.DeclarationExpression.visit(DeclarationExpression.java:89) + at org.codehaus.groovy.classgen.asm.StatementWriter.writeExpressionStatement(StatementWriter.java:633) + at org.codehaus.groovy.classgen.AsmClassGenerator.visitExpressionStatement(AsmClassGenerator.java:681) + at org.codehaus.groovy.ast.stmt.ExpressionStatement.visit(ExpressionStatement.java:42) + */ + // operandStack.pop(); + } + + } + + private ClassNode getLambdaType(LambdaExpression expression) { + ClassNode type = expression.getNodeMetaData(PARAMETER_TYPE); + + if (null == type) { + type = expression.getNodeMetaData(INFERRED_LAMBDA_TYPE); + } + return type; + } + + private void loadEnclosingClassInstance() { + MethodVisitor mv = controller.getMethodVisitor(); + OperandStack operandStack = controller.getOperandStack(); + CompileStack compileStack = controller.getCompileStack(); + + if (controller.isStaticMethod() || compileStack.isInSpecialConstructorCall()) { + operandStack.pushConstant(ConstantExpression.NULL); + } else { + mv.visitVarInsn(ALOAD, 0); + operandStack.push(controller.getClassNode()); + } + } + + private BytecodeVariable newGroovyLambdaWrapperAndLoad(ClassNode lambdaWrapperClassNode, MethodNode syntheticLambdaMethodNode) { + MethodVisitor mv = controller.getMethodVisitor(); + String lambdaWrapperClassInternalName = BytecodeHelper.getClassInternalName(lambdaWrapperClassNode); + mv.visitTypeInsn(NEW, lambdaWrapperClassInternalName); + mv.visitInsn(DUP); + + loadEnclosingClassInstance(); + loadEnclosingClassInstance(); + + loadSharedVariables(syntheticLambdaMethodNode); + + List<ConstructorNode> constructorNodeList = + lambdaWrapperClassNode.getDeclaredConstructors().stream() + .filter(e -> Boolean.TRUE.equals(e.getNodeMetaData(IS_GENERATED_CONSTRUCTOR))) + .collect(Collectors.toList()); + + if (constructorNodeList.size() == 0) { + throw new GroovyBugError("Failed to find the generated constructor"); + } + + ConstructorNode constructorNode = constructorNodeList.get(0); + Parameter[] lambdaWrapperClassConstructorParameters = constructorNode.getParameters(); + mv.visitMethodInsn(INVOKESPECIAL, lambdaWrapperClassInternalName, INIT, BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, lambdaWrapperClassConstructorParameters), lambdaWrapperClassNode.isInterface()); + OperandStack operandStack = controller.getOperandStack(); + operandStack.replace(ClassHelper.CLOSURE_TYPE, lambdaWrapperClassConstructorParameters.length); + + BytecodeVariable variable = controller.getCompileStack().defineVariable(new VariableExpression(LAMBDA_WRAPPER, ClassHelper.CLOSURE_TYPE), false); + operandStack.storeVar(variable); + + operandStack.loadOrStoreVariable(variable, false); + + return variable; + } + + private Parameter[] loadSharedVariables(MethodNode syntheticLambdaMethodNode) { + Parameter[] lambdaSharedVariableParameters = syntheticLambdaMethodNode.getNodeMetaData(LAMBDA_SHARED_VARIABLES); + for (Parameter parameter : lambdaSharedVariableParameters) { + String parameterName = parameter.getName(); + loadReference(parameterName, controller); + if (parameter.getNodeMetaData(LambdaWriter.UseExistingReference.class) == null) { + parameter.setNodeMetaData(LambdaWriter.UseExistingReference.class, Boolean.TRUE); + } + } + + return lambdaSharedVariableParameters; + } + + private String createAbstractMethodDesc(ClassNode parameterType, ClassNode lambdaClassNode) { + List<Parameter> lambdaSharedVariableList = new LinkedList<>(); + + prependEnclosingThis(lambdaSharedVariableList); + prependParameter(lambdaSharedVariableList, LAMBDA_THIS, lambdaClassNode); + + return BytecodeHelper.getMethodDescriptor(parameterType.redirect(), lambdaSharedVariableList.toArray(Parameter.EMPTY_ARRAY)); + } + + private Handle createBootstrapMethod(boolean isInterface) { + return new Handle( + Opcodes.H_INVOKESTATIC, + "java/lang/invoke/LambdaMetafactory", + "metafactory", + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", + isInterface + ); + } + + private Object[] createBootstrapMethodArguments(String abstractMethodDesc, ClassNode lambdaClassNode, MethodNode syntheticLambdaMethodNode) { + return new Object[]{ + Type.getType(abstractMethodDesc), + new Handle( + Opcodes.H_INVOKEVIRTUAL, + lambdaClassNode.getName(), + syntheticLambdaMethodNode.getName(), + BytecodeHelper.getMethodDescriptor(syntheticLambdaMethodNode), + lambdaClassNode.isInterface() + ), + Type.getType(BytecodeHelper.getMethodDescriptor(syntheticLambdaMethodNode.getReturnType(), syntheticLambdaMethodNode.getNodeMetaData(ORIGINAL_PARAMETERS_WITH_EXACT_TYPE))) + }; + } + + private String createMethodDescriptor(MethodNode abstractMethodNode) { + return BytecodeHelper.getMethodDescriptor( + abstractMethodNode.getReturnType().getTypeClass(), + Arrays.stream(abstractMethodNode.getParameters()) + .map(e -> e.getType().getTypeClass()) + .toArray(Class[]::new) + ); + } + + public ClassNode getOrAddLambdaClass(LambdaExpression expression, int mods, MethodNode abstractMethodNode) { + ClassNode lambdaClass = lambdaClassMap.get(expression); + if (lambdaClass == null) { + lambdaClass = createLambdaClass(expression, mods, abstractMethodNode); + lambdaClassMap.put(expression, lambdaClass); + controller.getAcg().addInnerClass(lambdaClass); + lambdaClass.addInterface(ClassHelper.GENERATED_LAMBDA_TYPE); + lambdaClass.putNodeMetaData(WriterControllerFactory.class, factory); + } + return lambdaClass; + } + + protected ClassNode createLambdaClass(LambdaExpression expression, int mods, MethodNode abstractMethodNode) { + ClassNode outerClass = controller.getOutermostClass(); + ClassNode classNode = controller.getClassNode(); + String name = genLambdaClassName(); + boolean staticMethodOrInStaticClass = controller.isStaticMethod() || classNode.isStaticClass(); + + InnerClassNode answer = new InnerClassNode(classNode, name, mods, ClassHelper.CLOSURE_TYPE.getPlainNodeReference()); + answer.setEnclosingMethod(controller.getMethodNode()); + answer.setSynthetic(true); + answer.setUsingGenerics(outerClass.isUsingGenerics()); + answer.setSourcePosition(expression); + + if (staticMethodOrInStaticClass) { + answer.setStaticClass(true); + } + if (controller.isInScriptBody()) { + answer.setScriptBody(true); + } + + answer.addField(SAM_NAME, ACC_PUBLIC | ACC_STATIC | ACC_FINAL, ClassHelper.STRING_TYPE, new ConstantExpression(abstractMethodNode.getName())); + + MethodNode syntheticLambdaMethodNode = addSyntheticLambdaMethodNode(expression, answer, abstractMethodNode); + Parameter[] localVariableParameters = syntheticLambdaMethodNode.getNodeMetaData(LAMBDA_SHARED_VARIABLES); + + addFieldsAndGettersForLocalVariables(answer, localVariableParameters); + ConstructorNode constructorNode = addConstructor(expression, localVariableParameters, answer, createBlockStatementForConstructor(expression)); + constructorNode.putNodeMetaData(IS_GENERATED_CONSTRUCTOR, Boolean.TRUE); + + Parameter enclosingThisParameter = syntheticLambdaMethodNode.getParameters()[0]; + new TransformationVisitor(answer, enclosingThisParameter).visitMethod(syntheticLambdaMethodNode); + + return answer; + } + + private String genLambdaClassName() { + ClassNode classNode = controller.getClassNode(); + ClassNode outerClass = controller.getOutermostClass(); + MethodNode methodNode = controller.getMethodNode(); + + return classNode.getName() + "$" + + controller.getContext().getNextLambdaInnerName(outerClass, classNode, methodNode); + } + + private MethodNode addSyntheticLambdaMethodNode(LambdaExpression expression, InnerClassNode answer, MethodNode abstractMethodNode) { + Parameter[] parametersWithExactType = createParametersWithExactType(expression); // expression.getParameters(); +// ClassNode returnType = expression.getNodeMetaData(StaticTypesMarker.INFERRED_RETURN_TYPE); //abstractMethodNode.getReturnType(); + Parameter[] localVariableParameters = getLambdaSharedVariables(expression); + removeInitialValues(localVariableParameters); + + List<Parameter> methodParameterList = new LinkedList<Parameter>(Arrays.asList(parametersWithExactType)); + prependEnclosingThis(methodParameterList); + + MethodNode methodNode = + answer.addMethod( + DO_CALL, + Opcodes.ACC_PUBLIC, + abstractMethodNode.getReturnType() /*ClassHelper.OBJECT_TYPE*/ /*returnType*/, + methodParameterList.toArray(Parameter.EMPTY_ARRAY), + ClassNode.EMPTY_ARRAY, + expression.getCode() + ); + methodNode.putNodeMetaData(ORIGINAL_PARAMETERS_WITH_EXACT_TYPE, parametersWithExactType); + methodNode.putNodeMetaData(LAMBDA_SHARED_VARIABLES, localVariableParameters); + methodNode.setSourcePosition(expression); + + return methodNode; + } + + private Parameter prependEnclosingThis(List<Parameter> methodParameterList) { + return prependParameter(methodParameterList, ENCLOSING_THIS, controller.getClassNode().getPlainNodeReference()); + } + + private Parameter prependParameter(List<Parameter> methodParameterList, String parameterName, ClassNode parameterType) { + Parameter parameter = new Parameter(parameterType, parameterName); + + parameter.setOriginType(parameterType); + parameter.setClosureSharedVariable(false); + + methodParameterList.add(0, parameter); + + return parameter; + } + + private Parameter[] createParametersWithExactType(LambdaExpression expression) { + Parameter[] parameters = expression.getParameters(); + if (parameters == null) { + parameters = Parameter.EMPTY_ARRAY; + } + + for (int i = 0; i < parameters.length; i++) { + ClassNode inferredType = parameters[i].getNodeMetaData(StaticTypesMarker.INFERRED_TYPE); + + if (null == inferredType) { + continue; + } + + parameters[i].setType(inferredType); + parameters[i].setOriginType(inferredType); + } + + return parameters; + } + + @Override + protected ClassNode createClosureClass(final ClosureExpression expression, final int mods) { + return staticTypesClosureWriter.createClosureClass(expression, mods); + } + + private static final class TransformationVisitor extends ClassCodeVisitorSupport { + private CorrectAccessedVariableVisitor correctAccessedVariableVisitor; + private Parameter enclosingThisParameter; + + public TransformationVisitor(InnerClassNode icn, Parameter enclosingThisParameter) { + correctAccessedVariableVisitor = new CorrectAccessedVariableVisitor(icn); + this.enclosingThisParameter = enclosingThisParameter; + } + + @Override + public void visitVariableExpression(VariableExpression expression) { + correctAccessedVariableVisitor.visitVariableExpression(expression); + } + + @Override + public void visitMethodCallExpression(MethodCallExpression call) { + if (!call.getMethodTarget().isStatic()) { + Expression objectExpression = call.getObjectExpression(); + + if (objectExpression instanceof VariableExpression) { + VariableExpression originalObjectExpression = (VariableExpression) objectExpression; + if (null == originalObjectExpression.getAccessedVariable()) { + VariableExpression thisVariable = new VariableExpression(enclosingThisParameter); + thisVariable.setSourcePosition(originalObjectExpression); + + call.setObjectExpression(thisVariable); + call.setImplicitThis(false); + } + } + } + + super.visitMethodCallExpression(call); + } + + @Override + protected SourceUnit getSourceUnit() { + return null; + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterController.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterController.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterController.java index 47ef3d4..15943e5 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterController.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterController.java @@ -31,6 +31,7 @@ import org.codehaus.groovy.classgen.asm.CallSiteWriter; import org.codehaus.groovy.classgen.asm.ClosureWriter; import org.codehaus.groovy.classgen.asm.DelegatingController; import org.codehaus.groovy.classgen.asm.InvocationWriter; +import org.codehaus.groovy.classgen.asm.LambdaWriter; import org.codehaus.groovy.classgen.asm.StatementWriter; import org.codehaus.groovy.classgen.asm.TypeChooser; import org.codehaus.groovy.classgen.asm.UnaryExpressionHelper; @@ -60,6 +61,7 @@ public class StaticTypesWriterController extends DelegatingController { private BinaryExpressionMultiTypeDispatcher binaryExprHelper; private UnaryExpressionHelper unaryExpressionHelper; private ClosureWriter closureWriter; + private LambdaWriter lambdaWriter; public StaticTypesWriterController(WriterController normalController) { super(normalController); @@ -74,6 +76,7 @@ public class StaticTypesWriterController extends DelegatingController { this.typeChooser = new StaticTypesTypeChooser(); this.invocationWriter = new StaticInvocationWriter(this); this.closureWriter = new StaticTypesClosureWriter(this); + this.lambdaWriter = new StaticTypesLambdaWriter(this); this.unaryExpressionHelper = new StaticTypesUnaryExpressionHelper(this); CompilerConfiguration config = cn.getCompileUnit().getConfig(); @@ -183,4 +186,12 @@ public class StaticTypesWriterController extends DelegatingController { } return super.getClosureWriter(); } + + @Override + public LambdaWriter getLambdaWriter() { + if (isInStaticallyCheckedMethod) { + return lambdaWriter; + } + return super.getLambdaWriter(); + } } http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/runtime/GeneratedLambda.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/GeneratedLambda.java b/src/main/java/org/codehaus/groovy/runtime/GeneratedLambda.java new file mode 100644 index 0000000..0a74b99 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/GeneratedLambda.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.codehaus.groovy.runtime; + +/** + * Marker interface to identify lambda generated by the groovy compiler. + * For internal use only! + * + * @since 3.0.0 + */ +public interface GeneratedLambda { } http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java b/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java index 207a7ca..0e1f6cc 100644 --- a/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java +++ b/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java @@ -142,7 +142,7 @@ public class ProxyGeneratorAdapter extends ClassVisitor implements Opcodes { final ClassLoader proxyLoader, final boolean emptyBody, final Class delegateClass) { - super(Opcodes.ASM4, new ClassWriter(0)); + super(Opcodes.ASM5, new ClassWriter(0)); this.loader = proxyLoader != null ? createInnerLoader(proxyLoader, interfaces) : findClassLoader(superClass, interfaces); this.visitedMethods = new LinkedHashSet<Object>(); this.delegatedClosures = closureMap.isEmpty() ? EMPTY_DELEGATECLOSURE_MAP : new HashMap<String, Boolean>(); http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java ---------------------------------------------------------------------- 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 a372284..c3f0148 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -281,13 +281,15 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { public static final MethodNode CLOSURE_CALL_ONE_ARG; public static final MethodNode CLOSURE_CALL_VARGS; + public static final String CALL = "call"; + static { // Cache closure call methods - CLOSURE_CALL_NO_ARG = CLOSURE_TYPE.getDeclaredMethod("call", Parameter.EMPTY_ARRAY); - CLOSURE_CALL_ONE_ARG = CLOSURE_TYPE.getDeclaredMethod("call", new Parameter[]{ + CLOSURE_CALL_NO_ARG = CLOSURE_TYPE.getDeclaredMethod(CALL, Parameter.EMPTY_ARRAY); + CLOSURE_CALL_ONE_ARG = CLOSURE_TYPE.getDeclaredMethod(CALL, new Parameter[]{ new Parameter(OBJECT_TYPE, "arg") }); - CLOSURE_CALL_VARGS = CLOSURE_TYPE.getDeclaredMethod("call", new Parameter[]{ + CLOSURE_CALL_VARGS = CLOSURE_TYPE.getDeclaredMethod(CALL, new Parameter[]{ new Parameter(OBJECT_TYPE.makeArray(), "args") }); } @@ -3164,6 +3166,8 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { addCategoryMethodCallError(call); } mn = disambiguateMethods(mn, chosenReceiver!=null?chosenReceiver.getType():null, args, call); + + if (mn.size() == 1) { MethodNode directMethodCallCandidate = mn.get(0); if (call.getNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION) == null && @@ -3402,7 +3406,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { } protected boolean isClosureCall(final String name, final Expression objectExpression, final Expression arguments) { - if (objectExpression instanceof ClosureExpression && ("call".equals(name)||"doCall".equals(name))) return true; + if (objectExpression instanceof ClosureExpression && (CALL.equals(name)||"doCall".equals(name))) return true; if (objectExpression == VariableExpression.THIS_EXPRESSION) { FieldNode fieldNode = typeCheckingContext.getEnclosingClassNode().getDeclaredField(name); if (fieldNode != null) { @@ -3412,7 +3416,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { } } } else { - if (!"call".equals(name) && !"doCall".equals(name)) return false; + if (!CALL.equals(name) && !"doCall".equals(name)) return false; } return (getType(objectExpression).equals(CLOSURE_TYPE)); } @@ -3958,7 +3962,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { return null; } - private List<MethodNode> disambiguateMethods(List<MethodNode> methods, ClassNode receiver, ClassNode[] argTypes, final Expression expr) { + private List<MethodNode> disambiguateMethods(List<MethodNode> methods, ClassNode receiver, ClassNode[] argTypes, final Expression call) { if (methods.size()>1 && receiver!=null && argTypes!=null) { List<MethodNode> filteredWithGenerics = new LinkedList<MethodNode>(); for (MethodNode methodNode : methods) { @@ -3969,8 +3973,26 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { if (filteredWithGenerics.size()==1) { return filteredWithGenerics; } - methods = extension.handleAmbiguousMethods(methods, expr); + methods = extension.handleAmbiguousMethods(methods, call); } + + if (methods.size() > 1) { + if (call instanceof MethodCall) { + List<MethodNode> methodNodeList = new LinkedList<>(); + + String methodName = ((MethodCall) call).getMethodAsString(); + + for (MethodNode methodNode : methods) { + if (!methodNode.getName().equals(methodName)) { + continue; + } + methodNodeList.add(methodNode); + } + + methods = methodNodeList; + } + } + return methods; } @@ -4098,6 +4120,15 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { if (receiver.isInterface()) { collectAllInterfaceMethodsByName(receiver, name, methods); methods.addAll(OBJECT_TYPE.getMethods(name)); + + if (CALL.equals(name) && ClassHelper.isFunctionalInterface(receiver)) { + MethodNode sam = ClassHelper.findSAM(receiver); + MethodNode callMethodNode = new MethodNode(CALL, sam.getModifiers(), sam.getReturnType(), sam.getParameters(), sam.getExceptions(), sam.getCode()); + callMethodNode.setDeclaringClass(sam.getDeclaringClass()); + callMethodNode.setSourcePosition(sam); + + methods.addAll(Collections.singletonList(callMethodNode)); + } } // TODO: investigate the trait exclusion a bit further, needed otherwise // CallMethodOfTraitInsideClosureAndClosureParamTypeInference fails saying http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/main/java/org/codehaus/groovy/transform/stc/StaticTypesMarker.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypesMarker.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypesMarker.java index 9785433..73eecad 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypesMarker.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypesMarker.java @@ -37,5 +37,7 @@ public enum StaticTypesMarker { PV_FIELDS_MUTATION, // set of private fields that are set from closures or inner classes PV_METHODS_ACCESS, // set of private methods that are accessed from closures or inner classes DYNAMIC_RESOLUTION, // call recognized by a type checking extension as a dynamic method call - SUPER_MOP_METHOD_REQUIRED // used to store the list of MOP methods that still have to be generated + SUPER_MOP_METHOD_REQUIRED, // used to store the list of MOP methods that still have to be generated + PARAMETER_TYPE, // used to store the parameter type information of method invocation on an expression + INFERRED_LAMBDA_TYPE // used to store the lambda type information on a lambda expression } http://git-wip-us.apache.org/repos/asf/groovy/blob/302be69d/src/test/gls/generics/GenericsTestBase.java ---------------------------------------------------------------------- diff --git a/src/test/gls/generics/GenericsTestBase.java b/src/test/gls/generics/GenericsTestBase.java index 801806f..dc5fc94 100644 --- a/src/test/gls/generics/GenericsTestBase.java +++ b/src/test/gls/generics/GenericsTestBase.java @@ -57,7 +57,7 @@ public abstract class GenericsTestBase extends GroovyTestCase { } private class GenericsTester extends ClassVisitor { public GenericsTester(ClassVisitor cv) { - super(Opcodes.ASM4,cv); + super(Opcodes.ASM5,cv); } public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {