This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new a708f64 GROOVY-6510: `@Category`: preserve implicit-this semantic
within closure
a708f64 is described below
commit a708f649e86f832390f40c9374c530234a7efa4b
Author: Eric Milles <[email protected]>
AuthorDate: Thu Feb 10 15:42:07 2022 -0600
GROOVY-6510: `@Category`: preserve implicit-this semantic within closure
---
.../transform/CategoryASTTransformation.java | 21 +++++--
src/test/groovy/lang/CategoryAnnotationTest.groovy | 67 +++++++++++++++++++---
2 files changed, 77 insertions(+), 11 deletions(-)
diff --git
a/src/main/java/org/codehaus/groovy/transform/CategoryASTTransformation.java
b/src/main/java/org/codehaus/groovy/transform/CategoryASTTransformation.java
index 26b1dea..72662fa 100644
--- a/src/main/java/org/codehaus/groovy/transform/CategoryASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/CategoryASTTransformation.java
@@ -32,6 +32,7 @@ import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
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.VariableExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
@@ -47,6 +48,8 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
+import static java.util.Collections.addAll;
+import static org.apache.groovy.ast.tools.ExpressionUtils.isThisExpression;
import static org.codehaus.groovy.ast.tools.ClosureUtils.getParametersSafe;
import static org.codehaus.groovy.ast.tools.ClosureUtils.hasImplicitParameter;
import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
@@ -119,6 +122,8 @@ public class CategoryASTTransformation implements
ASTTransformation {
varStack.add(names);
ClassCodeExpressionTransformer transformer = new
ClassCodeExpressionTransformer() {
+ private boolean inClosure; // GROOVY-6510: track closure
containment
+
private void addVariablesToStack(final Parameter[] parameter) {
Set<String> names = new HashSet<>(varStack.getLast());
for (Parameter p : parameter) names.add(p.getName());
@@ -144,17 +149,28 @@ public class CategoryASTTransformation implements
ASTTransformation {
Expression thisExpression = createThisExpression();
thisExpression.setSourcePosition(ve);
return thisExpression;
- } else if (!ve.isSuperExpression() &&
!varStack.getLast().contains(ve.getName())) {
+ } else if (!inClosure && !ve.isSuperExpression() &&
!varStack.getLast().contains(ve.getName())) {
PropertyExpression pe = new
PropertyExpression(createThisExpression(), ve.getName());
pe.setSourcePosition(ve);
return pe;
}
+ } else if (expression instanceof MethodCallExpression) {
+ MethodCallExpression mce = (MethodCallExpression)
expression;
+ if (inClosure && mce.isImplicitThis() &&
isThisExpression(mce.getObjectExpression())) {
+ // GROOVY-6510: preserve implicit-this semantics
+ mce.setArguments(transform(mce.getArguments()));
+ mce.setMethod(transform(mce.getMethod()));
+ return mce;
+ }
} else if (expression instanceof ClosureExpression) {
ClosureExpression ce = (ClosureExpression) expression;
addVariablesToStack(hasImplicitParameter(ce) ?
params(param(ClassHelper.OBJECT_TYPE, "it")) : getParametersSafe(ce));
ce.getVariableScope().putReferencedLocalVariable(selfParameter.get());
+ addAll(varStack.getLast(), "owner", "delegate",
"thisObject");
+ boolean closure = inClosure; inClosure = true;
ce.getCode().visit(this);
varStack.removeLast();
+ inClosure = closure;
}
return super.transform(expression);
}
@@ -176,9 +192,6 @@ public class CategoryASTTransformation implements
ASTTransformation {
@Override
public void visitClosureExpression(final ClosureExpression
expression) {
- addVariablesToStack(getParametersSafe(expression));
- super.visitClosureExpression(expression);
- varStack.removeLast();
}
@Override
diff --git a/src/test/groovy/lang/CategoryAnnotationTest.groovy
b/src/test/groovy/lang/CategoryAnnotationTest.groovy
index 382b660..593caaa 100644
--- a/src/test/groovy/lang/CategoryAnnotationTest.groovy
+++ b/src/test/groovy/lang/CategoryAnnotationTest.groovy
@@ -100,7 +100,7 @@ final class CategoryAnnotationTest {
interface Guy {
Type getType()
}
- class MyGuyver implements Guy {
+ class McGuyver implements Guy {
Type type
}
@Category(Guy)
@@ -114,7 +114,7 @@ final class CategoryAnnotationTest {
}
def atype = new Type(name: 'String')
- def onetest = new MyGuyver(type:atype)
+ def onetest = new McGuyver(type:atype)
use(Naming) {
assert onetest.getTypeName() == onetest.getType().getName()
@@ -129,24 +129,77 @@ final class CategoryAnnotationTest {
String getName()
List getMessages()
}
- class MyGuyver implements Guy {
+ class McGuyver implements Guy {
List messages
String name
}
@Category(Guy)
class Filtering {
List process() {
- this.messages.findAll{it.name != this.getName()}
+ this.messages.findAll{ it.name != this.getName() }
}
}
- def onetest = new MyGuyver(name: 'coucou',
+ def onetest = new McGuyver(name: 'coucou',
messages: [['name':'coucou'], ['name':'test'],
['name':'salut']]
)
- Guy.mixin Filtering
+ Guy.mixin(Filtering)
- assert onetest.process() == onetest.messages.findAll{it.name !=
onetest.getName()}
+ assert onetest.process() == onetest.messages.findAll{ it.name !=
onetest.getName() }
+ '''
+ }
+
+ @Test // GROOVY-6510
+ void testClosureUsingImplicitThis() {
+ assertScript '''
+ @Category(Number)
+ class NumberCategory {
+ def foo() {
+ def bar = { ->
+ baz() // do not want "$this.baz()"
+ }
+ bar.resolveStrategy = Closure.DELEGATE_FIRST
+ bar.delegate = new NumberDelegate(this)
+ bar.call()
+ }
+ }
+
+ class NumberDelegate {
+ private final Number n
+ NumberDelegate(Number n) { this.n = n }
+ String baz() { 'number ' + n.intValue() }
+ }
+
+ use(NumberCategory) {
+ String result = 1.foo()
+ assert result == 'number 1'
+ }
+ '''
+
+ assertScript '''
+ @Category(Number)
+ class NumberCategory {
+ def foo() {
+ def bar = { ->
+ baz // do not want "$this.baz"
+ }
+ bar.resolveStrategy = Closure.DELEGATE_FIRST
+ bar.delegate = new NumberDelegate(this)
+ bar.call()
+ }
+ }
+
+ class NumberDelegate {
+ private final Number n
+ NumberDelegate(Number n) { this.n = n }
+ String getBaz() { 'number ' + n.intValue() }
+ }
+
+ use(NumberCategory) {
+ String result = 1.foo()
+ assert result == 'number 1'
+ }
'''
}