You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2019/06/15 14:26:50 UTC

[groovy] branch master updated: GROOVY-9131: Extension method support for native method references (#955)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 2510d51  GROOVY-9131: Extension method support for native method references (#955)
2510d51 is described below

commit 2510d5151ddd38051d53b3f2e1306db1b9977b53
Author: Daniel.Sun <su...@apache.org>
AuthorDate: Sat Jun 15 09:26:44 2019 -0500

    GROOVY-9131: Extension method support for native method references (#955)
---
 ...StaticTypesMethodReferenceExpressionWriter.java | 131 +++++++++++++++------
 .../transform/stc/StaticTypeCheckingSupport.java   |   2 +-
 .../transform/stc/MethodReferenceTest.groovy       |  30 +++++
 3 files changed, 126 insertions(+), 37 deletions(-)

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 110d41f..2a8438d 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
@@ -27,6 +27,7 @@ import org.codehaus.groovy.ast.Parameter;
 import org.codehaus.groovy.ast.expr.ArgumentListExpression;
 import org.codehaus.groovy.ast.expr.ArrayExpression;
 import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.MethodReferenceExpression;
 import org.codehaus.groovy.ast.tools.GeneralUtils;
@@ -35,18 +36,22 @@ import org.codehaus.groovy.classgen.asm.BytecodeHelper;
 import org.codehaus.groovy.classgen.asm.MethodReferenceExpressionWriter;
 import org.codehaus.groovy.classgen.asm.WriterController;
 import org.codehaus.groovy.runtime.ArrayTypeUtils;
+import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl;
 import org.codehaus.groovy.syntax.RuntimeParserException;
+import org.codehaus.groovy.transform.stc.ExtensionMethodNode;
+import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
 import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.filterMethodsByVisibility;
@@ -95,37 +100,35 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
         Parameter[] parametersWithExactType = createParametersWithExactType(abstractMethodNode, methodReferenceParamTypes);
 
         boolean isConstructorReference = isConstructorReference(methodRefName);
+
+        MethodNode methodRefMethod;
         if (isConstructorReference) {
-            methodRefName = createSyntheticMethodForConstructorReference();
-            addSyntheticMethodForConstructorReference(methodRefName, typeOrTargetRefType, parametersWithExactType);
+            methodRefName = genSyntheticMethodNameForConstructorReference();
+            methodRefMethod = addSyntheticMethodForConstructorReference(methodRefName, typeOrTargetRefType, parametersWithExactType);
+        } else {
+            // TODO move the `findMethodRefMethod` and checking to `StaticTypeCheckingVisitor`
+            methodRefMethod = findMethodRefMethod(methodRefName, parametersWithExactType, typeOrTargetRef);
         }
 
-        // TODO move the `findMethodRefMethod` and checking to `StaticTypeCheckingVisitor`
-        MethodNode methodRefMethod = findMethodRefMethod(methodRefName, parametersWithExactType, typeOrTargetRef, isConstructorReference);
+        validate(methodReferenceExpression, typeOrTargetRef, typeOrTargetRefType, methodRefName, parametersWithExactType, methodRefMethod);
 
-        if (null == methodRefMethod) {
-            throw new RuntimeParserException("Failed to find the expected method["
-                    + methodRefName + "("
-                    + Arrays.stream(parametersWithExactType)
-                            .map(e -> e.getType().getName())
-                            .collect(Collectors.joining(","))
-                    + ")] in the type[" + typeOrTargetRefType.getName() + "]", methodReferenceExpression);
-        } else {
-            if (parametersWithExactType.length > 0 && isTypeReferingInstanceMethod(typeOrTargetRef, methodRefMethod)) {
-                Parameter firstParameter = parametersWithExactType[0];
-                Class<?> typeOrTargetClass = typeOrTargetRef.getType().getTypeClass();
-                Class<?> firstParameterClass = firstParameter.getType().getTypeClass();
-                if (!typeOrTargetClass.isAssignableFrom(firstParameterClass)) {
-                    throw new RuntimeParserException("Invalid receiver type: " + firstParameterClass + " is not compatible with " + typeOrTargetClass, typeOrTargetRef);
-                }
+        if (isExtensionMethod(methodRefMethod)) {
+            ExtensionMethodNode extensionMethodNode = (ExtensionMethodNode) methodRefMethod;
+            methodRefMethod = extensionMethodNode.getExtensionMethodNode();
+            if (extensionMethodNode.isStaticExtension()) {
+                methodRefMethod = addSyntheticMethodForDGSM(methodRefMethod);
             }
+
+            ClassExpression classExpression = new ClassExpression(methodRefMethod.getDeclaringClass());
+            classExpression.setSourcePosition(typeOrTargetRef);
+            typeOrTargetRef = classExpression;
+            typeOrTargetRefType = typeOrTargetRef.getType();
         }
 
         methodRefMethod.putNodeMetaData(ORIGINAL_PARAMETERS_WITH_EXACT_TYPE, parametersWithExactType);
         MethodVisitor mv = controller.getMethodVisitor();
 
         boolean isClassExpr = isClassExpr(typeOrTargetRef);
-
         if (!isClassExpr) {
             if (isConstructorReference) {
                 // TODO move the checking code to the Parrot parser
@@ -162,10 +165,53 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
         }
     }
 
-    private void addSyntheticMethodForConstructorReference(String syntheticMethodName, ClassNode returnType, Parameter[] parametersWithExactType) {
+    private void validate(MethodReferenceExpression methodReferenceExpression, Expression typeOrTargetRef, ClassNode typeOrTargetRefType, String methodRefName, Parameter[] parametersWithExactType, MethodNode methodRefMethod) {
+        if (null == methodRefMethod) {
+            throw new RuntimeParserException("Failed to find the expected method["
+                    + methodRefName + "("
+                    + Arrays.stream(parametersWithExactType)
+                            .map(e -> e.getType().getName())
+                            .collect(Collectors.joining(","))
+                    + ")] in the type[" + typeOrTargetRefType.getName() + "]", methodReferenceExpression);
+        }
+
+        if (parametersWithExactType.length > 0 && isTypeReferingInstanceMethod(typeOrTargetRef, methodRefMethod)) {
+            Parameter firstParameter = parametersWithExactType[0];
+            Class<?> typeOrTargetClass = typeOrTargetRef.getType().getTypeClass();
+            Class<?> firstParameterClass = firstParameter.getType().getTypeClass();
+            if (!typeOrTargetClass.isAssignableFrom(firstParameterClass)) {
+                throw new RuntimeParserException("Invalid receiver type: " + firstParameterClass + " is not compatible with " + typeOrTargetClass, typeOrTargetRef);
+            }
+        }
+    }
+
+    private static boolean isExtensionMethod(MethodNode methodRefMethod) {
+        return methodRefMethod instanceof ExtensionMethodNode;
+    }
+
+    private MethodNode addSyntheticMethodForDGSM(MethodNode mn) {
+        Parameter[] parameters = removeFirstParameter(mn.getParameters());
+        ArgumentListExpression args = args(parameters);
+        args.getExpressions().add(0, ConstantExpression.NULL);
+
+        return controller.getClassNode().addSyntheticMethod(
+                "dgsm$" + mn.getParameters()[0].getType().getName().replace(".", "_") + "$" + mn.getName(),
+                Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC,
+                mn.getReturnType(),
+                parameters,
+                ClassNode.EMPTY_ARRAY,
+                block(
+                        returnS(
+                                callX(new ClassExpression(mn.getDeclaringClass()), mn.getName(), args)
+                        )
+                )
+        );
+    }
+
+    private MethodNode addSyntheticMethodForConstructorReference(String syntheticMethodName, ClassNode returnType, Parameter[] parametersWithExactType) {
         ArgumentListExpression ctorArgs = args(parametersWithExactType);
 
-        controller.getClassNode().addSyntheticMethod(
+        return controller.getClassNode().addSyntheticMethod(
                 syntheticMethodName,
                 Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC,
                 returnType,
@@ -188,7 +234,7 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
 
     }
 
-    private String createSyntheticMethodForConstructorReference() {
+    private String genSyntheticMethodNameForConstructorReference() {
         return controller.getContext().getNextConstructorReferenceSyntheticMethodName(controller.getMethodNode());
     }
 
@@ -238,31 +284,35 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
         return parameters;
     }
 
-    private MethodNode findMethodRefMethod(String methodRefName, Parameter[] abstractMethodParameters, Expression typeOrTargetRef, boolean isConstructorReference) {
-        if (isConstructorReference) {
-            return controller.getClassNode().getMethod(methodRefName, abstractMethodParameters);
-        }
-
+    private MethodNode findMethodRefMethod(String methodRefName, Parameter[] abstractMethodParameters, Expression typeOrTargetRef) {
         ClassNode typeOrTargetRefType = typeOrTargetRef.getType();
         List<MethodNode> methodNodeList = typeOrTargetRefType.getMethods(methodRefName);
+        Set<MethodNode> dgmMethodNodeSet = StaticTypeCheckingSupport.findDGMMethodsForClassNode(MetaClassRegistryImpl.class.getClassLoader(), typeOrTargetRefType, methodRefName);
+
+        List<MethodNode> allMethodNodeList = new LinkedList<>(methodNodeList);
+        allMethodNodeList.addAll(dgmMethodNodeSet);
+
         ClassNode classNode = controller.getClassNode();
 
         List<MethodNode> candidates = new LinkedList<>();
-        for (MethodNode mn : filterMethodsByVisibility(methodNodeList, classNode)) {
+        for (MethodNode mn : filterMethodsByVisibility(allMethodNodeList, classNode)) {
             Parameter[] parameters = abstractMethodParameters;
             if (isTypeReferingInstanceMethod(typeOrTargetRef, mn)) {
                 if (0 == abstractMethodParameters.length) {
                     continue;
                 }
 
-                parameters =
-                        new ArrayList<>(Arrays.asList(abstractMethodParameters))
-                                .subList(1, abstractMethodParameters.length)
-                                .toArray(Parameter.EMPTY_ARRAY);
+                parameters = removeFirstParameter(abstractMethodParameters);
+            }
 
+            Parameter[] methodParameters;
+            if (isExtensionMethod(mn)) {
+                methodParameters = removeFirstParameter(((ExtensionMethodNode) mn).getExtensionMethodNode().getParameters());
+            } else {
+                methodParameters = mn.getParameters();
             }
 
-            if (ParameterUtils.parametersCompatible(parameters, mn.getParameters())) {
+            if (ParameterUtils.parametersCompatible(parameters, methodParameters)) {
                 candidates.add(mn);
             }
         }
@@ -270,8 +320,13 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
         return chooseMethodRefMethodCandidate(typeOrTargetRef, candidates);
     }
 
+    private static Parameter[] removeFirstParameter(Parameter[] parameters) {
+        return Arrays.stream(parameters).skip(1).toArray(Parameter[]::new);
+    }
+
     private static boolean isTypeReferingInstanceMethod(Expression typeOrTargetRef, MethodNode mn) {  // class::instanceMethod
-        return !mn.isStatic() && isClassExpr(typeOrTargetRef);
+        return (!mn.isStatic() || (isExtensionMethod(mn) && !((ExtensionMethodNode) mn).isStaticExtension()))
+                && isClassExpr(typeOrTargetRef);
     }
 
     /**
@@ -306,6 +361,10 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
             score += 9;
         }
 
+        if (isExtensionMethod(mn)) {
+            score -= 100;
+        }
+
         return score;
     }
 }
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
index 1490814..ef043d5 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -295,7 +295,7 @@ public abstract class StaticTypeCheckingSupport {
         return findDGMMethodsForClassNode(MetaClassRegistryImpl.class.getClassLoader(), clazz, name);
     }
 
-    protected static Set<MethodNode> findDGMMethodsForClassNode(final ClassLoader loader, ClassNode clazz, String name) {
+    public static Set<MethodNode> findDGMMethodsForClassNode(final ClassLoader loader, ClassNode clazz, String name) {
         TreeSet<MethodNode> accumulator = new TreeSet<MethodNode>(DGM_METHOD_NODE_COMPARATOR);
         findDGMMethodsForClassNode(loader, clazz, name, accumulator);
         return accumulator;
diff --git a/src/test/groovy/transform/stc/MethodReferenceTest.groovy b/src/test/groovy/transform/stc/MethodReferenceTest.groovy
index 8f932d8..ac75952 100644
--- a/src/test/groovy/transform/stc/MethodReferenceTest.groovy
+++ b/src/test/groovy/transform/stc/MethodReferenceTest.groovy
@@ -413,4 +413,34 @@ class MethodReferenceTest extends GroovyTestCase {
 
         assert errMsg.contains('Invalid receiver type: class java.lang.Integer is not compatible with class java.lang.String')
     }
+
+    // class::instanceMethod, actually class::staticMethod
+    void testFunctionCI_DGM() {
+        assertScript '''
+            import java.util.stream.Collectors
+            
+            @groovy.transform.CompileStatic
+            void p() {
+                def result = ['a', 'ab', 'abc'].stream().map(String::size).collect(Collectors.toList())
+                assert [1, 2, 3] == result
+            }
+            
+            p()
+        '''
+    }
+
+    // class::staticMethod
+    void testFunctionCS_DGSM() {
+        assertScript '''
+            import java.util.stream.Collectors
+            
+            @groovy.transform.CompileStatic
+            void p() {
+                def result = [{}, {}, {}].stream().map(Thread::startDaemon).collect(Collectors.toList())
+                assert result.every(e -> e instanceof Thread)
+            }
+            
+            p()
+        '''
+    }
 }