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()
'''
}
+
}