Permalink
Browse files

Support Groovy 2.1 @DelegatesTo annotation

See GRECLIPSE-1571
  • Loading branch information...
1 parent d8e53ef commit 2677643d383492ec4e0e68ba611dfa8741662683 @aeisenberg aeisenberg committed Feb 5, 2013
@@ -41,6 +41,7 @@ public static Test suite() throws Exception {
suite.addTestSuite(OperatorOverloadingInferencingTests.class);
suite.addTestSuite(SyntheticAccessorInferencingTests.class);
suite.addTestSuite(Groovy20InferencingTests.class);
+ suite.addTestSuite(Groovy21InferencingTests.class);
suite.addTestSuite(GenericsMappingTest.class);
return suite;
}
@@ -16,14 +16,9 @@
package org.eclipse.jdt.core.groovy.tests.search;
-import java.util.List;
-
import junit.framework.Test;
-import org.codehaus.groovy.ast.ASTNode;
-import org.codehaus.jdt.groovy.model.GroovyCompilationUnit;
import org.eclipse.jdt.core.tests.util.GroovyUtils;
-import org.eclipse.jdt.groovy.search.TypeInferencingVisitorWithRequestor;
/**
*
@@ -0,0 +1,130 @@
+ /*
+ * 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.eclipse.jdt.core.groovy.tests.search;
+
+import groovy.lang.DelegatesTo;
+import junit.framework.Test;
+
+import org.eclipse.jdt.core.tests.util.GroovyUtils;
+
+/**
+ * Tests for all Groovy 2.1 specific things
+ * for example, {@link DelegatesTo}
+ * @author Andrew Eisenberg
+ * @created Feb 5, 2013
+ */
+public class Groovy21InferencingTests extends AbstractInferencingTest {
+
+ public static Test suite() {
+ return buildTestSuite(Groovy21InferencingTests.class);
+ }
+
+ public Groovy21InferencingTests(String name) {
+ super(name);
+ }
+
+ // tests CompareToNullExpression
+ public void testDelegatesTo1() throws Exception {
+ if (GroovyUtils.GROOVY_LEVEL < 21 ) {
+ return;
+ }
+
+ String contents =
+ "class Other { }\n" +
+ "def meth(@DelegatesTo(Other) Closure c) { }\n" +
+ "meth { delegate }";
+
+ String toFind = "delegate";
+ int start = contents.lastIndexOf(toFind);
+ int end = start + toFind.length();
+ assertType(contents, start, end, "Other");
+ }
+ public void testDelegatesTo1a() throws Exception {
+ if (GroovyUtils.GROOVY_LEVEL < 21 ) {
+ return;
+ }
+
+ String contents =
+ "class Other { }\n" +
+ "def meth(@DelegatesTo(Other) c) { }\n" +
+ "meth { delegate }";
+
+ String toFind = "delegate";
+ int start = contents.lastIndexOf(toFind);
+ int end = start + toFind.length();
+ assertType(contents, start, end, "Other");
+ }
+ public void testDelegatesTo2() throws Exception {
+ if (GroovyUtils.GROOVY_LEVEL < 21 ) {
+ return;
+ }
+
+ String contents =
+ "class Other { int xxx }\n" +
+ "def meth(@DelegatesTo(Other) Closure c) { }\n" +
+ "meth { xxx }";
+
+ String toFind = "xxx";
+ int start = contents.lastIndexOf(toFind);
+ int end = start + toFind.length();
+ assertType(contents, start, end, "java.lang.Integer");
+ }
+ public void testDelegatesTo3() throws Exception {
+ if (GroovyUtils.GROOVY_LEVEL < 21 ) {
+ return;
+ }
+
+ String contents =
+ "def meth(@DelegatesTo(List) Closure c) { }\n" +
+ "meth { delegate }";
+
+ String toFind = "delegate";
+ int start = contents.lastIndexOf(toFind);
+ int end = start + toFind.length();
+ assertType(contents, start, end, "java.util.List");
+ }
+ public void testDelegatesTo4() throws Exception {
+ if (GroovyUtils.GROOVY_LEVEL < 21 ) {
+ return;
+ }
+
+ String contents =
+ "def meth(int x, int y, @DelegatesTo(List) Closure c) { }\n" +
+ "meth 1, 2, { delegate }";
+
+ String toFind = "delegate";
+ int start = contents.lastIndexOf(toFind);
+ int end = start + toFind.length();
+ assertType(contents, start, end, "java.util.List");
+ }
+
+ // expected to be broken
+ public void testDelegatesTo5() throws Exception {
+ if (GroovyUtils.GROOVY_LEVEL < 21 ) {
+ return;
+ }
+
+ String contents =
+ "def meth(int x, int y, @DelegatesTo(List<String) Closure c) { }\n" +
+ "meth { delegate }";
+
+ String toFind = "delegate";
+ int start = contents.lastIndexOf(toFind);
+ int end = start + toFind.length();
+ assertType(contents, start, end, "Search");
+ }
+}
@@ -118,6 +118,16 @@ public VisitCompleted(VisitStatus status) {
}
+ class Tuple {
+ ClassNode declaringType;
+ ASTNode declaration;
+
+ Tuple(ClassNode declaringType, ASTNode declaration) {
+ this.declaringType = declaringType;
+ this.declaration = declaration;
+ }
+ }
+
/**
* Set to true if debug mode is desired. Any exceptions will be spit to syserr. Also, after a visit, there will be a sanity
* check to ensure that all stacks are empty Only set to true if using a visitor that always visits the entire file
@@ -263,7 +273,7 @@ public VisitCompleted(VisitStatus status) {
* expression to find type information. this field is only applicable for {@link PropertyExpression}s and
* {@link MethodCallExpression}s.
*/
- private Stack<ClassNode> dependentDeclaringTypeStack;
+ private Stack<Tuple> dependentDeclarationStack;
/**
* Keeps track of the type of the type of the property field corresponding to each frame of the property expression.
@@ -288,7 +298,7 @@ public VisitCompleted(VisitStatus status) {
completeExpressionStack = new Stack<ASTNode>();
primaryTypeStack = new Stack<ClassNode>();
dependentTypeStack = new Stack<ClassNode>();
- dependentDeclaringTypeStack = new Stack<ClassNode>();
+ dependentDeclarationStack = new Stack<Tuple>();
}
public void visitCompilationUnit(ITypeRequestor requestor) {
@@ -850,7 +860,7 @@ public void visitImports(ModuleNode node) {
if (imp.getFieldNameExpr() != null) {
primaryTypeStack.push(type);
imp.getFieldNameExpr().visit(this);
- dependentDeclaringTypeStack.pop();
+ dependentDeclarationStack.pop();
dependentTypeStack.pop();
}
@@ -1188,8 +1198,12 @@ public void visitClosureExpression(ClosureExpression node) {
// Delegate is the declaring type of the enclosing call if one exists, or it is 'this'
CallAndType cat = scope.getEnclosingMethodCallExpression();
if (cat != null) {
- scope.addVariable("delegate", cat.declaringType, VariableScope.CLOSURE_CLASS);
- scope.addVariable("getDelegate", cat.declaringType, VariableScope.CLOSURE_CLASS);
+ ClassNode declaringType = cat.declaringType;
+ if (cat.delegatesToClosures.containsKey(node)) {
+ declaringType = cat.delegatesToClosures.get(node);
+ }
+ scope.addVariable("delegate", declaringType, VariableScope.CLOSURE_CLASS);
+ scope.addVariable("getDelegate", declaringType, VariableScope.CLOSURE_CLASS);
} else {
ClassNode thisType = scope.getThis();
scope.addVariable("delegate", thisType, VariableScope.CLOSURE_CLASS);
@@ -1521,8 +1535,8 @@ public void visitMethodCallExpression(MethodCallExpression node) {
ClassNode exprType = dependentTypeStack.pop();
// this is the inferred declaring type of this method
- ClassNode exprDeclaringType = dependentDeclaringTypeStack.pop();
- CallAndType call = new CallAndType(node, exprDeclaringType);
+ Tuple t = dependentDeclarationStack.pop();
+ CallAndType call = new CallAndType(node, t.declaringType, t.declaration);
completeExpressionStack.pop();
@@ -1543,7 +1557,7 @@ public void visitMethodCallExpression(MethodCallExpression node) {
if (node.isSpreadSafe()) {
exprType = createParameterizedList(exprType);
}
- handleCompleteExpression(node, exprType, exprDeclaringType);
+ handleCompleteExpression(node, exprType, t.declaringType);
scopes.peek().forgetCurrentNode();
}
@@ -1622,7 +1636,7 @@ public void visitPropertyExpression(PropertyExpression node) {
ClassNode exprType = dependentTypeStack.pop();
// don't care about either of these
- dependentDeclaringTypeStack.pop();
+ dependentDeclarationStack.pop();
completeExpressionStack.pop();
// if this property expression is the primary of a larger expression,
@@ -1846,12 +1860,12 @@ private void handleCompleteExpression(Expression node, ClassNode exprType, Class
scope));
}
- private void postVisit(Expression node, ClassNode type, ClassNode declaringType) {
+ private void postVisit(Expression node, ClassNode type, ClassNode declaringType, ASTNode declaration) {
if (isPrimaryExpression(node)) {
primaryTypeStack.push(type);
} else if (isDependentExpression(node)) {
dependentTypeStack.push(type);
- dependentDeclaringTypeStack.push(declaringType);
+ dependentDeclarationStack.push(new Tuple(declaringType, declaration));
}
}
@@ -1965,10 +1979,10 @@ private boolean handleRequestor(Expression node, ClassNode primaryType, TypeLook
}
switch (status) {
case CONTINUE:
- postVisit(node, result.type, rememberedDeclaringType);
+ postVisit(node, result.type, rememberedDeclaringType, result.declaration);
return true;
case CANCEL_BRANCH:
- postVisit(node, result.type, rememberedDeclaringType);
+ postVisit(node, result.type, rememberedDeclaringType, result.declaration);
return false;
case CANCEL_MEMBER:
case STOP_VISIT:
@@ -2414,7 +2428,7 @@ private void postVisitSanityCheck() {
"Inferencing engine in invalid state after visitor completed. All stacks should be empty after visit completed.");
Assert.isTrue(primaryTypeStack.isEmpty(),
"Inferencing engine in invalid state after visitor completed. All stacks should be empty after visit completed.");
- Assert.isTrue(dependentDeclaringTypeStack.isEmpty(),
+ Assert.isTrue(dependentDeclarationStack.isEmpty(),
"Inferencing engine in invalid state after visitor completed. All stacks should be empty after visit completed.");
Assert.isTrue(dependentTypeStack.isEmpty(),
"Inferencing engine in invalid state after visitor completed. All stacks should be empty after visit completed.");
@@ -38,6 +38,7 @@
import java.util.regex.Matcher;
import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
@@ -47,8 +48,11 @@
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.Variable;
+import org.codehaus.groovy.ast.expr.ClassExpression;
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.ast.expr.TupleExpression;
import org.codehaus.groovy.runtime.DateGroovyMethods;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.DefaultGroovyStaticMethods;
@@ -125,6 +129,16 @@
// public static final ClassNode PLUGIN6_GM_CLASS_NODE = ClassHelper
// .make(org.codehaus.groovy.vmplugin.v6.PluginDefaultGroovyMethods.class);
+ // only exists on 2.1 and later
+ public static ClassNode DELEGATES_TO;
+ static {
+ try {
+ DELEGATES_TO = ClassHelper.make(Class.forName("groovy.lang.DelegatesTo"));
+ } catch (ClassNotFoundException e) {
+ DELEGATES_TO = null;
+ }
+ }
+
public static Set<ClassNode> ALL_DEFAULT_CATEGORIES;
static {
// add all of the known DGM classes. Order counts since we look up earlier in the list before later and need to
@@ -188,13 +202,51 @@ public String getTypeSignature() {
}
public static class CallAndType {
- public CallAndType(MethodCallExpression call, ClassNode declaringType) {
+
+ public CallAndType(MethodCallExpression call, ClassNode declaringType, ASTNode declaration) {
this.call = call;
this.declaringType = declaringType;
+ this.declaration = declaration;
+
+ // the @DelegatesTo Groovy 2.1 annotation
+ if (DELEGATES_TO != null && declaration instanceof MethodNode) {
+ MethodNode methodDecl = (MethodNode) declaration;
+ if (methodDecl.getParameters() != null) {
+ Expression argsExpr = call.getArguments();
+ List<Expression> args = null;
+ if (argsExpr instanceof TupleExpression) {
+ args = ((TupleExpression) argsExpr).getExpressions();
+ }
+ if (args != null) {
+ Parameter[] parameters = methodDecl.getParameters();
+ for (int i = 0; i < parameters.length; i++) {
+ Parameter p = parameters[i];
+ List<AnnotationNode> annotations = p.getAnnotations();
+ if (annotations != null) {
+ for (AnnotationNode annotation : annotations) {
+ if (annotation.getClassNode().getName().equals(DELEGATES_TO.getName()) && args.size() > i
+ && args.get(i) instanceof ClosureExpression
+ && annotation.getMember("value") instanceof ClassExpression) {
+ delegatesToClosures = new HashMap<ClosureExpression, ClassNode>(3);
+ delegatesToClosures.put((ClosureExpression) args.get(i), annotation.getMember("value")
+ .getType());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (delegatesToClosures == null) {
+ delegatesToClosures = Collections.emptyMap();
+
+ }
}
+ public final ASTNode declaration;
public final MethodCallExpression call;
public final ClassNode declaringType;
+ public Map<ClosureExpression, ClassNode> delegatesToClosures;
}
/**

0 comments on commit 2677643

Please sign in to comment.