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 b02e75e0c2..718122a0d3 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
@@ -7214,4 +7214,23 @@ public void testCompileStatic10424() {
runConformTest(sources, "works");
}
+
+ @Test
+ public void testCompileStatic10457() {
+ //@formatter:off
+ String[] sources = {
+ "Main.groovy",
+ "@groovy.transform.CompileStatic\n" +
+ "class C {\n" +
+ " @groovy.transform.CompileDynamic\n" +
+ " C() {\n" +
+ " print(new StringReader('works').text)\n" +
+ " }\n" +
+ "}\n" +
+ "new C()\n",
+ };
+ //@formatter:on
+
+ runConformTest(sources, "works");
+ }
}
diff --git a/base/org.codehaus.groovy30/.checkstyle b/base/org.codehaus.groovy30/.checkstyle
index 2caec0e3b7..bfe4c5108f 100644
--- a/base/org.codehaus.groovy30/.checkstyle
+++ b/base/org.codehaus.groovy30/.checkstyle
@@ -68,6 +68,7 @@
+
diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
new file mode 100644
index 0000000000..55f0797281
--- /dev/null
+++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
@@ -0,0 +1,589 @@
+/*
+ * 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.transform.sc;
+
+import groovy.lang.Reference;
+import groovy.transform.CompileStatic;
+import groovy.transform.TypeChecked;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.GenericsType;
+import org.codehaus.groovy.ast.InnerClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.ClosureListExpression;
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.SpreadExpression;
+import org.codehaus.groovy.ast.stmt.EmptyStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.ForStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.classgen.asm.InvocationWriter;
+import org.codehaus.groovy.classgen.asm.MopWriter;
+import org.codehaus.groovy.classgen.asm.TypeChooser;
+import org.codehaus.groovy.classgen.asm.WriterControllerFactory;
+import org.codehaus.groovy.classgen.asm.sc.StaticCompilationMopWriter;
+import org.codehaus.groovy.classgen.asm.sc.StaticTypesTypeChooser;
+import org.codehaus.groovy.control.CompilationUnit.IPrimaryClassNodeOperation;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
+import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.codehaus.groovy.ast.ClassHelper.Character_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.LIST_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.STRING_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.int_TYPE;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.attrX;
+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.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorThisS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.addMethodGenerics;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.applyGenericsContextToPlaceHolders;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpecRecurse;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.createGenericsSpec;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.extractSuperClassGenerics;
+import static org.codehaus.groovy.classgen.Verifier.DEFAULT_PARAMETER_GENERATED;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.BINARY_EXP_TARGET;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.COMPONENT_TYPE;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.DYNAMIC_OUTER_NODE_CALLBACK;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.PRIVATE_BRIDGE_METHODS;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.PRIVATE_FIELDS_ACCESSORS;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.PRIVATE_FIELDS_MUTATORS;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.PROPERTY_OWNER;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.RECEIVER_OF_DYNAMIC_PROPERTY;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.STATIC_COMPILE_NODE;
+import static org.codehaus.groovy.transform.stc.StaticTypesMarker.DIRECT_METHOD_CALL_TARGET;
+import static org.codehaus.groovy.transform.stc.StaticTypesMarker.DYNAMIC_RESOLUTION;
+import static org.codehaus.groovy.transform.stc.StaticTypesMarker.INITIAL_EXPRESSION;
+import static org.codehaus.groovy.transform.stc.StaticTypesMarker.PV_FIELDS_ACCESS;
+import static org.codehaus.groovy.transform.stc.StaticTypesMarker.PV_FIELDS_MUTATION;
+import static org.codehaus.groovy.transform.stc.StaticTypesMarker.PV_METHODS_ACCESS;
+import static groovyjarjarasm.asm.Opcodes.ACC_PUBLIC;
+import static groovyjarjarasm.asm.Opcodes.ACC_STATIC;
+import static groovyjarjarasm.asm.Opcodes.ACC_SYNTHETIC;
+
+/**
+ * This visitor is responsible for amending the AST with static compilation metadata or transform the AST so that
+ * a class or a method can be statically compiled. It may also throw errors specific to static compilation which
+ * are not considered as an error at the type check pass. For example, usage of spread operator is not allowed
+ * in statically compiled portions of code, while it may be statically checked.
+ *
+ * Static compilation relies on static type checking, which explains why this visitor extends the type checker
+ * visitor.
+ */
+public class StaticCompilationVisitor extends StaticTypeCheckingVisitor {
+
+ public static final ClassNode TYPECHECKED_CLASSNODE = ClassHelper.make(TypeChecked.class);
+ public static final ClassNode COMPILESTATIC_CLASSNODE = ClassHelper.make(CompileStatic.class);
+
+ public static final ClassNode ARRAYLIST_CLASSNODE = ClassHelper.make(ArrayList.class);
+ public static final MethodNode ARRAYLIST_ADD_METHOD = ARRAYLIST_CLASSNODE.getMethod("add", new Parameter[]{new Parameter(OBJECT_TYPE, "o")});
+ public static final MethodNode ARRAYLIST_CONSTRUCTOR = new ConstructorNode(ACC_PUBLIC, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, EmptyStatement.INSTANCE);
+ static {
+ ARRAYLIST_CONSTRUCTOR.setDeclaringClass(StaticCompilationVisitor.ARRAYLIST_CLASSNODE);
+ }
+
+ private final TypeChooser typeChooser = new StaticTypesTypeChooser();
+
+ private ClassNode classNode;
+
+ public StaticCompilationVisitor(final SourceUnit unit, final ClassNode node) {
+ super(unit, node);
+ }
+
+ @Override
+ protected ClassNode[] getTypeCheckingAnnotations() {
+ return new ClassNode[]{TYPECHECKED_CLASSNODE, COMPILESTATIC_CLASSNODE};
+ }
+
+ public static boolean isStaticallyCompiled(final AnnotatedNode node) {
+ if (node != null && node.getNodeMetaData(STATIC_COMPILE_NODE) != null) {
+ return Boolean.TRUE.equals(node.getNodeMetaData(STATIC_COMPILE_NODE));
+ }
+ if (node instanceof MethodNode) {
+ // GROOVY-6851, GROOVY-9151, GROOVY-10104
+ if (!Boolean.TRUE.equals(node.getNodeMetaData(DEFAULT_PARAMETER_GENERATED))) {
+ return isStaticallyCompiled(node.getDeclaringClass());
+ }
+ } else if (node instanceof ClassNode) {
+ return isStaticallyCompiled(((ClassNode) node).getOuterClass());
+ }
+ return false;
+ }
+
+ private void addPrivateFieldAndMethodAccessors(final ClassNode node) {
+ addPrivateBridgeMethods(node);
+ addPrivateFieldsAccessors(node);
+ for (Iterator it = node.getInnerClasses(); it.hasNext(); ) {
+ addPrivateFieldAndMethodAccessors(it.next());
+ }
+ }
+
+ private void addDynamicOuterClassAccessorsCallback(final ClassNode outer) {
+ if (outer != null) {
+ if (!isStaticallyCompiled(outer) && outer.getNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK) == null) {
+ outer.putNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK, (IPrimaryClassNodeOperation) (source, context, classNode) -> {
+ if (classNode == outer) {
+ addPrivateBridgeMethods(classNode);
+ addPrivateFieldsAccessors(classNode);
+ }
+ });
+ }
+ // GROOVY-9328: apply to outer classes
+ addDynamicOuterClassAccessorsCallback(outer.getOuterClass());
+ }
+ }
+
+ @Override
+ public void visitClass(final ClassNode node) {
+ boolean skip = shouldSkipClassNode(node);
+ if (!skip && !anyMethodSkip(node)) {
+ node.putNodeMetaData(MopWriter.Factory.class, StaticCompilationMopWriter.FACTORY);
+ }
+
+ ClassNode previousClassNode = classNode; classNode = node;
+
+ classNode.getInnerClasses().forEachRemaining(innerClassNode -> {
+ boolean innerStaticCompile = !(skip || isSkippedInnerClass(innerClassNode));
+ innerClassNode.putNodeMetaData(STATIC_COMPILE_NODE, Boolean.valueOf(innerStaticCompile));
+ innerClassNode.putNodeMetaData(WriterControllerFactory.class, node.getNodeMetaData(WriterControllerFactory.class));
+ if (innerStaticCompile && !anyMethodSkip(innerClassNode)) {
+ innerClassNode.putNodeMetaData(MopWriter.Factory.class, StaticCompilationMopWriter.FACTORY);
+ }
+ });
+ super.visitClass(node);
+ addPrivateFieldAndMethodAccessors(node);
+ if (isStaticallyCompiled(node)) {
+ ClassNode outerClass = node.getOuterClass();
+ addDynamicOuterClassAccessorsCallback(outerClass);
+ }
+
+ classNode = previousClassNode;
+ }
+
+ private boolean anyMethodSkip(final ClassNode node) {
+ for (MethodNode methodNode : node.getMethods()) {
+ if (isSkipMode(methodNode)) return true;
+ }
+ return false;
+ }
+
+ /**
+ * If we are in a constructor, that is static compiled, but in a class, that
+ * is not, it may happen that init code from object initializers, fields
+ * or properties is added into the constructor code. The backend assumes
+ * a purely static constructor, so it may fail if it encounters dynamic
+ * code here. Thus we make this kind of code fail
+ */
+ private void checkForConstructorWithCSButClassWithout(final MethodNode node) {
+ if (!(node instanceof ConstructorNode)) return;
+ if (!Boolean.TRUE.equals(node.getNodeMetaData(STATIC_COMPILE_NODE))) return;
+ ClassNode outerClass = typeCheckingContext.getEnclosingClassNode();
+ if (Boolean.TRUE.equals(outerClass.getNodeMetaData(STATIC_COMPILE_NODE))) return;
+ if (outerClass.getObjectInitializerStatements().isEmpty()
+ && outerClass.getFields().isEmpty() && outerClass.getProperties().isEmpty()) {
+ return;
+ }
+
+ addStaticTypeError("Cannot statically compile constructor implicitly including non static elements from object initializers, properties or fields.",node);
+ }
+
+ // GRECLIPSE add -- GROOVY-10457
+ @Override
+ public void visitConstructor(final ConstructorNode node) {
+ if (isSkipMode(node)) {
+ node.putNodeMetaData(STATIC_COMPILE_NODE, Boolean.FALSE);
+ }
+ super.visitConstructor(node);
+ checkForConstructorWithCSButClassWithout(node);
+ if (isStaticallyCompiled(node)) {
+ ClassNode declaringClass = node.getDeclaringClass();
+ addDynamicOuterClassAccessorsCallback(declaringClass);
+ }
+ }
+ // GRECLIPSE end
+
+ @Override
+ public void visitMethod(final MethodNode node) {
+ if (isSkipMode(node)) {
+ node.putNodeMetaData(STATIC_COMPILE_NODE, Boolean.FALSE);
+ }
+ super.visitMethod(node);
+ checkForConstructorWithCSButClassWithout(node);
+ if (isStaticallyCompiled(node)) {
+ ClassNode declaringClass = node.getDeclaringClass();
+ addDynamicOuterClassAccessorsCallback(declaringClass);
+ }
+ }
+
+ /**
+ * Adds special accessors and mutators for private fields so that inner classes can get/set them.
+ */
+ private static void addPrivateFieldsAccessors(final ClassNode node) {
+ Map privateFieldAccessors = node.getNodeMetaData(PRIVATE_FIELDS_ACCESSORS);
+ Map privateFieldMutators = node.getNodeMetaData(PRIVATE_FIELDS_MUTATORS);
+ if (privateFieldAccessors != null || privateFieldMutators != null) {
+ // already added
+ return;
+ }
+ Set accessedFields = node.getNodeMetaData(PV_FIELDS_ACCESS);
+ Set mutatedFields = node.getNodeMetaData(PV_FIELDS_MUTATION);
+ if (accessedFields == null && mutatedFields == null) return;
+ // GROOVY-9385: mutation includes access in case of compound assignment or pre/post-increment/decrement
+ if (mutatedFields != null) {
+ accessedFields = new HashSet<>(Optional.ofNullable(accessedFields).orElseGet(Collections::emptySet));
+ accessedFields.addAll(mutatedFields);
+ }
+
+ int acc = -1;
+ privateFieldAccessors = (accessedFields != null ? new HashMap<>() : null);
+ privateFieldMutators = (mutatedFields != null ? new HashMap<>() : null);
+ final int modifiers = ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC;
+ for (FieldNode fieldNode : node.getFields()) {
+ boolean generateAccessor = accessedFields != null && accessedFields.contains(fieldNode);
+ boolean generateMutator = mutatedFields != null && mutatedFields.contains(fieldNode);
+ if (generateAccessor) {
+ acc += 1;
+ Parameter param = new Parameter(node.getPlainNodeReference(), "$that");
+ Expression receiver = fieldNode.isStatic() ? classX(node) : varX(param);
+ Statement body = returnS(attrX(receiver, constX(fieldNode.getName())));
+ MethodNode accessor = node.addMethod("pfaccess$" + acc, modifiers, fieldNode.getOriginType(), new Parameter[]{param}, ClassNode.EMPTY_ARRAY, body);
+ accessor.setNodeMetaData(STATIC_COMPILE_NODE, Boolean.TRUE);
+ privateFieldAccessors.put(fieldNode.getName(), accessor);
+ }
+ if (generateMutator) {
+ // increment acc if it hasn't been incremented in the current iteration
+ if (!generateAccessor) acc += 1;
+ Parameter param = new Parameter(node.getPlainNodeReference(), "$that");
+ Expression receiver = fieldNode.isStatic() ? classX(node) : varX(param);
+ Parameter value = new Parameter(fieldNode.getOriginType(), "$value");
+ Statement body = assignS(attrX(receiver, constX(fieldNode.getName())), varX(value));
+ MethodNode mutator = node.addMethod("pfaccess$0" + acc, modifiers, fieldNode.getOriginType(), new Parameter[]{param, value}, ClassNode.EMPTY_ARRAY, body);
+ mutator.setNodeMetaData(STATIC_COMPILE_NODE, Boolean.TRUE);
+ privateFieldMutators.put(fieldNode.getName(), mutator);
+ }
+ }
+ if (privateFieldAccessors != null) {
+ node.setNodeMetaData(PRIVATE_FIELDS_ACCESSORS, privateFieldAccessors);
+ }
+ if (privateFieldMutators != null) {
+ node.setNodeMetaData(PRIVATE_FIELDS_MUTATORS, privateFieldMutators);
+ }
+ }
+
+ /**
+ * Adds "bridge" methods for private methods of an inner/outer class so that
+ * the outer class is capable of calling them. It does basically the same
+ * job as access$000 like methods in Java.
+ *
+ * @param node an inner/outer class node for which to generate bridge methods
+ */
+ private static void addPrivateBridgeMethods(final ClassNode node) {
+ Set accessedMethods = node.getNodeMetaData(PV_METHODS_ACCESS);
+ if (accessedMethods == null) return;
+ List methods = new ArrayList<>(node.getAllDeclaredMethods());
+ methods.addAll(node.getDeclaredConstructors());
+ Map privateBridgeMethods = node.getNodeMetaData(PRIVATE_BRIDGE_METHODS);
+ if (privateBridgeMethods != null) {
+ // private bridge methods already added
+ return;
+ }
+ privateBridgeMethods = new HashMap<>();
+ int i = -1;
+ final int access = ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC;
+ for (MethodNode method : methods) {
+ if (accessedMethods.contains(method)) {
+ List methodSpecificGenerics = methodSpecificGenerics(method);
+ i += 1;
+ ClassNode declaringClass = method.getDeclaringClass();
+ Map genericsSpec = createGenericsSpec(node);
+ genericsSpec = addMethodGenerics(method, genericsSpec);
+ extractSuperClassGenerics(node, declaringClass, genericsSpec);
+ Parameter[] methodParameters = method.getParameters();
+ Parameter[] newParams = new Parameter[methodParameters.length + 1];
+ for (int j = 1; j < newParams.length; j += 1) {
+ Parameter orig = methodParameters[j - 1];
+ newParams[j] = new Parameter(
+ correctToGenericsSpecRecurse(genericsSpec, orig.getOriginType(), methodSpecificGenerics),
+ orig.getName()
+ );
+ }
+ Expression arguments;
+ if (method.getParameters() == null || method.getParameters().length == 0) {
+ arguments = ArgumentListExpression.EMPTY_ARGUMENTS;
+ } else {
+ List args = new ArrayList<>();
+ for (Parameter parameter : methodParameters) {
+ args.add(varX(parameter));
+ }
+ arguments = args(args);
+ }
+
+ MethodNode bridge;
+ if (method instanceof ConstructorNode) {
+ // create constructor with a nested class as the first parameter, creating one if necessary
+ ClassNode thatType = null;
+ Iterator innerClasses = node.getInnerClasses();
+ if (innerClasses.hasNext()) {
+ thatType = innerClasses.next();
+ } else {
+ thatType = new InnerClassNode(node.redirect(), node.getName() + "$1", ACC_STATIC | ACC_SYNTHETIC, OBJECT_TYPE);
+ node.getModule().addClass(thatType);
+ }
+ newParams[0] = new Parameter(thatType.getPlainNodeReference(), "$that");
+ Statement body = ctorThisS(arguments);
+
+ bridge = node.addConstructor(ACC_SYNTHETIC, newParams, ClassNode.EMPTY_ARRAY, body);
+ } else {
+ newParams[0] = new Parameter(node.getPlainNodeReference(), "$that");
+ Expression receiver = method.isStatic() ? classX(node) : varX(newParams[0]);
+ MethodCallExpression call = callX(receiver, method.getName(), arguments);
+ call.setMethodTarget(method);
+ ExpressionStatement body = new ExpressionStatement(call);
+
+ bridge = node.addMethod(
+ "access$" + i, access,
+ correctToGenericsSpecRecurse(genericsSpec, method.getReturnType(), methodSpecificGenerics),
+ newParams,
+ method.getExceptions(),
+ body);
+ }
+ GenericsType[] origGenericsTypes = method.getGenericsTypes();
+ if (origGenericsTypes != null) {
+ bridge.setGenericsTypes(applyGenericsContextToPlaceHolders(genericsSpec, origGenericsTypes));
+ }
+ bridge.setNodeMetaData(STATIC_COMPILE_NODE, Boolean.TRUE);
+ privateBridgeMethods.put(method, bridge);
+ }
+ }
+ if (!privateBridgeMethods.isEmpty()) {
+ node.setNodeMetaData(PRIVATE_BRIDGE_METHODS, privateBridgeMethods);
+ }
+ }
+
+ private static List methodSpecificGenerics(final MethodNode method) {
+ List genericTypeNames = new ArrayList<>();
+ GenericsType[] genericsTypes = method.getGenericsTypes();
+ if (genericsTypes != null) {
+ for (GenericsType gt : genericsTypes) {
+ genericTypeNames.add(gt.getName());
+ }
+ }
+ return genericTypeNames;
+ }
+
+ private static void memorizeInitialExpressions(final MethodNode node) {
+ // add node metadata for default parameters because they are erased by the Verifier
+ if (node.getParameters() != null) {
+ for (Parameter parameter : node.getParameters()) {
+ parameter.putNodeMetaData(INITIAL_EXPRESSION, parameter.getInitialExpression());
+ }
+ }
+ }
+
+ @Override
+ public void visitMethodCallExpression(final MethodCallExpression call) {
+ super.visitMethodCallExpression(call);
+
+ MethodNode target = call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
+ if (target != null) {
+ call.setMethodTarget(target);
+ memorizeInitialExpressions(target);
+ }
+
+ if (call.getMethodTarget() == null && call.getLineNumber() > 0) {
+ addError("Target method for method call expression hasn't been set", call);
+ }
+ }
+
+ @Override
+ public void visitConstructorCallExpression(final ConstructorCallExpression call) {
+ super.visitConstructorCallExpression(call);
+
+ if (call.isUsingAnonymousInnerClass() && call.getType().getNodeMetaData(StaticTypeCheckingVisitor.class) != null) {
+ ClassNode anonType = call.getType();
+ anonType.putNodeMetaData(STATIC_COMPILE_NODE, anonType.getEnclosingMethod().getNodeMetaData(STATIC_COMPILE_NODE));
+ anonType.putNodeMetaData(WriterControllerFactory.class, anonType.getOuterClass().getNodeMetaData(WriterControllerFactory.class));
+ }
+
+ MethodNode target = call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
+ if (target == null && call.getLineNumber() > 0) {
+ addError("Target constructor for constructor call expression hasn't been set", call);
+ } else if (target == null) {
+ // try to find a target
+ ArgumentListExpression argumentListExpression = InvocationWriter.makeArgumentList(call.getArguments());
+ List expressions = argumentListExpression.getExpressions();
+ ClassNode[] args = new ClassNode[expressions.size()];
+ for (int i = 0, n = args.length; i < n; i += 1) {
+ args[i] = typeChooser.resolveType(expressions.get(i), classNode);
+ }
+ target = findMethodOrFail(call, call.isSuperCall() ? classNode.getSuperClass() : classNode, "", args);
+ call.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, target);
+ }
+ if (target != null) {
+ memorizeInitialExpressions(target);
+ }
+ }
+
+ @Override
+ public void visitForLoop(final ForStatement statement) {
+ super.visitForLoop(statement);
+ Expression collectionExpression = statement.getCollectionExpression();
+ if (!(collectionExpression instanceof ClosureListExpression)) {
+ ClassNode forLoopVariableType = statement.getVariableType();
+ ClassNode collectionType = getType(collectionExpression);
+ ClassNode componentType;
+ if (Character_TYPE.equals(ClassHelper.getWrapper(forLoopVariableType)) && STRING_TYPE.equals(collectionType)) {
+ // we allow auto-coercion here
+ componentType = forLoopVariableType;
+ } else {
+ componentType = inferLoopElementType(collectionType);
+ }
+ statement.getVariable().setType(componentType);
+ }
+ }
+
+ @Override
+ protected MethodNode findMethodOrFail(final Expression expr, final ClassNode receiver, final String name, final ClassNode... args) {
+ MethodNode methodNode = super.findMethodOrFail(expr, receiver, name, args);
+ if (expr instanceof BinaryExpression && methodNode != null) {
+ expr.putNodeMetaData(BINARY_EXP_TARGET, new Object[]{methodNode, name});
+ }
+ return methodNode;
+ }
+
+ @Override
+ protected boolean existsProperty(final PropertyExpression pexp, final boolean checkForReadOnly, final ClassCodeVisitorSupport visitor) {
+ Expression objectExpression = pexp.getObjectExpression();
+ ClassNode objectExpressionType = getType(objectExpression);
+ Reference rType = new Reference<>(objectExpressionType);
+ ClassCodeVisitorSupport receiverMemoizer = new ClassCodeVisitorSupport() {
+ @Override
+ protected SourceUnit getSourceUnit() {
+ return null;
+ }
+
+ @Override
+ public void visitField(final FieldNode node) {
+ if (visitor != null) visitor.visitField(node);
+ ClassNode declaringClass = node.getDeclaringClass();
+ if (declaringClass != null) {
+ if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(declaringClass, LIST_TYPE)) {
+ boolean spread = declaringClass.getDeclaredField(node.getName()) != node;
+ pexp.setSpreadSafe(spread);
+ }
+ rType.set(declaringClass);
+ }
+ }
+
+ @Override
+ public void visitMethod(final MethodNode node) {
+ if (visitor != null) visitor.visitMethod(node);
+ ClassNode declaringClass = node.getDeclaringClass();
+ if (declaringClass != null) {
+ if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(declaringClass, LIST_TYPE)) {
+ List properties = declaringClass.getDeclaredMethods(node.getName());
+ boolean spread = true;
+ for (MethodNode mn : properties) {
+ if (node == mn) {
+ spread = false;
+ break;
+ }
+ }
+ // it's no real property but a property of the component
+ pexp.setSpreadSafe(spread);
+ }
+ rType.set(declaringClass);
+ }
+ }
+
+ @Override
+ public void visitProperty(final PropertyNode node) {
+ if (visitor != null) visitor.visitProperty(node);
+ ClassNode declaringClass = node.getDeclaringClass();
+ if (declaringClass != null) {
+ if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(declaringClass, LIST_TYPE)) {
+ List properties = declaringClass.getProperties();
+ boolean spread = true;
+ for (PropertyNode propertyNode : properties) {
+ if (propertyNode == node) {
+ spread = false;
+ break;
+ }
+ }
+ // it's no real property but a property of the component
+ pexp.setSpreadSafe(spread);
+ }
+ rType.set(declaringClass);
+ }
+ }
+ };
+
+ boolean exists = super.existsProperty(pexp, checkForReadOnly, receiverMemoizer);
+ if (exists) {
+ if (objectExpression.getNodeMetaData(PROPERTY_OWNER) == null) {
+ objectExpression.putNodeMetaData(PROPERTY_OWNER, rType.get());
+ }
+ if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(objectExpressionType, LIST_TYPE)) {
+ objectExpression.putNodeMetaData(COMPONENT_TYPE, inferComponentType(objectExpressionType, int_TYPE));
+ }
+ }
+ return exists;
+ }
+
+ @Override
+ public void visitPropertyExpression(final PropertyExpression expression) {
+ super.visitPropertyExpression(expression);
+ Object dynamic = expression.getNodeMetaData(DYNAMIC_RESOLUTION);
+ if (dynamic != null) {
+ expression.getObjectExpression().putNodeMetaData(RECEIVER_OF_DYNAMIC_PROPERTY, dynamic);
+ }
+ }
+
+ @Override
+ public void visitSpreadExpression(final SpreadExpression expression) {
+ }
+}
diff --git a/base/org.codehaus.groovy40/.checkstyle b/base/org.codehaus.groovy40/.checkstyle
index 158a999deb..8dd0c10c92 100644
--- a/base/org.codehaus.groovy40/.checkstyle
+++ b/base/org.codehaus.groovy40/.checkstyle
@@ -56,6 +56,7 @@
+
diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
new file mode 100644
index 0000000000..b38e5a8ef8
--- /dev/null
+++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
@@ -0,0 +1,590 @@
+/*
+ * 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.transform.sc;
+
+import groovy.lang.Reference;
+import groovy.transform.CompileStatic;
+import groovy.transform.TypeChecked;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.GenericsType;
+import org.codehaus.groovy.ast.InnerClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.ClosureListExpression;
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.SpreadExpression;
+import org.codehaus.groovy.ast.stmt.EmptyStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.ForStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.classgen.asm.InvocationWriter;
+import org.codehaus.groovy.classgen.asm.MopWriter;
+import org.codehaus.groovy.classgen.asm.TypeChooser;
+import org.codehaus.groovy.classgen.asm.WriterControllerFactory;
+import org.codehaus.groovy.classgen.asm.sc.StaticCompilationMopWriter;
+import org.codehaus.groovy.classgen.asm.sc.StaticTypesTypeChooser;
+import org.codehaus.groovy.control.CompilationUnit.IPrimaryClassNodeOperation;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
+import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.codehaus.groovy.ast.ClassHelper.LIST_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.int_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.isStringType;
+import static org.codehaus.groovy.ast.ClassHelper.isWrapperCharacter;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.attrX;
+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.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorThisS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.addMethodGenerics;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.applyGenericsContextToPlaceHolders;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpecRecurse;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.createGenericsSpec;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.extractSuperClassGenerics;
+import static org.codehaus.groovy.classgen.Verifier.DEFAULT_PARAMETER_GENERATED;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.BINARY_EXP_TARGET;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.COMPONENT_TYPE;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.DYNAMIC_OUTER_NODE_CALLBACK;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.PRIVATE_BRIDGE_METHODS;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.PRIVATE_FIELDS_ACCESSORS;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.PRIVATE_FIELDS_MUTATORS;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.PROPERTY_OWNER;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.RECEIVER_OF_DYNAMIC_PROPERTY;
+import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.STATIC_COMPILE_NODE;
+import static org.codehaus.groovy.transform.stc.StaticTypesMarker.DIRECT_METHOD_CALL_TARGET;
+import static org.codehaus.groovy.transform.stc.StaticTypesMarker.DYNAMIC_RESOLUTION;
+import static org.codehaus.groovy.transform.stc.StaticTypesMarker.INITIAL_EXPRESSION;
+import static org.codehaus.groovy.transform.stc.StaticTypesMarker.PV_FIELDS_ACCESS;
+import static org.codehaus.groovy.transform.stc.StaticTypesMarker.PV_FIELDS_MUTATION;
+import static org.codehaus.groovy.transform.stc.StaticTypesMarker.PV_METHODS_ACCESS;
+import static groovyjarjarasm.asm.Opcodes.ACC_PUBLIC;
+import static groovyjarjarasm.asm.Opcodes.ACC_STATIC;
+import static groovyjarjarasm.asm.Opcodes.ACC_SYNTHETIC;
+
+/**
+ * This visitor is responsible for amending the AST with static compilation metadata or transform the AST so that
+ * a class or a method can be statically compiled. It may also throw errors specific to static compilation which
+ * are not considered as an error at the type check pass. For example, usage of spread operator is not allowed
+ * in statically compiled portions of code, while it may be statically checked.
+ *
+ * Static compilation relies on static type checking, which explains why this visitor extends the type checker
+ * visitor.
+ */
+public class StaticCompilationVisitor extends StaticTypeCheckingVisitor {
+
+ public static final ClassNode TYPECHECKED_CLASSNODE = ClassHelper.make(TypeChecked.class);
+ public static final ClassNode COMPILESTATIC_CLASSNODE = ClassHelper.make(CompileStatic.class);
+
+ public static final ClassNode ARRAYLIST_CLASSNODE = ClassHelper.make(ArrayList.class);
+ public static final MethodNode ARRAYLIST_ADD_METHOD = ARRAYLIST_CLASSNODE.getMethod("add", new Parameter[]{new Parameter(OBJECT_TYPE, "o")});
+ public static final MethodNode ARRAYLIST_CONSTRUCTOR = new ConstructorNode(ACC_PUBLIC, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, EmptyStatement.INSTANCE);
+ static {
+ ARRAYLIST_CONSTRUCTOR.setDeclaringClass(StaticCompilationVisitor.ARRAYLIST_CLASSNODE);
+ }
+
+ private final TypeChooser typeChooser = new StaticTypesTypeChooser();
+
+ private ClassNode classNode;
+
+ public StaticCompilationVisitor(final SourceUnit unit, final ClassNode node) {
+ super(unit, node);
+ }
+
+ @Override
+ protected ClassNode[] getTypeCheckingAnnotations() {
+ return new ClassNode[]{TYPECHECKED_CLASSNODE, COMPILESTATIC_CLASSNODE};
+ }
+
+ public static boolean isStaticallyCompiled(final AnnotatedNode node) {
+ if (node != null && node.getNodeMetaData(STATIC_COMPILE_NODE) != null) {
+ return Boolean.TRUE.equals(node.getNodeMetaData(STATIC_COMPILE_NODE));
+ }
+ if (node instanceof MethodNode) {
+ // GROOVY-6851, GROOVY-9151, GROOVY-10104
+ if (!Boolean.TRUE.equals(node.getNodeMetaData(DEFAULT_PARAMETER_GENERATED))) {
+ return isStaticallyCompiled(node.getDeclaringClass());
+ }
+ } else if (node instanceof ClassNode) {
+ return isStaticallyCompiled(((ClassNode) node).getOuterClass());
+ }
+ return false;
+ }
+
+ private void addPrivateFieldAndMethodAccessors(final ClassNode node) {
+ addPrivateBridgeMethods(node);
+ addPrivateFieldsAccessors(node);
+ for (Iterator it = node.getInnerClasses(); it.hasNext(); ) {
+ addPrivateFieldAndMethodAccessors(it.next());
+ }
+ }
+
+ private void addDynamicOuterClassAccessorsCallback(final ClassNode outer) {
+ if (outer != null) {
+ if (!isStaticallyCompiled(outer) && outer.getNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK) == null) {
+ outer.putNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK, (IPrimaryClassNodeOperation) (source, context, classNode) -> {
+ if (classNode == outer) {
+ addPrivateBridgeMethods(classNode);
+ addPrivateFieldsAccessors(classNode);
+ }
+ });
+ }
+ // GROOVY-9328: apply to outer classes
+ addDynamicOuterClassAccessorsCallback(outer.getOuterClass());
+ }
+ }
+
+ @Override
+ public void visitClass(final ClassNode node) {
+ boolean skip = shouldSkipClassNode(node);
+ if (!skip && !anyMethodSkip(node)) {
+ node.putNodeMetaData(MopWriter.Factory.class, StaticCompilationMopWriter.FACTORY);
+ }
+
+ ClassNode previousClassNode = classNode; classNode = node;
+
+ classNode.getInnerClasses().forEachRemaining(innerClassNode -> {
+ boolean innerStaticCompile = !(skip || isSkippedInnerClass(innerClassNode));
+ innerClassNode.putNodeMetaData(STATIC_COMPILE_NODE, Boolean.valueOf(innerStaticCompile));
+ innerClassNode.putNodeMetaData(WriterControllerFactory.class, node.getNodeMetaData(WriterControllerFactory.class));
+ if (innerStaticCompile && !anyMethodSkip(innerClassNode)) {
+ innerClassNode.putNodeMetaData(MopWriter.Factory.class, StaticCompilationMopWriter.FACTORY);
+ }
+ });
+ super.visitClass(node);
+ addPrivateFieldAndMethodAccessors(node);
+ if (isStaticallyCompiled(node)) {
+ ClassNode outerClass = node.getOuterClass();
+ addDynamicOuterClassAccessorsCallback(outerClass);
+ }
+
+ classNode = previousClassNode;
+ }
+
+ private boolean anyMethodSkip(final ClassNode node) {
+ for (MethodNode methodNode : node.getMethods()) {
+ if (isSkipMode(methodNode)) return true;
+ }
+ return false;
+ }
+
+ /**
+ * If we are in a constructor, that is static compiled, but in a class, that
+ * is not, it may happen that init code from object initializers, fields
+ * or properties is added into the constructor code. The backend assumes
+ * a purely static constructor, so it may fail if it encounters dynamic
+ * code here. Thus we make this kind of code fail
+ */
+ private void checkForConstructorWithCSButClassWithout(final MethodNode node) {
+ if (!(node instanceof ConstructorNode)) return;
+ if (!Boolean.TRUE.equals(node.getNodeMetaData(STATIC_COMPILE_NODE))) return;
+ ClassNode outerClass = typeCheckingContext.getEnclosingClassNode();
+ if (Boolean.TRUE.equals(outerClass.getNodeMetaData(STATIC_COMPILE_NODE))) return;
+ if (outerClass.getObjectInitializerStatements().isEmpty()
+ && outerClass.getFields().isEmpty() && outerClass.getProperties().isEmpty()) {
+ return;
+ }
+
+ addStaticTypeError("Cannot statically compile constructor implicitly including non static elements from object initializers, properties or fields.",node);
+ }
+
+ // GRECLIPSE add -- GROOVY-10457
+ @Override
+ public void visitConstructor(final ConstructorNode node) {
+ if (isSkipMode(node)) {
+ node.putNodeMetaData(STATIC_COMPILE_NODE, Boolean.FALSE);
+ }
+ super.visitConstructor(node);
+ checkForConstructorWithCSButClassWithout(node);
+ if (isStaticallyCompiled(node)) {
+ ClassNode declaringClass = node.getDeclaringClass();
+ addDynamicOuterClassAccessorsCallback(declaringClass);
+ }
+ }
+ // GRECLIPSE end
+
+ @Override
+ public void visitMethod(final MethodNode node) {
+ if (isSkipMode(node)) {
+ node.putNodeMetaData(STATIC_COMPILE_NODE, Boolean.FALSE);
+ }
+ super.visitMethod(node);
+ checkForConstructorWithCSButClassWithout(node);
+ if (isStaticallyCompiled(node)) {
+ ClassNode declaringClass = node.getDeclaringClass();
+ addDynamicOuterClassAccessorsCallback(declaringClass);
+ }
+ }
+
+ /**
+ * Adds special accessors and mutators for private fields so that inner classes can get/set them.
+ */
+ private static void addPrivateFieldsAccessors(final ClassNode node) {
+ Map privateFieldAccessors = node.getNodeMetaData(PRIVATE_FIELDS_ACCESSORS);
+ Map privateFieldMutators = node.getNodeMetaData(PRIVATE_FIELDS_MUTATORS);
+ if (privateFieldAccessors != null || privateFieldMutators != null) {
+ // already added
+ return;
+ }
+ Set accessedFields = node.getNodeMetaData(PV_FIELDS_ACCESS);
+ Set mutatedFields = node.getNodeMetaData(PV_FIELDS_MUTATION);
+ if (accessedFields == null && mutatedFields == null) return;
+ // GROOVY-9385: mutation includes access in case of compound assignment or pre/post-increment/decrement
+ if (mutatedFields != null) {
+ accessedFields = new HashSet<>(Optional.ofNullable(accessedFields).orElseGet(Collections::emptySet));
+ accessedFields.addAll(mutatedFields);
+ }
+
+ int acc = -1;
+ privateFieldAccessors = (accessedFields != null ? new HashMap<>() : null);
+ privateFieldMutators = (mutatedFields != null ? new HashMap<>() : null);
+ final int modifiers = ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC;
+ for (FieldNode fieldNode : node.getFields()) {
+ boolean generateAccessor = accessedFields != null && accessedFields.contains(fieldNode);
+ boolean generateMutator = mutatedFields != null && mutatedFields.contains(fieldNode);
+ if (generateAccessor) {
+ acc += 1;
+ Parameter param = new Parameter(node.getPlainNodeReference(), "$that");
+ Expression receiver = fieldNode.isStatic() ? classX(node) : varX(param);
+ Statement body = returnS(attrX(receiver, constX(fieldNode.getName())));
+ MethodNode accessor = node.addMethod("pfaccess$" + acc, modifiers, fieldNode.getOriginType(), new Parameter[]{param}, ClassNode.EMPTY_ARRAY, body);
+ accessor.setNodeMetaData(STATIC_COMPILE_NODE, Boolean.TRUE);
+ privateFieldAccessors.put(fieldNode.getName(), accessor);
+ }
+ if (generateMutator) {
+ // increment acc if it hasn't been incremented in the current iteration
+ if (!generateAccessor) acc += 1;
+ Parameter param = new Parameter(node.getPlainNodeReference(), "$that");
+ Expression receiver = fieldNode.isStatic() ? classX(node) : varX(param);
+ Parameter value = new Parameter(fieldNode.getOriginType(), "$value");
+ Statement body = assignS(attrX(receiver, constX(fieldNode.getName())), varX(value));
+ MethodNode mutator = node.addMethod("pfaccess$0" + acc, modifiers, fieldNode.getOriginType(), new Parameter[]{param, value}, ClassNode.EMPTY_ARRAY, body);
+ mutator.setNodeMetaData(STATIC_COMPILE_NODE, Boolean.TRUE);
+ privateFieldMutators.put(fieldNode.getName(), mutator);
+ }
+ }
+ if (privateFieldAccessors != null) {
+ node.setNodeMetaData(PRIVATE_FIELDS_ACCESSORS, privateFieldAccessors);
+ }
+ if (privateFieldMutators != null) {
+ node.setNodeMetaData(PRIVATE_FIELDS_MUTATORS, privateFieldMutators);
+ }
+ }
+
+ /**
+ * Adds "bridge" methods for private methods of an inner/outer class so that
+ * the outer class is capable of calling them. It does basically the same
+ * job as access$000 like methods in Java.
+ *
+ * @param node an inner/outer class node for which to generate bridge methods
+ */
+ private static void addPrivateBridgeMethods(final ClassNode node) {
+ Set accessedMethods = node.getNodeMetaData(PV_METHODS_ACCESS);
+ if (accessedMethods == null) return;
+ List methods = new ArrayList<>(node.getAllDeclaredMethods());
+ methods.addAll(node.getDeclaredConstructors());
+ Map privateBridgeMethods = node.getNodeMetaData(PRIVATE_BRIDGE_METHODS);
+ if (privateBridgeMethods != null) {
+ // private bridge methods already added
+ return;
+ }
+ privateBridgeMethods = new HashMap<>();
+ int i = -1;
+ final int access = ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC;
+ for (MethodNode method : methods) {
+ if (accessedMethods.contains(method)) {
+ List methodSpecificGenerics = methodSpecificGenerics(method);
+ i += 1;
+ ClassNode declaringClass = method.getDeclaringClass();
+ Map genericsSpec = createGenericsSpec(node);
+ genericsSpec = addMethodGenerics(method, genericsSpec);
+ extractSuperClassGenerics(node, declaringClass, genericsSpec);
+ Parameter[] methodParameters = method.getParameters();
+ Parameter[] newParams = new Parameter[methodParameters.length + 1];
+ for (int j = 1; j < newParams.length; j += 1) {
+ Parameter orig = methodParameters[j - 1];
+ newParams[j] = new Parameter(
+ correctToGenericsSpecRecurse(genericsSpec, orig.getOriginType(), methodSpecificGenerics),
+ orig.getName()
+ );
+ }
+ Expression arguments;
+ if (method.getParameters() == null || method.getParameters().length == 0) {
+ arguments = ArgumentListExpression.EMPTY_ARGUMENTS;
+ } else {
+ List args = new ArrayList<>();
+ for (Parameter parameter : methodParameters) {
+ args.add(varX(parameter));
+ }
+ arguments = args(args);
+ }
+
+ MethodNode bridge;
+ if (method instanceof ConstructorNode) {
+ // create constructor with a nested class as the first parameter, creating one if necessary
+ ClassNode thatType = null;
+ Iterator innerClasses = node.getInnerClasses();
+ if (innerClasses.hasNext()) {
+ thatType = innerClasses.next();
+ } else {
+ thatType = new InnerClassNode(node.redirect(), node.getName() + "$1", ACC_STATIC | ACC_SYNTHETIC, OBJECT_TYPE);
+ node.getModule().addClass(thatType);
+ }
+ newParams[0] = new Parameter(thatType.getPlainNodeReference(), "$that");
+ Statement body = ctorThisS(arguments);
+
+ bridge = node.addConstructor(ACC_SYNTHETIC, newParams, ClassNode.EMPTY_ARRAY, body);
+ } else {
+ newParams[0] = new Parameter(node.getPlainNodeReference(), "$that");
+ Expression receiver = method.isStatic() ? classX(node) : varX(newParams[0]);
+ MethodCallExpression call = callX(receiver, method.getName(), arguments);
+ call.setMethodTarget(method);
+ ExpressionStatement body = new ExpressionStatement(call);
+
+ bridge = node.addMethod(
+ "access$" + i, access,
+ correctToGenericsSpecRecurse(genericsSpec, method.getReturnType(), methodSpecificGenerics),
+ newParams,
+ method.getExceptions(),
+ body);
+ }
+ GenericsType[] origGenericsTypes = method.getGenericsTypes();
+ if (origGenericsTypes != null) {
+ bridge.setGenericsTypes(applyGenericsContextToPlaceHolders(genericsSpec, origGenericsTypes));
+ }
+ bridge.setNodeMetaData(STATIC_COMPILE_NODE, Boolean.TRUE);
+ privateBridgeMethods.put(method, bridge);
+ }
+ }
+ if (!privateBridgeMethods.isEmpty()) {
+ node.setNodeMetaData(PRIVATE_BRIDGE_METHODS, privateBridgeMethods);
+ }
+ }
+
+ private static List methodSpecificGenerics(final MethodNode method) {
+ List genericTypeNames = new ArrayList<>();
+ GenericsType[] genericsTypes = method.getGenericsTypes();
+ if (genericsTypes != null) {
+ for (GenericsType gt : genericsTypes) {
+ genericTypeNames.add(gt.getName());
+ }
+ }
+ return genericTypeNames;
+ }
+
+ private static void memorizeInitialExpressions(final MethodNode node) {
+ // add node metadata for default parameters because they are erased by the Verifier
+ if (node.getParameters() != null) {
+ for (Parameter parameter : node.getParameters()) {
+ parameter.putNodeMetaData(INITIAL_EXPRESSION, parameter.getInitialExpression());
+ }
+ }
+ }
+
+ @Override
+ public void visitMethodCallExpression(final MethodCallExpression call) {
+ super.visitMethodCallExpression(call);
+
+ MethodNode target = call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
+ if (target != null) {
+ call.setMethodTarget(target);
+ memorizeInitialExpressions(target);
+ }
+
+ if (call.getMethodTarget() == null && call.getLineNumber() > 0) {
+ addError("Target method for method call expression hasn't been set", call);
+ }
+ }
+
+ @Override
+ public void visitConstructorCallExpression(final ConstructorCallExpression call) {
+ super.visitConstructorCallExpression(call);
+
+ if (call.isUsingAnonymousInnerClass() && call.getType().getNodeMetaData(StaticTypeCheckingVisitor.class) != null) {
+ ClassNode anonType = call.getType();
+ anonType.putNodeMetaData(STATIC_COMPILE_NODE, anonType.getEnclosingMethod().getNodeMetaData(STATIC_COMPILE_NODE));
+ anonType.putNodeMetaData(WriterControllerFactory.class, anonType.getOuterClass().getNodeMetaData(WriterControllerFactory.class));
+ }
+
+ MethodNode target = call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
+ if (target == null && call.getLineNumber() > 0) {
+ addError("Target constructor for constructor call expression hasn't been set", call);
+ } else if (target == null) {
+ // try to find a target
+ ArgumentListExpression argumentListExpression = InvocationWriter.makeArgumentList(call.getArguments());
+ List expressions = argumentListExpression.getExpressions();
+ ClassNode[] args = new ClassNode[expressions.size()];
+ for (int i = 0, n = args.length; i < n; i += 1) {
+ args[i] = typeChooser.resolveType(expressions.get(i), classNode);
+ }
+ target = findMethodOrFail(call, call.isSuperCall() ? classNode.getSuperClass() : classNode, "", args);
+ call.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, target);
+ }
+ if (target != null) {
+ memorizeInitialExpressions(target);
+ }
+ }
+
+ @Override
+ public void visitForLoop(final ForStatement statement) {
+ super.visitForLoop(statement);
+ Expression collectionExpression = statement.getCollectionExpression();
+ if (!(collectionExpression instanceof ClosureListExpression)) {
+ ClassNode forLoopVariableType = statement.getVariableType();
+ ClassNode collectionType = getType(collectionExpression);
+ ClassNode componentType;
+ if (isWrapperCharacter(ClassHelper.getWrapper(forLoopVariableType)) && isStringType(collectionType)) {
+ // we allow auto-coercion here
+ componentType = forLoopVariableType;
+ } else {
+ componentType = inferLoopElementType(collectionType);
+ }
+ statement.getVariable().setType(componentType);
+ }
+ }
+
+ @Override
+ protected MethodNode findMethodOrFail(final Expression expr, final ClassNode receiver, final String name, final ClassNode... args) {
+ MethodNode methodNode = super.findMethodOrFail(expr, receiver, name, args);
+ if (expr instanceof BinaryExpression && methodNode != null) {
+ expr.putNodeMetaData(BINARY_EXP_TARGET, new Object[]{methodNode, name});
+ }
+ return methodNode;
+ }
+
+ @Override
+ protected boolean existsProperty(final PropertyExpression pexp, final boolean checkForReadOnly, final ClassCodeVisitorSupport visitor) {
+ Expression objectExpression = pexp.getObjectExpression();
+ ClassNode objectExpressionType = getType(objectExpression);
+ Reference rType = new Reference<>(objectExpressionType);
+ ClassCodeVisitorSupport receiverMemoizer = new ClassCodeVisitorSupport() {
+ @Override
+ protected SourceUnit getSourceUnit() {
+ return null;
+ }
+
+ @Override
+ public void visitField(final FieldNode node) {
+ if (visitor != null) visitor.visitField(node);
+ ClassNode declaringClass = node.getDeclaringClass();
+ if (declaringClass != null) {
+ if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(declaringClass, LIST_TYPE)) {
+ boolean spread = declaringClass.getDeclaredField(node.getName()) != node;
+ pexp.setSpreadSafe(spread);
+ }
+ rType.set(declaringClass);
+ }
+ }
+
+ @Override
+ public void visitMethod(final MethodNode node) {
+ if (visitor != null) visitor.visitMethod(node);
+ ClassNode declaringClass = node.getDeclaringClass();
+ if (declaringClass != null) {
+ if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(declaringClass, LIST_TYPE)) {
+ List properties = declaringClass.getDeclaredMethods(node.getName());
+ boolean spread = true;
+ for (MethodNode mn : properties) {
+ if (node == mn) {
+ spread = false;
+ break;
+ }
+ }
+ // it's no real property but a property of the component
+ pexp.setSpreadSafe(spread);
+ }
+ rType.set(declaringClass);
+ }
+ }
+
+ @Override
+ public void visitProperty(final PropertyNode node) {
+ if (visitor != null) visitor.visitProperty(node);
+ ClassNode declaringClass = node.getDeclaringClass();
+ if (declaringClass != null) {
+ if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(declaringClass, LIST_TYPE)) {
+ List properties = declaringClass.getProperties();
+ boolean spread = true;
+ for (PropertyNode propertyNode : properties) {
+ if (propertyNode == node) {
+ spread = false;
+ break;
+ }
+ }
+ // it's no real property but a property of the component
+ pexp.setSpreadSafe(spread);
+ }
+ rType.set(declaringClass);
+ }
+ }
+ };
+
+ boolean exists = super.existsProperty(pexp, checkForReadOnly, receiverMemoizer);
+ if (exists) {
+ objectExpressionType = rType.get();
+ if (objectExpression.getNodeMetaData(PROPERTY_OWNER) == null) {
+ objectExpression.putNodeMetaData(PROPERTY_OWNER, objectExpressionType);
+ }
+ if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(objectExpressionType, LIST_TYPE)) {
+ objectExpression.putNodeMetaData(COMPONENT_TYPE, inferComponentType(objectExpressionType, int_TYPE));
+ }
+ }
+ return exists;
+ }
+
+ @Override
+ public void visitPropertyExpression(final PropertyExpression expression) {
+ super.visitPropertyExpression(expression);
+ Object dynamic = expression.getNodeMetaData(DYNAMIC_RESOLUTION);
+ if (dynamic != null) {
+ expression.getObjectExpression().putNodeMetaData(RECEIVER_OF_DYNAMIC_PROPERTY, dynamic);
+ }
+ }
+
+ @Override
+ public void visitSpreadExpression(final SpreadExpression expression) {
+ }
+}