GROOVY-6323, GROOVY-6325, GROOVY-6332: make Closure not sue static scope and add transformer for VariableExpression in the static compiler
diff --git a/src/main/org/codehaus/groovy/classgen/VariableScopeVisitor.java b/src/main/org/codehaus/groovy/classgen/VariableScopeVisitor.java
index 5f84cfa..36fe89a 100644
--- a/src/main/org/codehaus/groovy/classgen/VariableScopeVisitor.java
+++ b/src/main/org/codehaus/groovy/classgen/VariableScopeVisitor.java
@@ -422,6 +422,7 @@
currentScope.putDeclaredVariable(var);
}
+ currentScope.setInStaticContext(false);
super.visitClosureExpression(expression);
markClosureSharedVariables();
diff --git a/src/main/org/codehaus/groovy/transform/sc/transformers/StaticCompilationTransformer.java b/src/main/org/codehaus/groovy/transform/sc/transformers/StaticCompilationTransformer.java
index 1effdc9..93708c2 100644
--- a/src/main/org/codehaus/groovy/transform/sc/transformers/StaticCompilationTransformer.java
+++ b/src/main/org/codehaus/groovy/transform/sc/transformers/StaticCompilationTransformer.java
@@ -59,6 +59,7 @@
private final BinaryExpressionTransformer binaryExpressionTransformer = new BinaryExpressionTransformer(this);
private final ClosureExpressionTransformer closureExpressionTransformer = new ClosureExpressionTransformer(this);
private final BooleanExpressionTransformer booleanExpressionTransformer = new BooleanExpressionTransformer(this);
+ private final VariableExpressionTransformer variableExpressionTransformer = new VariableExpressionTransformer();
public StaticCompilationTransformer(final SourceUnit unit) {
this.unit = unit;
@@ -102,6 +103,9 @@
if (expr instanceof BooleanExpression) {
return booleanExpressionTransformer.transformBooleanExpression((BooleanExpression)expr);
}
+ if (expr instanceof VariableExpression) {
+ return variableExpressionTransformer.transformVariableExpression((VariableExpression)expr);
+ }
return super.transform(expr);
}
diff --git a/src/main/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java b/src/main/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java
new file mode 100644
index 0000000..b475bde
--- /dev/null
+++ b/src/main/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2003-2013 the original author or authors.
+ *
+ * Licensed 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.expr.Expression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.transform.stc.StaticTypesMarker;
+
+/**
+ * Transformer for VariableExpression the bytecode backend wouldn't be able to
+ * handle otherwise.
+ * @author <a href="mailto:blackdrag@gmx.org">Jochen "blackdrag" Theodorou</a>
+ */
+public class VariableExpressionTransformer {
+
+ public Expression transformVariableExpression(VariableExpression expr) {
+ // we need to transform variable expressions that go to a delegate
+ // to a property expression, as ACG would loose the information
+ // in processClassVariable before it reaches any makeCall, that could
+ // handle it
+ Object val = expr.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER);
+ if (val==null) return expr;
+ PropertyExpression pexp = new PropertyExpression(new VariableExpression("this"), expr.getName());
+ pexp.copyNodeMetaData(expr);
+ pexp.setImplicitThis(true);
+ return pexp;
+ }
+}
diff --git a/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
index 072a6da..63f3a69 100644
--- a/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -345,7 +345,10 @@
pe.setImplicitThis(true);
if (visitPropertyExpressionSilent(pe, vexp)) {
storeType(vexp, getType(pe));
- vexp.putNodeMetaData(StaticTypesMarker.READONLY_PROPERTY, pe.getNodeMetaData(StaticTypesMarker.READONLY_PROPERTY));
+ Object val = pe.getNodeMetaData(StaticTypesMarker.READONLY_PROPERTY);
+ if (val!=null) vexp.putNodeMetaData(StaticTypesMarker.READONLY_PROPERTY,val);
+ val = pe.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER);
+ if (val!=null) vexp.putNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER,val);
return;
}
@@ -957,15 +960,15 @@
FieldNode field = current.getDeclaredField(propertyName);
field = allowStaticAccessToMember(field, staticOnly);
- if (storeField(field, isAttributeExpression, pexp, objectExpressionType, visitor)) return true;
+ if (storeField(field, isAttributeExpression, pexp, objectExpressionType, visitor, receiver.getData())) return true;
PropertyNode propertyNode = current.getProperty(propertyName);
propertyNode = allowStaticAccessToMember(propertyNode, staticOnly);
- if (storeProperty(propertyNode, pexp, objectExpressionType, visitor)) return true;
+ if (storeProperty(propertyNode, pexp, objectExpressionType, visitor, receiver.getData())) return true;
boolean isThisExpression = objectExpression instanceof VariableExpression &&
((VariableExpression)objectExpression).isThisExpression();
- if (storeField(field, isThisExpression, pexp, objectExpressionType, visitor)) return true;
+ if (storeField(field, isThisExpression, pexp, objectExpressionType, visitor, receiver.getData())) return true;
MethodNode getter = current.getGetterMethod("get" + capName);
getter = allowStaticAccessToMember(getter, staticOnly);
@@ -983,6 +986,8 @@
ClassNode cn = inferReturnTypeGenerics(current, getter, ArgumentListExpression.EMPTY_ARGUMENTS);
storeInferredTypeForPropertyExpression(pexp, cn);
pexp.removeNodeMetaData(StaticTypesMarker.READONLY_PROPERTY);
+ String delegationData = receiver.getData();
+ if (delegationData!=null) pexp.putNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER, delegationData);
return true;
}
} else {
@@ -996,6 +1001,8 @@
SetterInfo info = new SetterInfo(current, setter);
BinaryExpression enclosingBinaryExpression = typeCheckingContext.getEnclosingBinaryExpression();
if (enclosingBinaryExpression!=null) putSetterInfo(enclosingBinaryExpression.getLeftExpression(), info);
+ String delegationData = receiver.getData();
+ if (delegationData!=null) pexp.putNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER, delegationData);
return true;
} else if (getter!=null) {
pexp.putNodeMetaData(StaticTypesMarker.READONLY_PROPERTY, true);
@@ -1003,7 +1010,7 @@
}
foundGetterOrSetter = foundGetterOrSetter || setter!=null || getter!=null;
- if (storeField(field, true, pexp, objectExpressionType, visitor)) return true;
+ if (storeField(field, true, pexp, objectExpressionType, visitor, receiver.getData())) return true;
// if the property expression is an attribute expression (o.@attr), then
// we stop now, otherwise we must check the parent class
if (/*!isAttributeExpression && */current.getSuperClass() != null) {
@@ -1034,11 +1041,13 @@
if (propertyType==null) propertyType = getTypeForSpreadExpression(testClass, objectExpressionType, pexp);
if (propertyType==null) continue;
if (visitor!=null) {
- // todo : type inferrence on maps and lists, if possible
+ // todo : type inference on maps and lists, if possible
PropertyNode node = new PropertyNode(propertyName, Opcodes.ACC_PUBLIC, propertyType, objectExpressionType, null, null, null);
visitor.visitProperty(node);
}
storeType(pexp, propertyType);
+ String delegationData = receiver.getData();
+ if (delegationData!=null) pexp.putNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER, delegationData);
return true;
}
return foundGetterOrSetter;
@@ -1144,18 +1153,24 @@
storeType(expressionToStoreOn, type);
}
- private boolean storeField(FieldNode field, boolean returnTrueIfFieldExists, PropertyExpression expressionToStoreOn, ClassNode receiver, ClassCodeVisitorSupport visitor) {
+ private boolean storeField(FieldNode field, boolean returnTrueIfFieldExists, PropertyExpression expressionToStoreOn, ClassNode receiver, ClassCodeVisitorSupport visitor, String delegationData) {
if (field==null || !returnTrueIfFieldExists) return false;
if (visitor != null) visitor.visitField(field);
storeWithResolve(field.getOriginType(), receiver, field.getDeclaringClass(), field.isStatic(), expressionToStoreOn);
checkOrMarkPrivateAccess(field);
+ if (delegationData!=null) {
+ expressionToStoreOn.putNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER, delegationData);
+ }
return true;
}
- private boolean storeProperty(PropertyNode propertyNode, PropertyExpression expressionToStoreOn, ClassNode receiver, ClassCodeVisitorSupport visitor) {
+ private boolean storeProperty(PropertyNode propertyNode, PropertyExpression expressionToStoreOn, ClassNode receiver, ClassCodeVisitorSupport visitor, String delegationData) {
if (propertyNode == null) return false;
if (visitor != null) visitor.visitProperty(propertyNode);
storeWithResolve(propertyNode.getOriginType(), receiver, propertyNode.getDeclaringClass(), propertyNode.isStatic(), expressionToStoreOn);
+ if (delegationData!=null) {
+ expressionToStoreOn.putNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER, delegationData);
+ }
return true;
}
diff --git a/src/test/groovy/transform/stc/DelegatesToSTCTest.groovy b/src/test/groovy/transform/stc/DelegatesToSTCTest.groovy
index b6f4af0..d472a05 100644
--- a/src/test/groovy/transform/stc/DelegatesToSTCTest.groovy
+++ b/src/test/groovy/transform/stc/DelegatesToSTCTest.groovy
@@ -13,18 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
-
-
-
package groovy.transform.stc
/**
- * Units tests aimed at testing the behaviour of {@link DelegatesTo} in combination
+ * Units tests aimed at testing the behavior of {@link DelegatesTo} in combination
* with static type checking.
*
* @author Cedric Champeau
+ * @author <a href="mailto:blackdrag@gmx.org">Jochen "blackdrag" Theodorou</a>
*/
class DelegatesToSTCTest extends StaticTypeCheckingTestCase {
void testShouldChooseMethodFromOwner() {
@@ -633,4 +629,105 @@
''', 'Cannot find matching method'
}
+ // GROOVY-6323, GROOVY-6325, GROOVY-6332
+ void testStaticContextAndProperty() {
+ assertScript '''
+ class MyCar {
+ String brand
+ String model
+ }
+
+ class MyCarMain {
+ MyCar configureCar(@DelegatesTo(MyCar) Closure closure) {
+ def car = new MyCar()
+ closure.delegate = car
+ closure.resolveStrategy = Closure.DELEGATE_FIRST
+ closure()
+ car
+ }
+ static void main(String[] args) {
+ def main = new MyCarMain()
+ def car = main.configureCar {
+ brand = "BMW"
+ model = brand + " X5"
+ }
+ assert car.model == "BMW X5"
+ }
+ }
+ MyCarMain.main()
+ '''
+
+ assertScript '''
+ class MyCar {
+ private String _brand
+ private String _model
+
+ String getBrand() {
+ return _brand
+ }
+
+ void setBrand(String brand) {
+ _brand = brand
+ }
+
+ String getModel() {
+ return _model
+ }
+
+ void setModel(String model) {
+ _model = model
+ }
+ }
+
+ class MyCarMain {
+ MyCar configureCar(@DelegatesTo(value = MyCar, strategy = Closure.DELEGATE_FIRST) Closure closure) {
+ def car = new MyCar()
+ closure.delegate = car
+ closure.resolveStrategy = Closure.DELEGATE_FIRST
+ closure()
+ car
+ }
+
+ static void main(String[] args) {
+ def main = new MyCarMain()
+ def car = main.configureCar {
+ brand = "BMW"
+ model = brand
+ }
+ assert car.model == "BMW"
+ }
+ }
+ MyCarMain.main()
+ '''
+
+ assertScript '''
+ class Car {
+ private String _brand
+ String getBrand() { _brand }
+ void setBrand(String brand) { _brand = brand }
+ }
+
+ class Builder {
+ def <T> T configure(@DelegatesTo.Target Class<T> target, @DelegatesTo(genericTypeIndex=0) Closure cl) {
+ def obj = target.newInstance()
+ cl.delegate = obj
+ cl.resolveStrategy = Closure.DELEGATE_FIRST
+ cl.call()
+ obj
+ }
+ }
+
+ class Main {
+ void run() {
+ def builder = new Builder()
+ def car = builder.configure(Car) {
+ brand = brand
+ }
+ }
+ }
+
+ new Main().run()
+ '''
+ }
+
}