You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2019/04/08 12:58:54 UTC

[groovy] 02/20: Initial implementation of native method reference

This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit b76af9ee07b0d1564f3950634b929cb97c6bb644
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sat Feb 23 04:00:50 2019 +0800

    Initial implementation of native method reference
---
 .../asm/MethodPointerExpressionWriter.java         |  2 +-
 .../asm/sc/AbstractFunctionInterfaceWriter.java    | 64 +++++++++++++++
 ...icTypesBinaryExpressionMultiTypeDispatcher.java |  7 +-
 .../classgen/asm/sc/StaticTypesLambdaWriter.java   | 40 ++-------
 ...StaticTypesMethodReferenceExpressionWriter.java | 96 +++++++++++++++++++++-
 .../groovy/transform/stc/StaticTypesMarker.java    |  2 +-
 src/test/groovy/bugs/Groovy9008.groovy             |  7 +-
 7 files changed, 173 insertions(+), 45 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/MethodPointerExpressionWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/MethodPointerExpressionWriter.java
index 12c6db5..ba14cb4 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/MethodPointerExpressionWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/MethodPointerExpressionWriter.java
@@ -31,7 +31,7 @@ public class MethodPointerExpressionWriter {
     // Closure
     static final MethodCaller getMethodPointer = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "getMethodPointer");
 
-    private final WriterController controller;
+    protected final WriterController controller;
 
     public MethodPointerExpressionWriter(final WriterController controller) {
         this.controller = controller;
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionInterfaceWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionInterfaceWriter.java
new file mode 100644
index 0000000..6a3731f
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionInterfaceWriter.java
@@ -0,0 +1,64 @@
+/*
+ *  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.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.classgen.asm.BytecodeHelper;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+
+import static org.codehaus.groovy.transform.stc.StaticTypesMarker.INFERRED_FUNCTION_INTERFACE_TYPE;
+import static org.codehaus.groovy.transform.stc.StaticTypesMarker.PARAMETER_TYPE;
+
+/**
+ * @since 3.0.0
+ */
+public interface AbstractFunctionInterfaceWriter {
+    default ClassNode getFunctionInterfaceType(Expression expression) {
+        ClassNode type = expression.getNodeMetaData(PARAMETER_TYPE);
+
+        if (null == type) {
+            type = expression.getNodeMetaData(INFERRED_FUNCTION_INTERFACE_TYPE);
+        }
+        return type;
+    }
+
+    default String createMethodDescriptor(MethodNode abstractMethodNode) {
+        return BytecodeHelper.getMethodDescriptor(
+                abstractMethodNode.getReturnType().getTypeClass(),
+                Arrays.stream(abstractMethodNode.getParameters())
+                        .map(e -> e.getType().getTypeClass())
+                        .toArray(Class[]::new)
+        );
+    }
+
+    default 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
+        );
+    }
+}
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 ccbe011..21f116b 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
@@ -35,6 +35,7 @@ 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.MethodReferenceExpression;
 import org.codehaus.groovy.ast.expr.PropertyExpression;
 import org.codehaus.groovy.ast.expr.VariableExpression;
 import org.codehaus.groovy.ast.stmt.EmptyStatement;
@@ -73,7 +74,7 @@ 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_FUNCTION_INTERFACE_TYPE;
 import static org.codehaus.groovy.transform.stc.StaticTypesMarker.INFERRED_TYPE;
 
 /**
@@ -150,8 +151,8 @@ public class StaticTypesBinaryExpressionMultiTypeDispatcher extends BinaryExpres
             }
         } else {
             Expression rightExpression = expression.getRightExpression();
-            if (rightExpression instanceof LambdaExpression) {
-                rightExpression.putNodeMetaData(INFERRED_LAMBDA_TYPE, leftExpression.getNodeMetaData(INFERRED_TYPE));
+            if (rightExpression instanceof LambdaExpression || rightExpression instanceof MethodReferenceExpression) {
+                rightExpression.putNodeMetaData(INFERRED_FUNCTION_INTERFACE_TYPE, leftExpression.getNodeMetaData(INFERRED_TYPE));
             }
         }
         // GROOVY-5620: Spread safe/Null safe operator on LHS is not supported
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
index c9301df..891e7e7 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
@@ -55,8 +55,6 @@ import java.util.Map;
 import java.util.stream.Collectors;
 
 import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
-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;
@@ -69,7 +67,7 @@ import static org.objectweb.asm.Opcodes.NEW;
 /**
  * Writer responsible for generating lambda classes in statically compiled mode.
  */
-public class StaticTypesLambdaWriter extends LambdaWriter {
+public class StaticTypesLambdaWriter extends LambdaWriter implements AbstractFunctionInterfaceWriter {
     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";
@@ -91,10 +89,10 @@ public class StaticTypesLambdaWriter extends LambdaWriter {
 
     @Override
     public void writeLambda(LambdaExpression expression) {
-        ClassNode lambdaType = getLambdaType(expression);
-        ClassNode redirect = lambdaType.redirect();
+        ClassNode functionInterfaceType = getFunctionInterfaceType(expression);
+        ClassNode redirect = functionInterfaceType.redirect();
 
-        if (null == lambdaType || !ClassHelper.isFunctionalInterface(redirect)) {
+        if (null == functionInterfaceType || !ClassHelper.isFunctionalInterface(redirect)) {
             // if the parameter type is not real FunctionInterface or failed to be inferred, generate the default bytecode, which is actually a closure
             super.writeLambda(expression);
             return;
@@ -118,22 +116,13 @@ public class StaticTypesLambdaWriter extends LambdaWriter {
 
         mv.visitInvokeDynamicInsn(
                 abstractMethodNode.getName(),
-                createAbstractMethodDesc(lambdaType, lambdaWrapperClassNode),
+                createAbstractMethodDesc(functionInterfaceType, lambdaWrapperClassNode),
                 createBootstrapMethod(isInterface),
                 createBootstrapMethodArguments(abstractMethodDesc, lambdaWrapperClassNode, syntheticLambdaMethodNode)
         );
         operandStack.replace(redirect, 2);
     }
 
-    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();
@@ -196,16 +185,6 @@ public class StaticTypesLambdaWriter extends LambdaWriter {
         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),
@@ -220,15 +199,6 @@ public class StaticTypesLambdaWriter extends LambdaWriter {
         };
     }
 
-    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) {
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
index 92b4ab9..339b112 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
@@ -18,21 +18,111 @@
  */
 package org.codehaus.groovy.classgen.asm.sc;
 
+import groovy.lang.GroovyRuntimeException;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
 import org.codehaus.groovy.ast.expr.MethodReferenceExpression;
+import org.codehaus.groovy.ast.tools.ParameterUtils;
+import org.codehaus.groovy.classgen.asm.BytecodeHelper;
 import org.codehaus.groovy.classgen.asm.MethodReferenceExpressionWriter;
 import org.codehaus.groovy.classgen.asm.WriterController;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * Writer responsible for generating method reference in statically compiled mode.
  * @since 3.0.0
  */
-public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceExpressionWriter {
+public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceExpressionWriter implements AbstractFunctionInterfaceWriter {
     public StaticTypesMethodReferenceExpressionWriter(WriterController controller) {
         super(controller);
     }
 
     @Override
-    public void writeMethodReferenceExpression(MethodReferenceExpression expression) {
-        super.writeMethodReferenceExpression(expression); // TODO generate native method reference bytecode here
+    public void writeMethodReferenceExpression(MethodReferenceExpression methodReferenceExpression) {
+        // TODO generate native method reference bytecode here
+        ClassNode functionInterfaceType = getFunctionInterfaceType(methodReferenceExpression);
+        ClassNode redirect = functionInterfaceType.redirect();
+
+        MethodNode abstractMethodNode = ClassHelper.findSAM(redirect);
+        String abstractMethodDesc = createMethodDescriptor(abstractMethodNode);
+
+        ClassNode classNode = controller.getClassNode();
+        boolean isInterface = classNode.isInterface();
+
+        ClassNode mrExpressionType = methodReferenceExpression.getExpression().getType();
+        String mrMethodName = methodReferenceExpression.getMethodName().getText();
+        MethodNode mrMethodNode = findMrMethodNode(mrMethodName, abstractMethodNode, mrExpressionType);
+
+        if (null == mrMethodNode) {
+            throw new GroovyRuntimeException("Failed to find the expected method[" + mrMethodName + "] in type[" + mrExpressionType.getName() + "]");
+        }
+
+        MethodVisitor mv = controller.getMethodVisitor();
+        mv.visitInvokeDynamicInsn(
+                abstractMethodNode.getName(),
+                BytecodeHelper.getMethodDescriptor(redirect, Parameter.EMPTY_ARRAY),
+                createBootstrapMethod(isInterface),
+                createBootstrapMethodArguments(abstractMethodDesc, mrExpressionType, mrMethodNode, abstractMethodNode));
+
+        controller.getOperandStack().push(redirect);
+    }
+
+    private MethodNode findMrMethodNode(String mrMethodName, MethodNode abstractMethodNode, ClassNode mrExpressionType) {
+        Parameter[] abstractMethodParameters = abstractMethodNode.getParameters();
+        List<MethodNode> methodNodeList = mrExpressionType.getMethods(mrMethodName);
+        ClassNode classNode = controller.getClassNode();
+
+        MethodNode mrMethodNode = null;
+        for (MethodNode mn : methodNodeList) {
+            if (mn.isPrivate() && !mrExpressionType.getName().equals(classNode.getName())) {
+                continue;
+            }
+            if ((mn.isPackageScope() || mn.isProtected()) && !mrExpressionType.getPackageName().equals(classNode.getPackageName())) {
+                continue;
+            }
+            if (mn.isProtected() && !classNode.isDerivedFrom(mrExpressionType)) {
+                continue;
+            }
+
+            if (mn.isStatic()) {
+                if (ParameterUtils.parametersEqual(mn.getParameters(), abstractMethodParameters)) {
+                    mrMethodNode = mn;
+                    break;
+                }
+            } else {
+                if (0 == abstractMethodParameters.length) {
+                    break;
+                }
+                if (ParameterUtils.parametersEqual(mn.getParameters(), new ArrayList<>(Arrays.asList(abstractMethodParameters)).subList(1, abstractMethodParameters.length).toArray(Parameter.EMPTY_ARRAY))) {
+                    mrMethodNode = mn;
+                    break;
+                }
+            }
+        }
+
+        return mrMethodNode;
+    }
+
+    private Object[] createBootstrapMethodArguments(String abstractMethodDesc, ClassNode expressionType, MethodNode mrMethodNode, MethodNode abstractMethodNode) {
+        return new Object[]{
+                Type.getType(abstractMethodDesc),
+                new Handle(
+                        Opcodes.H_INVOKEVIRTUAL,
+                        BytecodeHelper.getClassInternalName(expressionType.getTypeClass()),
+                        mrMethodNode.getName(),
+                        BytecodeHelper.getMethodDescriptor(mrMethodNode),
+                        expressionType.isInterface()
+                ),
+                Type.getType(BytecodeHelper.getMethodDescriptor(abstractMethodNode.getReturnType(), abstractMethodNode.getParameters()))
+        };
     }
 }
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 73eecad..44fbf3e 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypesMarker.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypesMarker.java
@@ -39,5 +39,5 @@ public enum StaticTypesMarker {
     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
     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
+    INFERRED_FUNCTION_INTERFACE_TYPE // used to store the function interface type information on an expression
 }
diff --git a/src/test/groovy/bugs/Groovy9008.groovy b/src/test/groovy/bugs/Groovy9008.groovy
index fba2a02..3d85933 100644
--- a/src/test/groovy/bugs/Groovy9008.groovy
+++ b/src/test/groovy/bugs/Groovy9008.groovy
@@ -19,12 +19,13 @@
 package groovy.bugs
 
 class Groovy9008 extends GroovyTestCase {
-    void testMethodReference() {
+    void testMethodReferenceFunction() {
         assertScript '''
             import java.util.stream.Collectors
             
             void p() {
                 def result = [1, 2, 3].stream().map(Object::toString).collect(Collectors.toList())
+                assert 3 == result.size()
                 assert ['1', '2', '3'] == result
             }
             
@@ -32,17 +33,19 @@ class Groovy9008 extends GroovyTestCase {
         '''
     }
 
-    void testMethodReferenceSC() {
+    void testMethodReferenceFunctionSC() {
         assertScript '''
             import java.util.stream.Collectors
             
             @groovy.transform.CompileStatic
             void p() {
                 def result = [1, 2, 3].stream().map(Object::toString).collect(Collectors.toList())
+                assert 3 == result.size()
                 assert ['1', '2', '3'] == result
             }
             
             p()
         '''
     }
+
 }