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 91b0a5303e..25e76192fc 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 @@ -763,6 +763,31 @@ public void testCompileStatic6276() { runConformTest(sources, ""); } + @Test + public void testCompileStatic6610() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.CompileStatic\n" + + "class Outer {\n" + + " private static Integer VALUE = 42\n" + + " static class Inner {\n" + + " public final String value\n" + + " Inner(String string) {\n" + + " value = string\n" + + " }\n" + + " Inner() {\n" + + " this(VALUE.toString())\n" + + " }\n" + + " }\n" + + "}\n" + + "print new Outer.Inner().value\n", + }; + //@formatter:on + + runConformTest(sources, "42"); + } + @Test public void testCompileStatic6904() { //@formatter:off diff --git a/base/org.codehaus.groovy25/.checkstyle b/base/org.codehaus.groovy25/.checkstyle index 2a14e9b9d9..fd01c99290 100644 --- a/base/org.codehaus.groovy25/.checkstyle +++ b/base/org.codehaus.groovy25/.checkstyle @@ -78,10 +78,8 @@ - + - - diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java new file mode 100644 index 0000000000..2f445df4cb --- /dev/null +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java @@ -0,0 +1,106 @@ +/* + * 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.transformers; + +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys; +import org.codehaus.groovy.transform.stc.StaticTypesMarker; + +import static org.codehaus.groovy.ast.tools.GeneralUtils.*; + +/** + * Transformer for VariableExpression the bytecode backend wouldn't be able to + * handle otherwise. + */ +public class VariableExpressionTransformer { + + public Expression transformVariableExpression(VariableExpression expr) { + Expression trn = tryTransformDelegateToProperty(expr); + if (trn != null) { + return trn; + } + trn = tryTransformPrivateFieldAccess(expr); + if (trn != null) { + return trn; + } + return expr; + } + + private static Expression tryTransformDelegateToProperty(VariableExpression expr) { + // we need to transform variable expressions that go to a delegate + // to a property expression, as ACG would lose the information in + // processClassVariable before it reaches any makeCall, that could handle it + Object val = expr.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER); + if (val == null || val.equals(expr.getName())) return null; + + // TODO handle the owner and delegate cases better for nested scenarios and potentially remove the need for the implicit this case + VariableExpression receiver = new VariableExpression("owner".equals(val) ? (String) val : "delegate".equals(val) ? (String) val : "this"); + // GROOVY-9136 -- object expression should not overlap source range of property; property stands in for original variable expression + receiver.setLineNumber(expr.getLineNumber()); + receiver.setColumnNumber(expr.getColumnNumber()); + + PropertyExpression pexp = new PropertyExpression(receiver, expr.getName()); + pexp.getProperty().setSourcePosition(expr); + pexp.copyNodeMetaData(expr); + pexp.setImplicitThis(true); + + ClassNode owner = expr.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER); + if (owner != null) { + receiver.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, owner); + receiver.putNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER, val); + } + pexp.removeNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER); + + return pexp; + } + + private static Expression tryTransformPrivateFieldAccess(VariableExpression expr) { + FieldNode field = expr.getNodeMetaData(StaticTypesMarker.PV_FIELDS_ACCESS); + if (field == null) { + field = expr.getNodeMetaData(StaticTypesMarker.PV_FIELDS_MUTATION); + } + if (field != null) { + // access to a private field from a section of code that normally doesn't have access to it, like a + // closure or an inner class + /* GRECLIPSE edit -- GROOVY-6610 + VariableExpression receiver = new VariableExpression("this"); + PropertyExpression pexp = new PropertyExpression( + receiver, + expr.getName() + ); + pexp.setImplicitThis(true); + pexp.getProperty().setSourcePosition(expr); + */ + PropertyExpression pexp = !field.isStatic() ? thisPropX(true, expr.getName()) + : (PropertyExpression) propX(classX(field.getDeclaringClass()), expr.getName()); + pexp.getProperty().setSourcePosition(expr); + // GRECLIPSE end + // put the receiver inferred type so that the class writer knows that it will have to call a bridge method + pexp.getObjectExpression().putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, field.getDeclaringClass()); + // add inferred type information + pexp.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, field.getOriginType()); + return pexp; + } + return null; + } +} diff --git a/base/org.codehaus.groovy30/.checkstyle b/base/org.codehaus.groovy30/.checkstyle index e0eb9214a1..27bfd5759a 100644 --- a/base/org.codehaus.groovy30/.checkstyle +++ b/base/org.codehaus.groovy30/.checkstyle @@ -72,7 +72,7 @@ - + diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java new file mode 100644 index 0000000000..4e2281046e --- /dev/null +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java @@ -0,0 +1,92 @@ +/* + * 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.transformers; + +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys; +import org.codehaus.groovy.transform.stc.StaticTypesMarker; + +import static org.codehaus.groovy.ast.tools.GeneralUtils.*; + +/** + * Transformer for VariableExpression the bytecode backend wouldn't be able to + * handle otherwise. + */ +public class VariableExpressionTransformer { + + public Expression transformVariableExpression(final VariableExpression expr) { + Expression trn = tryTransformDelegateToProperty(expr); + if (trn == null) { + trn = tryTransformPrivateFieldAccess(expr); + } + return trn != null ? trn : expr; + } + + private static Expression tryTransformDelegateToProperty(final VariableExpression expr) { + // we need to transform variable expressions that go to a delegate + // to a property expression, as ACG would lose the information in + // processClassVariable before it reaches any makeCall, that could handle it + Object val = expr.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER); + if (val == null || val.equals(expr.getName())) return null; + + // TODO: handle the owner and delegate cases better for nested scenarios and potentially remove the need for the implicit this case + Expression receiver = varX("owner".equals(val) ? (String) val : "delegate".equals(val) ? (String) val : "this"); + // GROOVY-9136 -- object expression should not overlap source range of property; property stands in for original variable expression + receiver.setLineNumber(expr.getLineNumber()); + receiver.setColumnNumber(expr.getColumnNumber()); + + PropertyExpression pexp = propX(receiver, expr.getName()); + pexp.getProperty().setSourcePosition(expr); + pexp.copyNodeMetaData(expr); + pexp.setImplicitThis(true); + + ClassNode owner = expr.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER); + if (owner != null) { + receiver.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, owner); + receiver.putNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER, val); + } + pexp.removeNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER); + + return pexp; + } + + private static Expression tryTransformPrivateFieldAccess(final VariableExpression expr) { + FieldNode field = expr.getNodeMetaData(StaticTypesMarker.PV_FIELDS_ACCESS); + if (field == null) { + field = expr.getNodeMetaData(StaticTypesMarker.PV_FIELDS_MUTATION); + } + if (field != null) { + // access to a private field from a section of code that normally doesn't have access to it, like a closure or an inner class + PropertyExpression pexp = thisPropX(true, expr.getName()); + // GRECLIPSE add -- GROOVY-6610 + if (field.isStatic()) pexp = propX(classX(field.getDeclaringClass()), expr.getName()); + // GRECLIPSE end + // store the declaring class so that the class writer knows that it will have to call a bridge method + pexp.getObjectExpression().putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, field.getDeclaringClass()); + pexp.putNodeMetaData(StaticTypesMarker.DECLARATION_INFERRED_TYPE, field.getOriginType()); + pexp.getProperty().setSourcePosition(expr); + return pexp; + } + return null; + } +} diff --git a/base/org.codehaus.groovy40/.checkstyle b/base/org.codehaus.groovy40/.checkstyle index b24763ed63..08b65984c5 100644 --- a/base/org.codehaus.groovy40/.checkstyle +++ b/base/org.codehaus.groovy40/.checkstyle @@ -70,7 +70,7 @@ - + diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java new file mode 100644 index 0000000000..4e2281046e --- /dev/null +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java @@ -0,0 +1,92 @@ +/* + * 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.transformers; + +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys; +import org.codehaus.groovy.transform.stc.StaticTypesMarker; + +import static org.codehaus.groovy.ast.tools.GeneralUtils.*; + +/** + * Transformer for VariableExpression the bytecode backend wouldn't be able to + * handle otherwise. + */ +public class VariableExpressionTransformer { + + public Expression transformVariableExpression(final VariableExpression expr) { + Expression trn = tryTransformDelegateToProperty(expr); + if (trn == null) { + trn = tryTransformPrivateFieldAccess(expr); + } + return trn != null ? trn : expr; + } + + private static Expression tryTransformDelegateToProperty(final VariableExpression expr) { + // we need to transform variable expressions that go to a delegate + // to a property expression, as ACG would lose the information in + // processClassVariable before it reaches any makeCall, that could handle it + Object val = expr.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER); + if (val == null || val.equals(expr.getName())) return null; + + // TODO: handle the owner and delegate cases better for nested scenarios and potentially remove the need for the implicit this case + Expression receiver = varX("owner".equals(val) ? (String) val : "delegate".equals(val) ? (String) val : "this"); + // GROOVY-9136 -- object expression should not overlap source range of property; property stands in for original variable expression + receiver.setLineNumber(expr.getLineNumber()); + receiver.setColumnNumber(expr.getColumnNumber()); + + PropertyExpression pexp = propX(receiver, expr.getName()); + pexp.getProperty().setSourcePosition(expr); + pexp.copyNodeMetaData(expr); + pexp.setImplicitThis(true); + + ClassNode owner = expr.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER); + if (owner != null) { + receiver.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, owner); + receiver.putNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER, val); + } + pexp.removeNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER); + + return pexp; + } + + private static Expression tryTransformPrivateFieldAccess(final VariableExpression expr) { + FieldNode field = expr.getNodeMetaData(StaticTypesMarker.PV_FIELDS_ACCESS); + if (field == null) { + field = expr.getNodeMetaData(StaticTypesMarker.PV_FIELDS_MUTATION); + } + if (field != null) { + // access to a private field from a section of code that normally doesn't have access to it, like a closure or an inner class + PropertyExpression pexp = thisPropX(true, expr.getName()); + // GRECLIPSE add -- GROOVY-6610 + if (field.isStatic()) pexp = propX(classX(field.getDeclaringClass()), expr.getName()); + // GRECLIPSE end + // store the declaring class so that the class writer knows that it will have to call a bridge method + pexp.getObjectExpression().putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, field.getDeclaringClass()); + pexp.putNodeMetaData(StaticTypesMarker.DECLARATION_INFERRED_TYPE, field.getOriginType()); + pexp.getProperty().setSourcePosition(expr); + return pexp; + } + return null; + } +}