diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java index 63e1ad2bfc..61f269bf59 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java @@ -5199,4 +5199,31 @@ public void testCompileStatic9786() { runConformTest(sources, "B"); } + + @Test + public void testCompileStatic9799() { + assumeTrue(isParrotParser()); + + //@formatter:off + String[] sources = { + "Main.groovy", + "class C {\n" + + " String x\n" + + "}\n" + + "class D {\n" + + " String x\n" + + " static D from(C c) {\n" + + " new D(x: c.x)\n" + + " }\n" + + "}\n" + + "@groovy.transform.CompileStatic\n" + + "void test(C c) {\n" + + " print Optional.of(c).map(D::from).map(D::getX).get()\n" + + "}\n" + + "test(new C(x: 'works'))\n", + }; + //@formatter:on + + runConformTest(sources, "works"); + } } diff --git a/base/org.codehaus.groovy30/.checkstyle b/base/org.codehaus.groovy30/.checkstyle index 28860e99b6..d9b361ba2f 100644 --- a/base/org.codehaus.groovy30/.checkstyle +++ b/base/org.codehaus.groovy30/.checkstyle @@ -37,8 +37,7 @@ - - + diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/ast/tools/ParameterUtils.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/ast/tools/ParameterUtils.java new file mode 100644 index 0000000000..d96bd32e08 --- /dev/null +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/ast/tools/ParameterUtils.java @@ -0,0 +1,84 @@ +/* + * 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.ast.tools; + +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.Parameter; + +import java.util.function.BiPredicate; + +public class ParameterUtils { + + public static boolean parametersEqual(Parameter[] a, Parameter[] b) { + return parametersEqual(a, b, false); + } + + public static boolean parametersEqualWithWrapperType(Parameter[] a, Parameter[] b) { + return parametersEqual(a, b, true); + } + + /** + * Checks if two parameter arrays are type-compatible. + * + * each parameter should match the following condition: + * {@code targetParameter.getType().getTypeClass().isAssignableFrom(sourceParameter.getType().getTypeClass())} + * + * @param source source parameters + * @param target target parameters + * @return the check result + * @since 3.0.0 + */ + public static boolean parametersCompatible(Parameter[] source, Parameter[] target) { + /* GRECLIPSE edit -- GROOVY-9799 + return parametersMatch(source, target, (sourceType, targetType) -> + ClassHelper.getWrapper(targetType).getTypeClass().isAssignableFrom(ClassHelper.getWrapper(sourceType).getTypeClass()) + ); + */ + return parametersMatch(source, target, org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport::isAssignableTo); + // GRECLIPSE end + } + + private static boolean parametersEqual(Parameter[] a, Parameter[] b, boolean wrapType) { + return parametersMatch(a, b, (aType, bType) -> { + if (wrapType) { + aType = ClassHelper.getWrapper(aType); + bType = ClassHelper.getWrapper(bType); + } + return aType.equals(bType); + }); + } + + private static boolean parametersMatch(Parameter[] a, Parameter[] b, BiPredicate typeChecker) { + if (a.length == b.length) { + boolean answer = true; + for (int i = 0, n = a.length; i < n; i += 1) { + ClassNode aType = a[i].getType(); + ClassNode bType = b[i].getType(); + + if (!typeChecker.test(aType, bType)) { + answer = false; + break; + } + } + return answer; + } + return false; + } +} diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java index 13d840e048..bbb5e032db 100644 --- a/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java @@ -35,29 +35,29 @@ import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.MethodReferenceExpression; import org.codehaus.groovy.ast.tools.GeneralUtils; -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.codehaus.groovy.runtime.ArrayTypeUtils; import org.codehaus.groovy.syntax.RuntimeParserException; import org.codehaus.groovy.transform.stc.ExtensionMethodNode; -import groovyjarjarasm.asm.MethodVisitor; import groovyjarjarasm.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.classX; import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX; import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS; +import static org.codehaus.groovy.ast.tools.ParameterUtils.parametersCompatible; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.filterMethodsByVisibility; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsForClassNode; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isAssignableTo; import static org.codehaus.groovy.transform.stc.StaticTypesMarker.CLOSURE_ARGUMENTS; /** @@ -70,45 +70,33 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE private static final ClassNode GENERATED_TYPE = ClassHelper.make(Generated.class); private static final ClassNode COMPILE_STATIC_TYPE = ClassHelper.make(CompileStatic.class); - public StaticTypesMethodReferenceExpressionWriter(WriterController controller) { + public StaticTypesMethodReferenceExpressionWriter(final WriterController controller) { super(controller); } @Override - public void writeMethodReferenceExpression(MethodReferenceExpression methodReferenceExpression) { + public void writeMethodReferenceExpression(final MethodReferenceExpression methodReferenceExpression) { ClassNode functionalInterfaceType = getFunctionalInterfaceType(methodReferenceExpression); - if (null == functionalInterfaceType) { - // if the parameter type failed to be inferred, generate the default bytecode, which is actually a method closure + if (functionalInterfaceType == null || !ClassHelper.isFunctionalInterface(functionalInterfaceType)) { + // generate the default bytecode, which is actually a method closure super.writeMethodReferenceExpression(methodReferenceExpression); return; } ClassNode redirect = functionalInterfaceType.redirect(); - if (!ClassHelper.isFunctionalInterface(redirect)) { - // if the parameter type is not real FunctionalInterface, generate the default bytecode, which is actually a method closure - super.writeMethodReferenceExpression(methodReferenceExpression); - return; - } - MethodNode abstractMethodNode = ClassHelper.findSAM(redirect); - String abstractMethodDesc = createMethodDescriptor(abstractMethodNode); ClassNode classNode = controller.getClassNode(); - boolean isInterface = classNode.isInterface(); - Expression typeOrTargetRef = methodReferenceExpression.getExpression(); - ClassNode typeOrTargetRefType = typeOrTargetRef.getType(); - // GRECLIPSE add -- GROOVY-9762 - if (!isClassExpr(typeOrTargetRef)) { - typeOrTargetRefType = controller.getTypeChooser().resolveType(typeOrTargetRef, classNode); - } - // GRECLIPSE end - String methodRefName = methodReferenceExpression.getMethodName().getText(); + boolean isClassExpression = (typeOrTargetRef instanceof ClassExpression); + ClassNode typeOrTargetRefType = isClassExpression ? typeOrTargetRef.getType() + : controller.getTypeChooser().resolveType(typeOrTargetRef, classNode); ClassNode[] methodReferenceParamTypes = methodReferenceExpression.getNodeMetaData(CLOSURE_ARGUMENTS); Parameter[] parametersWithExactType = createParametersWithExactType(abstractMethodNode, methodReferenceParamTypes); + String methodRefName = methodReferenceExpression.getMethodName().getText(); boolean isConstructorReference = isConstructorReference(methodRefName); MethodNode methodRefMethod; @@ -116,8 +104,8 @@ public void writeMethodReferenceExpression(MethodReferenceExpression methodRefer 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 + methodRefMethod = findMethodRefMethod(methodRefName, parametersWithExactType, typeOrTargetRef, typeOrTargetRefType); } validate(methodReferenceExpression, typeOrTargetRef, typeOrTargetRefType, methodRefName, parametersWithExactType, methodRefMethod); @@ -129,77 +117,65 @@ public void writeMethodReferenceExpression(MethodReferenceExpression methodRefer methodRefMethod = addSyntheticMethodForDGSM(methodRefMethod); } - ClassExpression classExpression = new ClassExpression(methodRefMethod.getDeclaringClass()); + typeOrTargetRefType = methodRefMethod.getDeclaringClass(); + Expression classExpression = classX(typeOrTargetRefType); 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 (!isClassExpression) { if (isConstructorReference) { - // TODO move the checking code to the Parrot parser + // TODO: move the checking code to the parser addFatalError("Constructor reference must be className::new", methodReferenceExpression); - } - - if (methodRefMethod.isStatic()) { - ClassExpression classExpression = new ClassExpression(typeOrTargetRefType); + } else if (methodRefMethod.isStatic()) { + ClassExpression classExpression = classX(typeOrTargetRefType); classExpression.setSourcePosition(typeOrTargetRef); typeOrTargetRef = classExpression; - isClassExpr = true; - } - - if (!isClassExpr) { + isClassExpression = true; + } else { typeOrTargetRef.visit(controller.getAcg()); } } - mv.visitInvokeDynamicInsn( + controller.getMethodVisitor().visitInvokeDynamicInsn( abstractMethodNode.getName(), createAbstractMethodDesc(functionalInterfaceType, typeOrTargetRef), - createBootstrapMethod(isInterface, false), + createBootstrapMethod(classNode.isInterface(), false), createBootstrapMethodArguments( abstractMethodDesc, methodRefMethod.isStatic() || isConstructorReference ? Opcodes.H_INVOKESTATIC : Opcodes.H_INVOKEVIRTUAL, isConstructorReference ? controller.getClassNode() : typeOrTargetRefType, - methodRefMethod, false) + methodRefMethod, + false + ) ); - if (isClassExpr) { + if (isClassExpression) { controller.getOperandStack().push(redirect); } else { controller.getOperandStack().replace(redirect, 1); } } - private void validate(MethodReferenceExpression methodReferenceExpression, Expression typeOrTargetRef, ClassNode typeOrTargetRefType, String methodRefName, Parameter[] parametersWithExactType, MethodNode methodRefMethod) { - if (null == methodRefMethod) { + private void validate(final MethodReferenceExpression methodReferenceExpression, final Expression typeOrTargetRef, final ClassNode typeOrTargetRefType, final String methodRefName, final Parameter[] parametersWithExactType, final MethodNode methodRefMethod) { + if (methodRefMethod == null) { addFatalError("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); + + ")] in the type[" + typeOrTargetRefType.getText() + "]", methodReferenceExpression); + } else if (parametersWithExactType.length > 0 && isTypeReferingInstanceMethod(typeOrTargetRef, methodRefMethod)) { + ClassNode firstParameterType = parametersWithExactType[0].getType(); + if (!isAssignableTo(firstParameterType, typeOrTargetRefType)) { + throw new RuntimeParserException("Invalid receiver type: " + firstParameterType.getText() + " is not compatible with " + typeOrTargetRefType.getText(), typeOrTargetRef); } } } - private static boolean isExtensionMethod(MethodNode methodRefMethod) { - return methodRefMethod instanceof ExtensionMethodNode; - } - - private MethodNode addSyntheticMethodForDGSM(MethodNode mn) { + private MethodNode addSyntheticMethodForDGSM(final MethodNode mn) { Parameter[] parameters = removeFirstParameter(mn.getParameters()); ArgumentListExpression args = args(parameters); args.getExpressions().add(0, ConstantExpression.NULL); @@ -212,7 +188,7 @@ private MethodNode addSyntheticMethodForDGSM(MethodNode mn) { ClassNode.EMPTY_ARRAY, block( returnS( - callX(new ClassExpression(mn.getDeclaringClass()), mn.getName(), args) + callX(classX(mn.getDeclaringClass()), mn.getName(), args) ) ) ); @@ -223,7 +199,7 @@ private MethodNode addSyntheticMethodForDGSM(MethodNode mn) { return syntheticMethodNode; } - private MethodNode addSyntheticMethodForConstructorReference(String syntheticMethodName, ClassNode returnType, Parameter[] parametersWithExactType) { + private MethodNode addSyntheticMethodForConstructorReference(final String syntheticMethodName, final ClassNode returnType, final Parameter[] parametersWithExactType) { ArgumentListExpression ctorArgs = args(parametersWithExactType); MethodNode syntheticMethodNode = controller.getClassNode().addSyntheticMethod( @@ -257,35 +233,23 @@ private String genSyntheticMethodNameForConstructorReference() { return controller.getContext().getNextConstructorReferenceSyntheticMethodName(controller.getMethodNode()); } - private boolean isConstructorReference(String methodRefName) { - return "new".equals(methodRefName); - } - - private static boolean isClassExpr(Expression methodRef) { - return methodRef instanceof ClassExpression; - } + private String createAbstractMethodDesc(final ClassNode functionalInterfaceType, final Expression methodRef) { + List methodReferenceSharedVariableList = new ArrayList<>(); - private String createAbstractMethodDesc(ClassNode functionalInterfaceType, Expression methodRef) { - List methodReferenceSharedVariableList = new LinkedList<>(); - - if (!(isClassExpr(methodRef))) { - ClassNode methodRefTargetType = methodRef.getType(); - prependParameter(methodReferenceSharedVariableList, METHODREF_EXPR_INSTANCE, methodRefTargetType); + if (!(methodRef instanceof ClassExpression)) { + prependParameter(methodReferenceSharedVariableList, METHODREF_EXPR_INSTANCE, + controller.getTypeChooser().resolveType(methodRef, controller.getClassNode())); } return BytecodeHelper.getMethodDescriptor(functionalInterfaceType.redirect(), methodReferenceSharedVariableList.toArray(Parameter.EMPTY_ARRAY)); } - private Parameter[] createParametersWithExactType(MethodNode abstractMethodNode, ClassNode[] inferredParameterTypes) { + private Parameter[] createParametersWithExactType(final MethodNode abstractMethodNode, final ClassNode[] inferredParameterTypes) { Parameter[] originalParameters = abstractMethodNode.getParameters(); - - // We MUST clone the parameters to avoid impacting the original parameter type of SAM + // MUST clone the parameters to avoid impacting the original parameter type of SAM Parameter[] parameters = GeneralUtils.cloneParams(originalParameters); - if (parameters == null) { - parameters = Parameter.EMPTY_ARRAY; - } - for (int i = 0; i < parameters.length; i++) { + for (int i = 0, n = parameters.length; i < n; i += 1) { Parameter parameter = parameters[i]; ClassNode parameterType = parameter.getType(); ClassNode inferredType = inferredParameterTypes[i]; @@ -303,60 +267,61 @@ private Parameter[] createParametersWithExactType(MethodNode abstractMethodNode, return parameters; } - private MethodNode findMethodRefMethod(String methodRefName, Parameter[] abstractMethodParameters, Expression typeOrTargetRef) { - ClassNode typeOrTargetRefType = typeOrTargetRef.getType(); - // GRECLIPSE add -- GROOVY-9762 - if (!isClassExpr(typeOrTargetRef)) { - typeOrTargetRefType = controller.getTypeChooser().resolveType(typeOrTargetRef, controller.getClassNode()); - } - // GRECLIPSE end - List methodNodeList = typeOrTargetRefType.getMethods(methodRefName); - Set dgmMethodNodeSet = findDGMMethodsForClassNode(controller.getSourceUnit().getClassLoader(), typeOrTargetRefType, methodRefName); - - List allMethodNodeList = new LinkedList<>(methodNodeList); - allMethodNodeList.addAll(dgmMethodNodeSet); + private MethodNode findMethodRefMethod(final String methodName, final Parameter[] samParameters, final Expression typeOrTargetRef, final ClassNode typeOrTargetRefType) { + List methods = findVisibleMethods(methodName, typeOrTargetRefType); - ClassNode classNode = controller.getClassNode(); + return chooseMethodRefMethodCandidate(typeOrTargetRef, methods.stream().filter(method -> { + Parameter[] parameters = method.getParameters(); + if (isTypeReferingInstanceMethod(typeOrTargetRef, method)) { + // there is an implicit parameter for "String::length" + ClassNode firstParamType = method.getDeclaringClass(); - List candidates = new LinkedList<>(); - for (MethodNode mn : filterMethodsByVisibility(allMethodNodeList, classNode)) { - Parameter[] parameters = abstractMethodParameters; - if (isTypeReferingInstanceMethod(typeOrTargetRef, mn)) { - if (0 == abstractMethodParameters.length) { - continue; - } + int n = parameters.length; + Parameter[] plusOne = new Parameter[n + 1]; + plusOne[0] = new Parameter(firstParamType, ""); + System.arraycopy(parameters, 0, plusOne, 1, n); - parameters = removeFirstParameter(abstractMethodParameters); + parameters = plusOne; } + return parametersCompatible(samParameters, parameters); + }).collect(Collectors.toList())); + } - Parameter[] methodParameters; - if (isExtensionMethod(mn)) { - methodParameters = removeFirstParameter(((ExtensionMethodNode) mn).getExtensionMethodNode().getParameters()); - } else { - methodParameters = mn.getParameters(); - } + private List findVisibleMethods(final String name, final ClassNode type) { + List methods = type.getMethods(name); + methods.addAll(findDGMMethodsForClassNode(controller.getSourceUnit().getClassLoader(), type, name)); + methods = filterMethodsByVisibility(methods, controller.getClassNode()); + return methods; + } - if (ParameterUtils.parametersCompatible(parameters, methodParameters)) { - candidates.add(mn); - } - } + private void addFatalError(final String msg, final ASTNode node) { + controller.getSourceUnit().addFatalError(msg, node); + } - return chooseMethodRefMethodCandidate(typeOrTargetRef, candidates); + //-------------------------------------------------------------------------- + + private static boolean isConstructorReference(final String methodRefName) { + return "new".equals(methodRefName); } - private static Parameter[] removeFirstParameter(Parameter[] parameters) { - return Arrays.stream(parameters).skip(1).toArray(Parameter[]::new); + private static boolean isExtensionMethod(final MethodNode methodRefMethod) { + return (methodRefMethod instanceof ExtensionMethodNode); } - private static boolean isTypeReferingInstanceMethod(Expression typeOrTargetRef, MethodNode mn) { // class::instanceMethod - return (!mn.isStatic() || (isExtensionMethod(mn) && !((ExtensionMethodNode) mn).isStaticExtension())) - && isClassExpr(typeOrTargetRef); + private static boolean isTypeReferingInstanceMethod(final Expression typeOrTargetRef, final MethodNode mn) { + // class::instanceMethod + return (typeOrTargetRef instanceof ClassExpression) && ((mn != null && !mn.isStatic()) + || (isExtensionMethod(mn) && !((ExtensionMethodNode) mn).isStaticExtension())); + } + + private static Parameter[] removeFirstParameter(final Parameter[] parameters) { + return Arrays.copyOfRange(parameters, 1, parameters.length); } /** * Choose the best method node for method reference. */ - private MethodNode chooseMethodRefMethodCandidate(Expression methodRef, List candidates) { + private static MethodNode chooseMethodRefMethodCandidate(final Expression methodRef, final List candidates) { if (1 == candidates.size()) return candidates.get(0); return candidates.stream() @@ -366,22 +331,19 @@ private MethodNode chooseMethodRefMethodCandidate(Expression methodRef, Listpublic + public static boolean isAssignableTo(ClassNode type, ClassNode toBeAssignedTo) { if (type == toBeAssignedTo || type == UNKNOWN_PARAMETER_TYPE) return true; if (isPrimitiveType(type)) type = getWrapper(type); if (isPrimitiveType(toBeAssignedTo)) toBeAssignedTo = getWrapper(toBeAssignedTo);