From ee8e9e4044cfa3a919a4b7d2d456399fe66f979c Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Mon, 3 May 2021 15:47:40 -0500 Subject: [PATCH] GROOVY-10071, GROOVY-10072 --- .../tests/xform/StaticCompilationTests.java | 46 +- .../core/tests/xform/TypeCheckedTests.java | 8 +- base/org.codehaus.groovy25/.checkstyle | 2 +- .../groovy/classgen/asm/ClosureWriter.java | 2 +- .../asm/sc/StaticTypesClosureWriter.java | 154 +++++++ .../stc/StaticTypeCheckingSupport.java | 7 +- .../stc/StaticTypeCheckingVisitor.java | 74 +++- base/org.codehaus.groovy30/.checkstyle | 2 +- .../groovy/classgen/asm/ClosureWriter.java | 2 +- .../asm/sc/StaticTypesClosureWriter.java | 148 +++++++ ...cTypesMethodReferenceExpressionWriter.java | 2 +- .../stc/StaticTypeCheckingSupport.java | 7 +- .../stc/StaticTypeCheckingVisitor.java | 53 ++- base/org.codehaus.groovy40/.checkstyle | 3 +- .../groovy/classgen/asm/ClosureWriter.java | 419 ++++++++++++++++++ .../asm/sc/StaticTypesClosureWriter.java | 148 +++++++ ...cTypesMethodReferenceExpressionWriter.java | 2 +- .../stc/StaticTypeCheckingSupport.java | 7 +- .../stc/StaticTypeCheckingVisitor.java | 48 +- 19 files changed, 1086 insertions(+), 48 deletions(-) create mode 100644 base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java create mode 100644 base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java create mode 100644 base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/ClosureWriter.java create mode 100644 base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java 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 a552704c99..550252a919 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 @@ -333,7 +333,7 @@ public void testCompileStatic12() { "1. ERROR in Main.groovy (at line 3)\n" + "\tdef list = new LinkedList([1,2,3])\n" + "\t ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + - "Groovy:[Static type checking] - Cannot call java.util.LinkedList#(java.util.Collection) with arguments [java.util.List] \n" + + "Groovy:[Static type checking] - Cannot call java.util.LinkedList#(java.util.Collection) with arguments [java.util.List]\n" + "----------\n"); } @@ -4551,7 +4551,7 @@ public void testCompileStatic9338() { "1. ERROR in Main.groovy (at line 7)\n" + "\tmeth(c)\n" + "\t^^^^^^^\n" + - "Groovy:[Static type checking] - Cannot call Main#meth(java.lang.Class) with arguments [java.lang.Class] \n" + + "Groovy:[Static type checking] - Cannot call Main#meth(java.lang.Class) with arguments [java.lang.Class]\n" + "----------\n"); } @@ -4577,7 +4577,7 @@ public void testCompileStatic9338a() { "1. ERROR in Main.groovy (at line 7)\n" + "\tmeth(c)\n" + "\t^^^^^^^\n" + - "Groovy:[Static type checking] - Cannot call Main#meth(java.lang.Class) with arguments [java.lang.Class] \n" + + "Groovy:[Static type checking] - Cannot call Main#meth(java.lang.Class) with arguments [java.lang.Class]\n" + "----------\n"); } @@ -6166,4 +6166,44 @@ public void testCompileStatic10047() { runConformTest(sources, "[a:1, bc:2, def:3]"); } + + @Test + public void testCompileStatic10071() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.CompileStatic\n" + + "void test() {\n" + + " def c = { ... zeroOrMore -> 'foo' + zeroOrMore }\n" + + " assert c('bar', 'baz') == 'foo[bar, baz]'\n" + + " assert c('bar') == 'foo[bar]'\n" + + " assert c() == 'foo[]'\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources); + } + + @Test + public void testCompileStatic10072() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.CompileStatic\n" + + "void test() {\n" + + " def c = { p = 'foo' -> return p }\n" + + " assert c('bar') == 'bar'\n" + + " assert c() == 'foo'\n" + + " c = { p, q = 'baz' -> '' + p + q }\n" + + " assert c('foo', 'bar') == 'foobar'\n" + + " assert c('foo') == 'foobaz'\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources); + } } diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java index 2c488502d5..66e2e251c3 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java @@ -624,7 +624,7 @@ public void testTypeChecked7945() { "1. ERROR in Test.groovy (at line 12)\n" + "\tsuper(Integer, String)\n" + "\t^^^^^^^^^^^^^^^^^^^^^^\n" + - "Groovy:[Static type checking] - Cannot call A#(java.lang.Class, java.lang.Class) with arguments [java.lang.Class, java.lang.Class] \n" + + "Groovy:[Static type checking] - Cannot call A#(java.lang.Class, java.lang.Class) with arguments [java.lang.Class, java.lang.Class]\n" + "----------\n"); } @@ -1375,17 +1375,17 @@ public void testTypeChecked9902() { "1. ERROR in Main.groovy (at line 19)\n" + "\th.stringProperty.eq(\"${0}\")\n" + "\t^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + - "Groovy:[Static type checking] - Cannot call TypedProperty#eq(java.lang.String) with arguments [groovy.lang.GString] \n" + + "Groovy:[Static type checking] - Cannot call TypedProperty#eq(java.lang.String) with arguments [groovy.lang.GString]\n" + "----------\n" + "2. ERROR in Main.groovy (at line 21)\n" + "\tstringProperty.eq(1234)\n" + "\t^^^^^^^^^^^^^^^^^^^^^^^\n" + - "Groovy:[Static type checking] - Cannot call TypedProperty#eq(java.lang.String) with arguments [int] \n" + + "Groovy:[Static type checking] - Cannot call TypedProperty#eq(java.lang.String) with arguments [int]\n" + "----------\n" + "3. ERROR in Main.groovy (at line 22)\n" + "\tnumberProperty.eq('xx')\n" + "\t^^^^^^^^^^^^^^^^^^^^^^^\n" + - "Groovy:[Static type checking] - Cannot call TypedProperty#eq(java.lang.Number) with arguments [java.lang.String] \n" + + "Groovy:[Static type checking] - Cannot call TypedProperty#eq(java.lang.Number) with arguments [java.lang.String]\n" + "----------\n"); } diff --git a/base/org.codehaus.groovy25/.checkstyle b/base/org.codehaus.groovy25/.checkstyle index b15325dd63..ff9df09a2a 100644 --- a/base/org.codehaus.groovy25/.checkstyle +++ b/base/org.codehaus.groovy25/.checkstyle @@ -54,7 +54,7 @@ - + diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/ClosureWriter.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/ClosureWriter.java index 461dec32f0..e9fc051deb 100644 --- a/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/ClosureWriter.java +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/ClosureWriter.java @@ -198,7 +198,7 @@ protected ClassNode createClosureClass(ClosureExpression expression, int mods) { parameters = Parameter.EMPTY_ARRAY; } else if (parameters.length == 0) { // let's create a default 'it' parameter - Parameter it = new Parameter(ClassHelper.OBJECT_TYPE, "it", ConstantExpression.NULL); + Parameter it = new Parameter(ClassHelper.OBJECT_TYPE, "it", new ConstantExpression(null)); parameters = new Parameter[]{it}; Variable ref = expression.getVariableScope().getDeclaredVariable("it"); if (ref!=null) it.setClosureSharedVariable(ref.isClosureSharedVariable()); diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java new file mode 100644 index 0000000000..0a81218fcb --- /dev/null +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java @@ -0,0 +1,154 @@ +/* + * 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.MethodNode; +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.ClosureExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.ast.stmt.ReturnStatement; +import org.codehaus.groovy.classgen.asm.ClosureWriter; +import org.codehaus.groovy.classgen.asm.WriterController; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys; +import org.codehaus.groovy.transform.stc.StaticTypesMarker; +import groovyjarjarasm.asm.Opcodes; + +import java.util.Collections; +import java.util.List; + +/** + * Writer responsible for generating closure classes in statically compiled mode. + */ +public class StaticTypesClosureWriter extends ClosureWriter { + public StaticTypesClosureWriter(WriterController wc) { + super(wc); + } + + @Override + protected ClassNode createClosureClass(final ClosureExpression expression, final int mods) { + ClassNode closureClass = super.createClosureClass(expression, mods); + List methods = closureClass.getDeclaredMethods("call"); + List doCall = closureClass.getMethods("doCall"); + if (doCall.size() != 1) { + throw new GroovyBugError("Expected to find one (1) doCall method on generated closure, but found " + doCall.size()); + } + MethodNode doCallMethod = doCall.get(0); + if (methods.isEmpty() && doCallMethod.getParameters().length == 1) { + createDirectCallMethod(closureClass, doCallMethod); + } + MethodTargetCompletionVisitor visitor = new MethodTargetCompletionVisitor(doCallMethod); + Object dynamic = expression.getNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION); + if (dynamic != null) { + doCallMethod.putNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION, dynamic); + } + for (MethodNode method : methods) { + visitor.visitMethod(method); + } + closureClass.putNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE, Boolean.TRUE); + return closureClass; + } + + private static void createDirectCallMethod(final ClassNode closureClass, final MethodNode doCallMethod) { + // in case there is no "call" method on the closure, we can create a "fast invocation" paths + // to avoid going through ClosureMetaClass by call(Object...) method + + // we can't have a specialized version of call(Object...) because the dispatch logic in ClosureMetaClass + // is too complex! + + // call(Object) + /* GRECLIPSE edit -- GROOVY-10071 + Parameter args = new Parameter(ClassHelper.OBJECT_TYPE, "args"); + */ + Parameter doCallParam = doCallMethod.getParameters()[0]; + Parameter args = new Parameter(doCallParam.getType(), "args"); + // GRECLIPSE end + MethodCallExpression doCall1arg = new MethodCallExpression( + new VariableExpression("this", closureClass), + "doCall", + new ArgumentListExpression(new VariableExpression(args)) + ); + doCall1arg.setImplicitThis(true); + doCall1arg.setMethodTarget(doCallMethod); + closureClass.addMethod( + new MethodNode("call", + Opcodes.ACC_PUBLIC, + ClassHelper.OBJECT_TYPE, + new Parameter[]{args}, + ClassNode.EMPTY_ARRAY, + new ReturnStatement(doCall1arg))); + + // call() + /* GRECLIPSE edit -- GROOVY-10071, GROOVY-10072 + MethodCallExpression doCallNoArgs = new MethodCallExpression(new VariableExpression("this", closureClass), "doCall", new ArgumentListExpression(new ConstantExpression(null))); + */ + Expression argument; + if (doCallParam.hasInitialExpression()) { + argument = doCallParam.getInitialExpression(); + } else if (doCallParam.getType().isArray()) { + ClassNode elementType = doCallParam.getType().getComponentType(); + argument = new ArrayExpression(elementType, null, Collections.singletonList(new ConstantExpression(0, true))); + } else { + argument = new ConstantExpression(null); + } + MethodCallExpression doCallNoArgs = new MethodCallExpression(new VariableExpression("this", closureClass), "doCall", new ArgumentListExpression(argument)); + // GRECLIPSE end + doCallNoArgs.setImplicitThis(true); + doCallNoArgs.setMethodTarget(doCallMethod); + closureClass.addMethod( + new MethodNode("call", + Opcodes.ACC_PUBLIC, + ClassHelper.OBJECT_TYPE, + Parameter.EMPTY_ARRAY, + ClassNode.EMPTY_ARRAY, + new ReturnStatement(doCallNoArgs))); + } + + private static final class MethodTargetCompletionVisitor extends ClassCodeVisitorSupport { + + private final MethodNode doCallMethod; + + private MethodTargetCompletionVisitor(final MethodNode doCallMethod) { + this.doCallMethod = doCallMethod; + } + + @Override + protected SourceUnit getSourceUnit() { + return null; + } + + @Override + public void visitMethodCallExpression(final MethodCallExpression call) { + super.visitMethodCallExpression(call); + MethodNode mn = call.getMethodTarget(); + if (mn == null) { + call.setMethodTarget(doCallMethod); + } + } + } +} diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java index 98c2ae9d47..65a3e7358c 100644 --- a/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java @@ -443,12 +443,15 @@ static int excessArgumentsMatchesVargsParameter(Parameter[] params, ClassNode[] */ static int lastArgMatchesVarg(Parameter[] params, ClassNode... args) { if (!isVargs(params)) return -1; - // case length ==0 handled already + // GRECLIPSE add -- GROOVY-10071 + int lastParamIndex = params.length - 1; + if (lastParamIndex == args.length) return 0; + // GRECLIPSE end // we have now two cases, // the argument is wrapped in the vargs array or // the argument is an array that can be used for the vargs part directly // we test only the wrapping part, since the non wrapping is done already - ClassNode lastParamType = params[params.length - 1].getType(); + ClassNode lastParamType = params[lastParamIndex].getType(); ClassNode ptype = lastParamType.getComponentType(); ClassNode arg = args[args.length - 1]; if (isNumberType(ptype) && isNumberType(arg) && !getWrapper(ptype).equals(getWrapper(arg))) return -1; diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index 99ffcdaae1..59d7ff1f58 100644 --- a/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -131,6 +131,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.StringJoiner; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiPredicate; @@ -230,7 +231,7 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.NUMBER_OPS; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.UNKNOWN_PARAMETER_TYPE; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.addMethodLevelDeclaredGenerics; -import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.allParametersAndArgumentsMatch; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.allParametersAndArgumentsMatchWithDefaultParams; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsConnections; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsContext; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsContextToParameterClass; @@ -2201,26 +2202,11 @@ public void visitField(final FieldNode node) { try { typeCheckingContext.isInStaticContext = node.isInStaticContext(); currentField = node; - /* GRECLIPSE edit -- GROOVY-9977 + /* GRECLIPSE edit -- GROOVY-9882, GROOVY-9977, GROOVY-9995 super.visitField(node); - */ visitAnnotations(node); - // GRECLIPSE end Expression init = node.getInitialExpression(); if (init != null) { - // GRECLIPSE add -- GROOVY-9977, GROOVY-9995 - ClassNode lType = getType(node); - if (isFunctionalInterface(lType)) { - processFunctionalInterfaceAssignment(lType, init); - } - init.visit(this); - ClassNode rType = getType(init); - if (init instanceof ConstructorCallExpression) { - inferDiamondType((ConstructorCallExpression) init, lType); - } else if (isClosureWithType(lType) && init instanceof ClosureExpression) { - storeInferredReturnType(init, getCombinedBoundType(lType.getGenericsTypes()[0])); - } - // GRECLIPSE end FieldExpression left = new FieldExpression(node); BinaryExpression bexp = binX( left, @@ -2228,22 +2214,48 @@ public void visitField(final FieldNode node) { init ); bexp.setSourcePosition(init); - /* GRECLIPSE edit -- GROOVY-9882 typeCheckAssignment(bexp, left, node.getOriginType(), init, getType(init)); ClassNode lType = node.getOriginType(), rType = getType(init); if (init instanceof ConstructorCallExpression) { inferDiamondType((ConstructorCallExpression) init, node.getOriginType()); } - */ - typeCheckAssignment(bexp, left, lType, init, getResultType(lType, ASSIGN, rType, bexp)); - // GRECLIPSE end } + */ + visitAnnotations(node); + visitInitialExpression(node.getInitialExpression(), new FieldExpression(node), node); + // GRECLIPSE end } finally { currentField = null; typeCheckingContext.isInStaticContext = osc; } } + // GRECLIPSE add + private void visitInitialExpression(final Expression value, final Expression target, final ASTNode position) { + if (value != null) { + ClassNode lType = target.getType(); + if (isFunctionalInterface(lType)) { + processFunctionalInterfaceAssignment(lType, value); + } else if (isClosureWithType(lType) && value instanceof ClosureExpression) { + storeInferredReturnType(value, getCombinedBoundType(lType.getGenericsTypes()[0])); + } + value.visit(this); + ClassNode rType = getType(value); + if (value instanceof ConstructorCallExpression) { + inferDiamondType((ConstructorCallExpression) value, lType); + } + + BinaryExpression bexp = binX( + target, + Token.newSymbol("=", position.getLineNumber(), position.getColumnNumber()), + value + ); + bexp.setSourcePosition(position); + typeCheckAssignment(bexp, target, lType, value, getResultType(lType, ASSIGN, rType, bexp)); + } + } + // GRECLIPSE end + @Override public void visitForLoop(final ForStatement forLoop) { // collect every variable expression used in the loop body @@ -2887,6 +2899,9 @@ public void visitClosureExpression(final ClosureExpression expression) { typeCheckingContext.isInStaticContext = oldStaticContext; for (Parameter parameter : getParametersSafe(expression)) { typeCheckingContext.controlStructureVariables.remove(parameter); + // GRECLIPSE add -- GROOVY-10072: visit param default argument expression if present + visitInitialExpression(parameter.getInitialExpression(), varX(parameter), parameter); + // GRECLIPSE end } } @@ -3918,6 +3933,9 @@ public void visitMethodCallExpression(MethodCallExpression call) { } else if (objectExpression instanceof ClosureExpression) { // we can get actual parameters directly Parameter[] parameters = ((ClosureExpression) objectExpression).getParameters(); + // GRECLIPSE add + if (parameters != null) + // GRECLIPSE end typeCheckClosureCall(callArguments, args, parameters); ClassNode data = getInferredReturnType(objectExpression); if (data != null) { @@ -4380,6 +4398,7 @@ protected boolean isClosureCall(final String name, final Expression objectExpres } protected void typeCheckClosureCall(final Expression callArguments, final ClassNode[] args, final Parameter[] parameters) { + /* GRECLIPSE edit -- GROOVY-10071, GROOVY-10072 if (allParametersAndArgumentsMatch(parameters, args) < 0 && lastArgMatchesVarg(parameters, args) < 0) { StringBuilder sb = new StringBuilder("["); @@ -4391,6 +4410,11 @@ protected void typeCheckClosureCall(final Expression callArguments, final ClassN sb.append("]"); addStaticTypeError("Closure argument types: " + sb + " do not match with parameter types: " + formatArgumentList(args), callArguments); } + */ + if (allParametersAndArgumentsMatchWithDefaultParams(parameters, args) < 0 && lastArgMatchesVarg(parameters, args) < 0) { + addStaticTypeError("Cannot call closure that accepts " + formatArgumentList(extractTypesFromParameters(parameters)) + " with " + formatArgumentList(args), callArguments); + } + // GRECLIPSE end } @Override @@ -6379,6 +6403,7 @@ private static String toMethodGenericTypesString(MethodNode node) { protected static String formatArgumentList(ClassNode[] nodes) { if (nodes == null || nodes.length == 0) return "[]"; + /* GRECLIPSE edit StringBuilder sb = new StringBuilder(24 * nodes.length); sb.append("["); for (ClassNode node : nodes) { @@ -6389,6 +6414,13 @@ protected static String formatArgumentList(ClassNode[] nodes) { sb.setCharAt(sb.length() - 2, ']'); } return sb.toString(); + */ + StringJoiner joiner = new StringJoiner(", ", "[", "]"); + for (ClassNode node : nodes) { + joiner.add(prettyPrintType(node)); + } + return joiner.toString(); + // GRECLIPSE end } private static void putSetterInfo(Expression exp, SetterInfo info) { diff --git a/base/org.codehaus.groovy30/.checkstyle b/base/org.codehaus.groovy30/.checkstyle index 44e204ca8f..326aa701ed 100644 --- a/base/org.codehaus.groovy30/.checkstyle +++ b/base/org.codehaus.groovy30/.checkstyle @@ -50,7 +50,7 @@ - + diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/ClosureWriter.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/ClosureWriter.java index efca03631a..87857cc997 100644 --- a/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/ClosureWriter.java +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/ClosureWriter.java @@ -197,7 +197,7 @@ protected ClassNode createClosureClass(final ClosureExpression expression, final parameters = Parameter.EMPTY_ARRAY; } else if (parameters.length == 0) { // let's create a default 'it' parameter - Parameter it = param(ClassHelper.OBJECT_TYPE, "it", ConstantExpression.NULL); + Parameter it = param(ClassHelper.OBJECT_TYPE, "it", new ConstantExpression(null)); parameters = new Parameter[]{it}; Variable ref = expression.getVariableScope().getDeclaredVariable("it"); if (ref != null) it.setClosureSharedVariable(ref.isClosureSharedVariable()); diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java new file mode 100644 index 0000000000..53aa164f6c --- /dev/null +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java @@ -0,0 +1,148 @@ +/* + * 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.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.expr.ArrayExpression; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.classgen.asm.ClosureWriter; +import org.codehaus.groovy.classgen.asm.WriterController; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys; +import org.codehaus.groovy.transform.stc.StaticTypesMarker; +import groovyjarjarasm.asm.Opcodes; + +import java.util.Collections; +import java.util.List; + +import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod; +import static org.codehaus.groovy.ast.tools.GeneralUtils.args; +import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; + +/** + * Writer responsible for generating closure classes in statically compiled mode. + */ +public class StaticTypesClosureWriter extends ClosureWriter { + public StaticTypesClosureWriter(WriterController wc) { + super(wc); + } + + @Override + protected ClassNode createClosureClass(final ClosureExpression expression, final int mods) { + ClassNode closureClass = super.createClosureClass(expression, mods); + List methods = closureClass.getDeclaredMethods("call"); + List doCall = closureClass.getMethods("doCall"); + if (doCall.size() != 1) { + throw new GroovyBugError("Expected to find one (1) doCall method on generated closure, but found " + doCall.size()); + } + MethodNode doCallMethod = doCall.get(0); + if (methods.isEmpty() && doCallMethod.getParameters().length == 1) { + createDirectCallMethod(closureClass, doCallMethod); + } + MethodTargetCompletionVisitor visitor = new MethodTargetCompletionVisitor(doCallMethod); + Object dynamic = expression.getNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION); + if (dynamic != null) { + doCallMethod.putNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION, dynamic); + } + for (MethodNode method : methods) { + visitor.visitMethod(method); + } + closureClass.putNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE, Boolean.TRUE); + return closureClass; + } + + private static void createDirectCallMethod(final ClassNode closureClass, final MethodNode doCallMethod) { + // in case there is no "call" method on the closure, we can create a "fast invocation" paths + // to avoid going through ClosureMetaClass by call(Object...) method + + // we can't have a specialized version of call(Object...) because the dispatch logic in ClosureMetaClass + // is too complex! + + // call(Object) + /* GRECLIPSE edit -- GROOVY-10071 + Parameter args = param(ClassHelper.OBJECT_TYPE, "args"); + */ + Parameter doCallParam = doCallMethod.getParameters()[0]; + Parameter args = new Parameter(doCallParam.getType(), "args"); + // GRECLIPSE end + addGeneratedCallMethod(closureClass, doCallMethod, varX(args), new Parameter[]{args}); + + // call() + /* GRECLIPSE edit -- GROOVY-10071, GROOVY-10072 + addGeneratedCallMethod(closureClass, doCallMethod, constX(null), Parameter.EMPTY_ARRAY); + */ + Expression argument; + if (doCallParam.hasInitialExpression()) { + argument = doCallParam.getInitialExpression(); + } else if (doCallParam.getType().isArray()) { + ClassNode elementType = doCallParam.getType().getComponentType(); + argument = new ArrayExpression(elementType, null, Collections.singletonList(constX(0, true))); + } else { + argument = constX(null); + } + addGeneratedCallMethod(closureClass, doCallMethod, argument, Parameter.EMPTY_ARRAY); + // GRECLIPSE end + } + + private static void addGeneratedCallMethod(ClassNode closureClass, MethodNode doCallMethod, Expression expression, Parameter[] params) { + MethodCallExpression doCallarg = callX(varX("this", closureClass), "doCall", args(expression)); + doCallarg.setImplicitThis(true); + doCallarg.setMethodTarget(doCallMethod); + MethodNode call = new MethodNode("call", + Opcodes.ACC_PUBLIC, + ClassHelper.OBJECT_TYPE, + params, + ClassNode.EMPTY_ARRAY, + returnS(doCallarg)); + addGeneratedMethod(closureClass, call, true); + } + + private static final class MethodTargetCompletionVisitor extends ClassCodeVisitorSupport { + + private final MethodNode doCallMethod; + + private MethodTargetCompletionVisitor(final MethodNode doCallMethod) { + this.doCallMethod = doCallMethod; + } + + @Override + protected SourceUnit getSourceUnit() { + return null; + } + + @Override + public void visitMethodCallExpression(final MethodCallExpression call) { + super.visitMethodCallExpression(call); + MethodNode mn = call.getMethodTarget(); + if (mn == null) { + call.setMethodTarget(doCallMethod); + } + } + } +} 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 98fbefc39a..40a8598d8e 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 @@ -180,7 +180,7 @@ private void validate(final MethodReferenceExpression methodReferenceExpression, private MethodNode addSyntheticMethodForDGSM(final MethodNode mn) { Parameter[] parameters = removeFirstParameter(mn.getParameters()); ArgumentListExpression args = args(parameters); - args.getExpressions().add(0, ConstantExpression.NULL); + args.getExpressions().add(0, new ConstantExpression(null)); MethodNode syntheticMethodNode = controller.getClassNode().addSyntheticMethod( "dgsm$$" + mn.getParameters()[0].getType().getName().replace(".", "$") + "$$" + mn.getName(), diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java index d128dabd5b..7b413c4190 100644 --- a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java @@ -430,12 +430,15 @@ static int excessArgumentsMatchesVargsParameter(final Parameter[] parameters, fi */ static int lastArgMatchesVarg(final Parameter[] parameters, final ClassNode... argumentTypes) { if (!isVargs(parameters)) return -1; - // case length ==0 handled already + // GRECLIPSE add -- GROOVY-10071 + int lastParamIndex = parameters.length - 1; + if (lastParamIndex == argumentTypes.length) return 0; + // GRECLIPSE end // we have now two cases, // the argument is wrapped in the vargs array or // the argument is an array that can be used for the vargs part directly // we test only the wrapping part, since the non wrapping is done already - ClassNode lastParamType = parameters[parameters.length - 1].getType(); + ClassNode lastParamType = parameters[lastParamIndex].getType(); ClassNode ptype = lastParamType.getComponentType(); ClassNode arg = argumentTypes[argumentTypes.length - 1]; if (isNumberType(ptype) && isNumberType(arg) && !getWrapper(ptype).equals(getWrapper(arg))) return -1; diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index cdd054d81e..1e8601dde0 100644 --- a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -130,6 +130,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.StringJoiner; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiPredicate; @@ -235,7 +236,7 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.NUMBER_OPS; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.UNKNOWN_PARAMETER_TYPE; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.addMethodLevelDeclaredGenerics; -import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.allParametersAndArgumentsMatch; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.allParametersAndArgumentsMatchWithDefaultParams; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsConnections; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsContext; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsContextToParameterClass; @@ -2023,17 +2024,13 @@ public void visitField(final FieldNode node) { typeCheckingContext.isInStaticContext = node.isInStaticContext(); currentField = node; visitAnnotations(node); + /* GRECLIPSE edit -- GROOVY-9995 Expression init = node.getInitialExpression(); if (init != null) { ClassNode lType = getType(node); if (isFunctionalInterface(lType)) { // GROOVY-9977 processFunctionalInterfaceAssignment(lType, init); } - // GRECLIPSE add -- GROOVY-9995 - else if (isClosureWithType(lType) && init instanceof ClosureExpression) { - storeInferredReturnType(init, getCombinedBoundType(lType.getGenericsTypes()[0])); - } - // GRECLIPSE end init.visit(this); ClassNode rType = getType(init); if (init instanceof ConstructorCallExpression) { @@ -2044,12 +2041,36 @@ else if (isClosureWithType(lType) && init instanceof ClosureExpression) { BinaryExpression bexp = assignX(left, init, node); typeCheckAssignment(bexp, left, lType, init, getResultType(lType, ASSIGN, rType, bexp)); } + */ + visitInitialExpression(node.getInitialExpression(), new FieldExpression(node), node); + // GRECLIPSE end } finally { currentField = null; typeCheckingContext.isInStaticContext = osc; } } + // GRECLIPSE add + private void visitInitialExpression(final Expression value, final Expression target, final ASTNode position) { + if (value != null) { + ClassNode lType = target.getType(); + if (isFunctionalInterface(lType)) { // GROOVY-9977 + processFunctionalInterfaceAssignment(lType, value); + } else if (isClosureWithType(lType) && value instanceof ClosureExpression) { + storeInferredReturnType(value, getCombinedBoundType(lType.getGenericsTypes()[0])); + } + value.visit(this); + ClassNode rType = getType(value); + if (value instanceof ConstructorCallExpression) { + inferDiamondType((ConstructorCallExpression) value, lType); + } + + BinaryExpression bexp = assignX(target, value, position); + typeCheckAssignment(bexp, target, lType, value, getResultType(lType, ASSIGN, rType, bexp)); + } + } + // GRECLIPSE end + @Override public void visitForLoop(final ForStatement forLoop) { // collect every variable expression used in the loop body @@ -2577,6 +2598,9 @@ public void visitClosureExpression(final ClosureExpression expression) { typeCheckingContext.isInStaticContext = oldStaticContext; for (Parameter parameter : getParametersSafe(expression)) { typeCheckingContext.controlStructureVariables.remove(parameter); + // GRECLIPSE add -- GROOVY-10072: visit param default argument expression if present + visitInitialExpression(parameter.getInitialExpression(), varX(parameter), parameter); + // GRECLIPSE end } } @@ -3622,6 +3646,9 @@ public void visitMethodCallExpression(final MethodCallExpression call) { } else if (objectExpression instanceof ClosureExpression) { // we can get actual parameters directly Parameter[] parameters = ((ClosureExpression) objectExpression).getParameters(); + // GRECLIPSE add + if (parameters != null) + // GRECLIPSE end typeCheckClosureCall(callArguments, args, parameters); ClassNode data = getInferredReturnType(objectExpression); if (data != null) { @@ -4107,6 +4134,7 @@ protected boolean isClosureCall(final String name, final Expression objectExpres } protected void typeCheckClosureCall(final Expression callArguments, final ClassNode[] args, final Parameter[] parameters) { + /* GRECLIPSE edit -- GROOVY-10071, GROOVY-10072 if (allParametersAndArgumentsMatch(parameters, args) < 0 && lastArgMatchesVarg(parameters, args) < 0) { StringBuilder sb = new StringBuilder("["); @@ -4118,6 +4146,11 @@ protected void typeCheckClosureCall(final Expression callArguments, final ClassN sb.append("]"); addStaticTypeError("Closure argument types: " + sb + " do not match with parameter types: " + formatArgumentList(args), callArguments); } + */ + if (allParametersAndArgumentsMatchWithDefaultParams(parameters, args) < 0 && lastArgMatchesVarg(parameters, args) < 0) { + addStaticTypeError("Cannot call closure that accepts " + formatArgumentList(extractTypesFromParameters(parameters)) + " with " + formatArgumentList(args), callArguments); + } + // GRECLIPSE end } @Override @@ -6063,6 +6096,7 @@ private static String toMethodGenericTypesString(final MethodNode node) { protected static String formatArgumentList(final ClassNode[] nodes) { if (nodes == null || nodes.length == 0) return "[]"; + /* GRECLIPSE edit StringBuilder sb = new StringBuilder(24 * nodes.length); sb.append('['); for (ClassNode node : nodes) { @@ -6073,6 +6107,13 @@ protected static String formatArgumentList(final ClassNode[] nodes) { sb.setCharAt(sb.length() - 2, ']'); } return sb.toString(); + */ + StringJoiner joiner = new StringJoiner(", ", "[", "]"); + for (ClassNode node : nodes) { + joiner.add(prettyPrintType(node)); + } + return joiner.toString(); + // GRECLIPSE end } private static void putSetterInfo(final Expression exp, final SetterInfo info) { diff --git a/base/org.codehaus.groovy40/.checkstyle b/base/org.codehaus.groovy40/.checkstyle index 27e287491b..f246968d8f 100644 --- a/base/org.codehaus.groovy40/.checkstyle +++ b/base/org.codehaus.groovy40/.checkstyle @@ -38,9 +38,10 @@ + - + diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/ClosureWriter.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/ClosureWriter.java new file mode 100644 index 0000000000..de6e076ec6 --- /dev/null +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/ClosureWriter.java @@ -0,0 +1,419 @@ +/* + * 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.GroovyBugError; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.CodeVisitorSupport; +import org.codehaus.groovy.ast.ConstructorNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.InnerClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.Variable; +import org.codehaus.groovy.ast.VariableScope; +import org.codehaus.groovy.ast.expr.ClassExpression; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.ConstructorCallExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.FieldExpression; +import org.codehaus.groovy.ast.expr.TupleExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.tools.GenericsUtils; +import org.codehaus.groovy.classgen.AsmClassGenerator; +import org.codehaus.groovy.classgen.Verifier; +import groovyjarjarasm.asm.MethodVisitor; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import static org.apache.groovy.ast.tools.AnnotatedNodeUtils.markAsGenerated; +import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod; +import static org.codehaus.groovy.ast.tools.GeneralUtils.args; +import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorSuperX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.param; +import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt; +import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; +import static org.codehaus.groovy.transform.stc.StaticTypesMarker.INFERRED_RETURN_TYPE; +import static groovyjarjarasm.asm.Opcodes.ACC_FINAL; +import static groovyjarjarasm.asm.Opcodes.ACC_PRIVATE; +import static groovyjarjarasm.asm.Opcodes.ACC_PUBLIC; +import static groovyjarjarasm.asm.Opcodes.ACC_STATIC; +import static groovyjarjarasm.asm.Opcodes.ACC_SYNTHETIC; +import static groovyjarjarasm.asm.Opcodes.ALOAD; +import static groovyjarjarasm.asm.Opcodes.DUP; +import static groovyjarjarasm.asm.Opcodes.GETFIELD; +import static groovyjarjarasm.asm.Opcodes.INVOKESPECIAL; +import static groovyjarjarasm.asm.Opcodes.INVOKEVIRTUAL; +import static groovyjarjarasm.asm.Opcodes.NEW; + +public class ClosureWriter { + + public static final String OUTER_INSTANCE = "_outerInstance"; + public static final String THIS_OBJECT = "_thisObject"; + + protected interface UseExistingReference { + } + + protected final WriterController controller; + private final Map closureClasses = new HashMap<>(); + + public ClosureWriter(final WriterController controller) { + this.controller = controller; + } + + public void writeClosure(final ClosureExpression expression) { + CompileStack compileStack = controller.getCompileStack(); + MethodVisitor mv = controller.getMethodVisitor(); + ClassNode classNode = controller.getClassNode(); + AsmClassGenerator acg = controller.getAcg(); + + // generate closure as public class to make sure it can be properly invoked by classes of the + // Groovy runtime without circumventing JVM access checks (see CachedMethod for example). + int mods = ACC_PUBLIC | ACC_FINAL; + if (classNode.isInterface()) { + mods |= ACC_STATIC; + } + ClassNode closureClass = getOrAddClosureClass(expression, mods); + String closureClassinternalName = BytecodeHelper.getClassInternalName(closureClass); + List constructors = closureClass.getDeclaredConstructors(); + ConstructorNode node = constructors.get(0); + + Parameter[] localVariableParams = node.getParameters(); + + mv.visitTypeInsn(NEW, closureClassinternalName); + mv.visitInsn(DUP); + if (controller.isStaticMethod() || compileStack.isInSpecialConstructorCall()) { + (new ClassExpression(classNode)).visit(acg); + (new ClassExpression(controller.getOutermostClass())).visit(acg); + } else { + mv.visitVarInsn(ALOAD, 0); + controller.getOperandStack().push(ClassHelper.OBJECT_TYPE); + loadThis(); + } + + // now let's load the various parameters we're passing + // we start at index 2 because the first variable we pass + // is the owner instance and at this point it is already + // on the stack + for (int i = 2; i < localVariableParams.length; i++) { + Parameter param = localVariableParams[i]; + String name = param.getName(); + loadReference(name, controller); + if (param.getNodeMetaData(ClosureWriter.UseExistingReference.class) == null) { + param.setNodeMetaData(ClosureWriter.UseExistingReference.class, Boolean.TRUE); + } + } + + // we may need to pass in some other constructors + //cv.visitMethodInsn(INVOKESPECIAL, innerClassinternalName, "", prototype + ")V"); + mv.visitMethodInsn(INVOKESPECIAL, closureClassinternalName, "", BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, localVariableParams), false); + controller.getOperandStack().replace(ClassHelper.CLOSURE_TYPE, localVariableParams.length); + } + + public static void loadReference(final String name, final WriterController controller) { + CompileStack compileStack = controller.getCompileStack(); + MethodVisitor mv = controller.getMethodVisitor(); + ClassNode classNode = controller.getClassNode(); + AsmClassGenerator acg = controller.getAcg(); + + // compileStack.containsVariable(name) means to ask if the variable is already declared + // compileStack.getScope().isReferencedClassVariable(name) means to ask if the variable is a field + // If it is no field and is not yet declared, then it is either a closure shared variable or + // an already declared variable. + if (!compileStack.containsVariable(name) && compileStack.getScope().isReferencedClassVariable(name)) { + acg.visitFieldExpression(new FieldExpression(classNode.getDeclaredField(name))); + } else { + BytecodeVariable v = compileStack.getVariable(name, !classNodeUsesReferences(controller.getClassNode())); + if (v == null) { + // variable is not on stack because we are + // inside a nested Closure and this variable + // was not used before + // then load it from the Closure field + FieldNode field = classNode.getDeclaredField(name); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, controller.getInternalClassName(), name, BytecodeHelper.getTypeDescription(field.getType())); + } else { + mv.visitVarInsn(ALOAD, v.getIndex()); + } + controller.getOperandStack().push(ClassHelper.REFERENCE_TYPE); + } + } + + public ClassNode getOrAddClosureClass(final ClosureExpression expression, final int modifiers) { + ClassNode closureClass = closureClasses.get(expression); + if (closureClass == null) { + closureClass = createClosureClass(expression, modifiers); + closureClasses.put(expression, closureClass); + controller.getAcg().addInnerClass(closureClass); + closureClass.addInterface(ClassHelper.GENERATED_CLOSURE_Type); + closureClass.putNodeMetaData(WriterControllerFactory.class, (WriterControllerFactory) x -> controller); + } + return closureClass; + } + + private static boolean classNodeUsesReferences(final ClassNode classNode) { + boolean ret = classNode.getSuperClass() == ClassHelper.CLOSURE_TYPE; + if (ret) return ret; + if (classNode instanceof InnerClassNode) { + InnerClassNode inner = (InnerClassNode) classNode; + return inner.isAnonymous(); + } + return false; + } + + protected ClassNode createClosureClass(final ClosureExpression expression, final int modifiers) { + ClassNode classNode = controller.getClassNode(); + ClassNode outerClass = controller.getOutermostClass(); + String name = genClosureClassName(); + + Parameter[] parameters = expression.getParameters(); + if (parameters == null) { + parameters = Parameter.EMPTY_ARRAY; + } else if (parameters.length == 0) { + // let's create a default 'it' parameter + Parameter it = param(ClassHelper.OBJECT_TYPE, "it", new ConstantExpression(null)); + parameters = new Parameter[]{it}; + Variable ref = expression.getVariableScope().getDeclaredVariable("it"); + if (ref != null) it.setClosureSharedVariable(ref.isClosureSharedVariable()); + } + + Parameter[] localVariableParams = getClosureSharedVariables(expression); + removeInitialValues(localVariableParams); + + // GROOVY-9971: closure return type is mapped to Groovy cast by classgen + ClassNode returnType = expression.getNodeMetaData(INFERRED_RETURN_TYPE); + if (returnType == null) returnType = ClassHelper.OBJECT_TYPE; // not STC or unknown path + else if (returnType.isPrimaryClassNode()) returnType = returnType.getPlainNodeReference(); + else if (ClassHelper.isPrimitiveType(returnType)) returnType = ClassHelper.getWrapper(returnType); + else if (GenericsUtils.hasUnresolvedGenerics(returnType)) returnType = GenericsUtils.nonGeneric(returnType); + + InnerClassNode answer = new InnerClassNode(classNode, name, modifiers, ClassHelper.CLOSURE_TYPE.getPlainNodeReference()); + answer.setEnclosingMethod(controller.getMethodNode()); + answer.setScriptBody(controller.isInScriptBody()); + answer.setSourcePosition(expression); + answer.setStaticClass(controller.isStaticMethod() || classNode.isStaticClass()); + answer.setSynthetic(true); + answer.setUsingGenerics(outerClass.isUsingGenerics()); + + MethodNode method = answer.addMethod("doCall", ACC_PUBLIC, returnType, parameters, ClassNode.EMPTY_ARRAY, expression.getCode()); + method.setSourcePosition(expression); + + VariableScope varScope = expression.getVariableScope(); + if (varScope == null) { + throw new RuntimeException( + "Must have a VariableScope by now! for expression: " + expression + " class: " + name); + } else { + method.setVariableScope(varScope.copy()); + } + if (parameters.length > 1 + || (parameters.length == 1 + && parameters[0].getType() != null + && !ClassHelper.OBJECT_TYPE.equals(parameters[0].getType()) + && !ClassHelper.OBJECT_TYPE.equals(parameters[0].getType().getComponentType()))) { + // let's add a typesafe call method + MethodNode call = new MethodNode( + "call", + ACC_PUBLIC, + returnType, + parameters, + ClassNode.EMPTY_ARRAY, + returnS(callThisX("doCall", args(parameters)))); + addGeneratedMethod(answer, call, true); + call.setSourcePosition(expression); + } + + // let's make the constructor + BlockStatement block = createBlockStatementForConstructor(expression, outerClass, classNode); + + // 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(final ClosureExpression expression, final Parameter[] localVariableParams, final InnerClassNode answer, final BlockStatement block) { + Parameter[] params = new Parameter[2 + localVariableParams.length]; + params[0] = param(ClassHelper.OBJECT_TYPE, OUTER_INSTANCE); + params[1] = param(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(final InnerClassNode answer, final Parameter[] localVariableParams) { + for (Parameter param : localVariableParams) { + String paramName = param.getName(); + ClassNode type = param.getType(); + VariableExpression initialValue = varX(paramName); + initialValue.setAccessedVariable(param); + initialValue.setUseReferenceDirectly(true); + ClassNode realType = type; + type = ClassHelper.makeReference(); + param.setType(ClassHelper.makeReference()); + FieldNode paramField = answer.addField(paramName, ACC_PRIVATE | ACC_SYNTHETIC, type, initialValue); + paramField.setOriginType(ClassHelper.getWrapper(param.getOriginType())); + paramField.setHolder(true); + String methodName = Verifier.capitalize(paramName); + + // let's add a getter & setter + Expression fieldExp = fieldX(paramField); + markAsGenerated(answer, + answer.addMethod( + "get" + methodName, + ACC_PUBLIC, + realType.getPlainNodeReference(), + Parameter.EMPTY_ARRAY, + ClassNode.EMPTY_ARRAY, + returnS(fieldExp)), + true); + } + } + + protected BlockStatement createBlockStatementForConstructor(final ClosureExpression expression, final ClassNode outerClass, final ClassNode thisClassNode) { + 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 = varX(OUTER_INSTANCE, outerClass); + outer.setSourcePosition(expression); + block.getVariableScope().putReferencedLocalVariable(outer); + VariableExpression thisObject = varX(THIS_OBJECT, thisClassNode); + thisObject.setSourcePosition(expression); + block.getVariableScope().putReferencedLocalVariable(thisObject); + TupleExpression conArgs = new TupleExpression(outer, thisObject); + block.addStatement(stmt(ctorSuperX(conArgs))); + return block; + } + + 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(final InnerClassNode icn) { + this.icn = icn; + } + + @Override + public void visitVariableExpression(final 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, final ClosureExpression ce) { + new CorrectAccessedVariableVisitor(closureClass).visitClosureExpression(ce); + } + + /* + * this method is called for local variables shared between scopes. + * These variables must not have init values because these would + * then in later steps be used to create multiple versions of the + * same method, in this case the constructor. A closure should not + * have more than one constructor! + */ + protected static void removeInitialValues(final Parameter[] params) { + for (int i = 0; i < params.length; i++) { + if (params[i].hasInitialExpression()) { + Parameter p = param(params[i].getType(), params[i].getName()); + p.setOriginType(p.getOriginType()); + params[i] = p; + } + } + } + + public boolean addGeneratedClosureConstructorCall(final ConstructorCallExpression call) { + ClassNode classNode = controller.getClassNode(); + if (!classNode.declaresInterface(ClassHelper.GENERATED_CLOSURE_Type)) return false; + + AsmClassGenerator acg = controller.getAcg(); + OperandStack operandStack = controller.getOperandStack(); + + MethodVisitor mv = controller.getMethodVisitor(); + mv.visitVarInsn(ALOAD, 0); + ClassNode callNode = classNode.getSuperClass(); + TupleExpression arguments = (TupleExpression) call.getArguments(); + if (arguments.getExpressions().size() != 2) + throw new GroovyBugError("expected 2 arguments for closure constructor super call, but got" + arguments.getExpressions().size()); + arguments.getExpression(0).visit(acg); + operandStack.box(); + arguments.getExpression(1).visit(acg); + operandStack.box(); + //TODO: replace with normal String, p not needed + Parameter p = param(ClassHelper.OBJECT_TYPE, "_p"); + String descriptor = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, new Parameter[]{p, p}); + mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(callNode), "", descriptor, false); + operandStack.remove(2); + return true; + } + + protected Parameter[] getClosureSharedVariables(final ClosureExpression ce) { + VariableScope scope = ce.getVariableScope(); + Parameter[] ret = new Parameter[scope.getReferencedLocalVariablesCount()]; + int index = 0; + for (Iterator iter = scope.getReferencedLocalVariablesIterator(); iter.hasNext(); ) { + Variable element = iter.next(); + Parameter p = param(element.getType(), element.getName()); + p.setOriginType(element.getOriginType()); + p.setClosureSharedVariable(element.isClosureSharedVariable()); + ret[index] = p; + index++; + } + return ret; + } + + protected void loadThis() { + MethodVisitor mv = controller.getMethodVisitor(); + mv.visitVarInsn(ALOAD, 0); + if (controller.isInGeneratedFunction()) { + mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Closure", "getThisObject", "()Ljava/lang/Object;", false); + controller.getOperandStack().push(ClassHelper.OBJECT_TYPE); + } else { + controller.getOperandStack().push(controller.getClassNode()); + } + } +} diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java new file mode 100644 index 0000000000..53aa164f6c --- /dev/null +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java @@ -0,0 +1,148 @@ +/* + * 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.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.expr.ArrayExpression; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.classgen.asm.ClosureWriter; +import org.codehaus.groovy.classgen.asm.WriterController; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys; +import org.codehaus.groovy.transform.stc.StaticTypesMarker; +import groovyjarjarasm.asm.Opcodes; + +import java.util.Collections; +import java.util.List; + +import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod; +import static org.codehaus.groovy.ast.tools.GeneralUtils.args; +import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; + +/** + * Writer responsible for generating closure classes in statically compiled mode. + */ +public class StaticTypesClosureWriter extends ClosureWriter { + public StaticTypesClosureWriter(WriterController wc) { + super(wc); + } + + @Override + protected ClassNode createClosureClass(final ClosureExpression expression, final int mods) { + ClassNode closureClass = super.createClosureClass(expression, mods); + List methods = closureClass.getDeclaredMethods("call"); + List doCall = closureClass.getMethods("doCall"); + if (doCall.size() != 1) { + throw new GroovyBugError("Expected to find one (1) doCall method on generated closure, but found " + doCall.size()); + } + MethodNode doCallMethod = doCall.get(0); + if (methods.isEmpty() && doCallMethod.getParameters().length == 1) { + createDirectCallMethod(closureClass, doCallMethod); + } + MethodTargetCompletionVisitor visitor = new MethodTargetCompletionVisitor(doCallMethod); + Object dynamic = expression.getNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION); + if (dynamic != null) { + doCallMethod.putNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION, dynamic); + } + for (MethodNode method : methods) { + visitor.visitMethod(method); + } + closureClass.putNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE, Boolean.TRUE); + return closureClass; + } + + private static void createDirectCallMethod(final ClassNode closureClass, final MethodNode doCallMethod) { + // in case there is no "call" method on the closure, we can create a "fast invocation" paths + // to avoid going through ClosureMetaClass by call(Object...) method + + // we can't have a specialized version of call(Object...) because the dispatch logic in ClosureMetaClass + // is too complex! + + // call(Object) + /* GRECLIPSE edit -- GROOVY-10071 + Parameter args = param(ClassHelper.OBJECT_TYPE, "args"); + */ + Parameter doCallParam = doCallMethod.getParameters()[0]; + Parameter args = new Parameter(doCallParam.getType(), "args"); + // GRECLIPSE end + addGeneratedCallMethod(closureClass, doCallMethod, varX(args), new Parameter[]{args}); + + // call() + /* GRECLIPSE edit -- GROOVY-10071, GROOVY-10072 + addGeneratedCallMethod(closureClass, doCallMethod, constX(null), Parameter.EMPTY_ARRAY); + */ + Expression argument; + if (doCallParam.hasInitialExpression()) { + argument = doCallParam.getInitialExpression(); + } else if (doCallParam.getType().isArray()) { + ClassNode elementType = doCallParam.getType().getComponentType(); + argument = new ArrayExpression(elementType, null, Collections.singletonList(constX(0, true))); + } else { + argument = constX(null); + } + addGeneratedCallMethod(closureClass, doCallMethod, argument, Parameter.EMPTY_ARRAY); + // GRECLIPSE end + } + + private static void addGeneratedCallMethod(ClassNode closureClass, MethodNode doCallMethod, Expression expression, Parameter[] params) { + MethodCallExpression doCallarg = callX(varX("this", closureClass), "doCall", args(expression)); + doCallarg.setImplicitThis(true); + doCallarg.setMethodTarget(doCallMethod); + MethodNode call = new MethodNode("call", + Opcodes.ACC_PUBLIC, + ClassHelper.OBJECT_TYPE, + params, + ClassNode.EMPTY_ARRAY, + returnS(doCallarg)); + addGeneratedMethod(closureClass, call, true); + } + + private static final class MethodTargetCompletionVisitor extends ClassCodeVisitorSupport { + + private final MethodNode doCallMethod; + + private MethodTargetCompletionVisitor(final MethodNode doCallMethod) { + this.doCallMethod = doCallMethod; + } + + @Override + protected SourceUnit getSourceUnit() { + return null; + } + + @Override + public void visitMethodCallExpression(final MethodCallExpression call) { + super.visitMethodCallExpression(call); + MethodNode mn = call.getMethodTarget(); + if (mn == null) { + call.setMethodTarget(doCallMethod); + } + } + } +} diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java index 98fbefc39a..40a8598d8e 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java @@ -180,7 +180,7 @@ private void validate(final MethodReferenceExpression methodReferenceExpression, private MethodNode addSyntheticMethodForDGSM(final MethodNode mn) { Parameter[] parameters = removeFirstParameter(mn.getParameters()); ArgumentListExpression args = args(parameters); - args.getExpressions().add(0, ConstantExpression.NULL); + args.getExpressions().add(0, new ConstantExpression(null)); MethodNode syntheticMethodNode = controller.getClassNode().addSyntheticMethod( "dgsm$$" + mn.getParameters()[0].getType().getName().replace(".", "$") + "$$" + mn.getName(), diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java index 1da7fd343f..71ad0cf2cb 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java @@ -432,12 +432,15 @@ static int excessArgumentsMatchesVargsParameter(final Parameter[] parameters, fi */ static int lastArgMatchesVarg(final Parameter[] parameters, final ClassNode... argumentTypes) { if (!isVargs(parameters)) return -1; - // case length ==0 handled already + // GRECLIPSE add -- GROOVY-10071 + int lastParamIndex = parameters.length - 1; + if (lastParamIndex == argumentTypes.length) return 0; + // GRECLIPSE end // we have now two cases, // the argument is wrapped in the vargs array or // the argument is an array that can be used for the vargs part directly // we test only the wrapping part, since the non wrapping is done already - ClassNode lastParamType = parameters[parameters.length - 1].getType(); + ClassNode lastParamType = parameters[lastParamIndex].getType(); ClassNode ptype = lastParamType.getComponentType(); ClassNode arg = argumentTypes[argumentTypes.length - 1]; if (isNumberType(ptype) && isNumberType(arg) && !getWrapper(ptype).equals(getWrapper(arg))) return -1; diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index e05abc768f..fdddf605e7 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -130,6 +130,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.StringJoiner; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiPredicate; @@ -237,7 +238,7 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.NUMBER_OPS; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.UNKNOWN_PARAMETER_TYPE; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.addMethodLevelDeclaredGenerics; -import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.allParametersAndArgumentsMatch; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.allParametersAndArgumentsMatchWithDefaultParams; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsConnections; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsContext; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsContextToParameterClass; @@ -1942,6 +1943,7 @@ public void visitField(final FieldNode node) { typeCheckingContext.isInStaticContext = node.isInStaticContext(); currentField = node; visitAnnotations(node); + /* GRECLIPSE edit Expression init = node.getInitialExpression(); if (init != null) { ClassNode lType = getType(node); @@ -1960,12 +1962,36 @@ public void visitField(final FieldNode node) { BinaryExpression bexp = assignX(left, init, node); typeCheckAssignment(bexp, left, lType, init, getResultType(lType, ASSIGN, rType, bexp)); } + */ + visitInitialExpression(node.getInitialExpression(), new FieldExpression(node), node); + // GRECLIPSE end } finally { currentField = null; typeCheckingContext.isInStaticContext = osc; } } + // GRECLIPSE add + private void visitInitialExpression(final Expression value, final Expression target, final ASTNode position) { + if (value != null) { + ClassNode lType = target.getType(); + if (isFunctionalInterface(lType)) { // GROOVY-9977 + processFunctionalInterfaceAssignment(lType, value); + } else if (isClosureWithType(lType) && value instanceof ClosureExpression) { + storeInferredReturnType(value, getCombinedBoundType(lType.getGenericsTypes()[0])); + } + value.visit(this); + ClassNode rType = getType(value); + if (value instanceof ConstructorCallExpression) { + inferDiamondType((ConstructorCallExpression) value, lType); + } + + BinaryExpression bexp = assignX(target, value, position); + typeCheckAssignment(bexp, target, lType, value, getResultType(lType, ASSIGN, rType, bexp)); + } + } + // GRECLIPSE end + @Override public void visitForLoop(final ForStatement forLoop) { // collect every variable expression used in the loop body @@ -2493,6 +2519,9 @@ public void visitClosureExpression(final ClosureExpression expression) { typeCheckingContext.isInStaticContext = oldStaticContext; for (Parameter parameter : getParametersSafe(expression)) { typeCheckingContext.controlStructureVariables.remove(parameter); + // GRECLIPSE add -- GROOVY-10072: visit param default argument expression if present + visitInitialExpression(parameter.getInitialExpression(), varX(parameter), parameter); + // GRECLIPSE end } } @@ -3528,6 +3557,9 @@ public void visitMethodCallExpression(final MethodCallExpression call) { } else if (objectExpression instanceof ClosureExpression) { // we can get actual parameters directly Parameter[] parameters = ((ClosureExpression) objectExpression).getParameters(); + // GRECLIPSE add + if (parameters != null) + // GRECLIPSE end typeCheckClosureCall(callArguments, args, parameters); ClassNode type = getInferredReturnType(objectExpression); if (type != null) { @@ -3993,6 +4025,7 @@ private void checkSuperCallFromClosure(final Expression call, final MethodNode d } protected void typeCheckClosureCall(final Expression callArguments, final ClassNode[] args, final Parameter[] parameters) { + /* GRECLIPSE edit -- GROOVY-10071, GROOVY-10072 if (allParametersAndArgumentsMatch(parameters, args) < 0 && lastArgMatchesVarg(parameters, args) < 0) { StringBuilder sb = new StringBuilder("["); @@ -4004,6 +4037,11 @@ protected void typeCheckClosureCall(final Expression callArguments, final ClassN sb.append("]"); addStaticTypeError("Closure argument types: " + sb + " do not match with parameter types: " + formatArgumentList(args), callArguments); } + */ + if (allParametersAndArgumentsMatchWithDefaultParams(parameters, args) < 0 && lastArgMatchesVarg(parameters, args) < 0) { + addStaticTypeError("Cannot call closure that accepts " + formatArgumentList(extractTypesFromParameters(parameters)) + " with " + formatArgumentList(args), callArguments); + } + // GRECLIPSE end } @Override @@ -5841,6 +5879,7 @@ private static String toMethodGenericTypesString(final MethodNode node) { protected static String formatArgumentList(final ClassNode[] nodes) { if (nodes == null || nodes.length == 0) return "[]"; + /* GRECLIPSE edit StringBuilder sb = new StringBuilder(24 * nodes.length); sb.append('['); for (ClassNode node : nodes) { @@ -5851,6 +5890,13 @@ protected static String formatArgumentList(final ClassNode[] nodes) { sb.setCharAt(sb.length() - 2, ']'); } return sb.toString(); + */ + StringJoiner joiner = new StringJoiner(", ", "[", "]"); + for (ClassNode node : nodes) { + joiner.add(prettyPrintType(node)); + } + return joiner.toString(); + // GRECLIPSE end } private static void putSetterInfo(final Expression exp, final SetterInfo info) {