From fa234686603ab28ad64aa0f51f59407c77176fa7 Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Mon, 5 Mar 2018 10:55:30 -0600 Subject: [PATCH] Fix for issue #415: add support for @DelegatesTo(type='pack.Type') --- .../search/Groovy21InferencingTests.java | 54 ++++++------ .../jdt/groovy/search/VariableScope.java | 88 ++++++++----------- 2 files changed, 66 insertions(+), 76 deletions(-) diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/Groovy21InferencingTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/Groovy21InferencingTests.java index 129819d878..1dd315bc30 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/Groovy21InferencingTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/Groovy21InferencingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2017 the original author or authors. + * Copyright 2009-2018 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. @@ -37,9 +37,8 @@ public void testDelegatesToValue() { "meth { delegate }"; String toFind = "delegate"; - int start = contents.lastIndexOf(toFind); - int end = start + toFind.length(); - assertType(contents, start, end, "Other"); + int offset = contents.lastIndexOf(toFind); + assertType(contents, offset, offset + toFind.length(), "Other"); } @Test @@ -50,9 +49,8 @@ public void testDelegatesToValue2() { "meth { delegate }"; String toFind = "delegate"; - int start = contents.lastIndexOf(toFind); - int end = start + toFind.length(); - assertType(contents, start, end, "Other"); + int offset = contents.lastIndexOf(toFind); + assertType(contents, offset, offset + toFind.length(), "Other"); } @Test @@ -63,9 +61,8 @@ public void testDelegatesToValue3() { "meth { xxx }"; String toFind = "xxx"; - int start = contents.lastIndexOf(toFind); - int end = start + toFind.length(); - assertType(contents, start, end, "java.lang.Integer"); + int offset = contents.lastIndexOf(toFind); + assertType(contents, offset, offset + toFind.length(), "java.lang.Integer"); } @Test @@ -75,9 +72,8 @@ public void testDelegatesToValue4() { "meth { delegate }"; String toFind = "delegate"; - int start = contents.lastIndexOf(toFind); - int end = start + toFind.length(); - assertType(contents, start, end, "java.util.List"); + int offset = contents.lastIndexOf(toFind); + assertType(contents, offset, offset + toFind.length(), "java.util.List"); } @Test @@ -87,9 +83,8 @@ public void testDelegatesToValue5() { "meth 1, 2, { delegate }"; String toFind = "delegate"; - int start = contents.lastIndexOf(toFind); - int end = start + toFind.length(); - assertType(contents, start, end, "java.util.List"); + int offset = contents.lastIndexOf(toFind); + assertType(contents, offset, offset + toFind.length(), "java.util.List"); } @Test // expected to be broken (due to missing closing angle bracket on type) @@ -99,9 +94,8 @@ public void testDelegatesToValue6() { "meth { delegate }"; String toFind = "delegate"; - int start = contents.lastIndexOf(toFind); - int end = start + toFind.length(); - assertType(contents, start, end, "Search"); + int offset = contents.lastIndexOf(toFind); + assertType(contents, offset, offset + toFind.length(), "Search"); } @Test @@ -217,6 +211,17 @@ public void testDelegatesToTarget5() { assertUnknownConfidence(contents, offset, offset + 1, "B", false); } + @Test // https://github.com/groovy/groovy-eclipse/issues/415 + public void testDelegatesToTypeName() { + String contents = + "def meth(int x, int y, @DelegatesTo(type='java.util.List') Closure c) { }\n" + + "meth 1, 2, { delegate }"; + + String toFind = "delegate"; + int offset = contents.lastIndexOf(toFind); + assertType(contents, offset, offset + toFind.length(), "java.util.List"); + } + @Test // https://github.com/groovy/groovy-eclipse/issues/389 public void testEnumOverrides() { String contents = @@ -294,12 +299,11 @@ public void testTypeCheckingExtension() { " robot.move \"left\"\n" + "}"; - int start = contents.lastIndexOf("move"); - int end = start + "move".length(); - assertType(contents, start, end, "java.lang.Void"); - start = contents.lastIndexOf("robot"); - end = start + "robot".length(); - assertType(contents, start, end, "Robot"); + int offset = contents.lastIndexOf("move"); + assertType(contents, offset, offset + "move".length(), "java.lang.Void"); + + offset = contents.lastIndexOf("robot"); + assertType(contents, offset, offset + "robot".length(), "Robot"); // also, just make sure no problems env.fullBuild(project.getFullPath()); diff --git a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/VariableScope.java b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/VariableScope.java index ca30502baf..fa0a147aaf 100644 --- a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/VariableScope.java +++ b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/VariableScope.java @@ -15,6 +15,11 @@ */ package org.eclipse.jdt.groovy.search; +import static org.codehaus.groovy.ast.tools.GeneralUtils.castX; +import static org.codehaus.groovy.ast.tools.GenericsUtils.parseClassNodesFromString; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.evaluateExpression; + +import java.beans.Introspector; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.DataInputStream; @@ -60,6 +65,7 @@ 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.control.CompilationUnit; import org.codehaus.groovy.runtime.DateGroovyMethods; import org.codehaus.groovy.runtime.DefaultGroovyMethods; import org.codehaus.groovy.runtime.DefaultGroovyStaticMethods; @@ -234,32 +240,26 @@ public CallAndType(MethodCallExpression call, ASTNode declaration, ClassNode dec Expression delegatesToGenericTypeIndex = annotation.getMember("genericTypeIndex"); Integer strategy = null, generics = null; - /*if (delegatesToStrategy != null) { - strategy = (Integer) org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.evaluateExpression(org.codehaus.groovy.ast.tools.GeneralUtils.castX(INTEGER_CLASS_NODE, delegatesToStrategy), enclosingModule.getUnit().getConfig()); + if (delegatesToStrategy != null) { + strategy = (Integer) evaluateExpression(castX(INTEGER_CLASS_NODE, delegatesToStrategy), enclosingModule.getUnit().getConfig()); } if (delegatesToGenericTypeIndex != null) { - strategy = (Integer) org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.evaluateExpression(org.codehaus.groovy.ast.tools.GeneralUtils.castX(INTEGER_CLASS_NODE, delegatesToGenericTypeIndex), enclosingModule.getUnit().getConfig()); - }*/ - if (delegatesToStrategy instanceof ConstantExpression) { - strategy = Integer.valueOf(delegatesToStrategy.getText()); - } - if (delegatesToGenericTypeIndex instanceof ConstantExpression) { - generics = Integer.valueOf(delegatesToGenericTypeIndex.getText()); + generics = (Integer) evaluateExpression(castX(INTEGER_CLASS_NODE, delegatesToGenericTypeIndex), enclosingModule.getUnit().getConfig()); } // handle three modes: @DelegatesTo(Type.class), @DelegatesTo(type="pack.Type"), @DelegatesTo(target="name", genericTypeIndex=i) if (delegatesToValue instanceof ClassExpression && !delegatesToValue.getType().getName().equals("groovy.lang.DelegatesTo$Target")) { addDelegatesToClosure(closure, delegatesToValue.getType(), strategy); - } else if (delegatesToType instanceof ConstantExpression && !"".equals(delegatesToType.getText())) { - //ClassNode[] resolved = org.codehaus.groovy.ast.tools.GenericsUtils.parseClassNodesFromString(delegatesToType.getText(), enclosingModule.getContext(), an org.codehaus.groovy.control.CompilationUnit, methodNode, delegatesToType); - //addDelegatesToClosure(closure, resolved[0], strategy); + } else if (delegatesToType instanceof ConstantExpression && !"".equals(delegatesToType.getText())) { CompilationUnit compilationUnit = null; // TODO + ClassNode[] resolved = parseClassNodesFromString(delegatesToType.getText(), enclosingModule.getContext(), compilationUnit, methodNode, delegatesToType); + addDelegatesToClosure(closure, resolved[0], strategy); } else if (delegatesToValue == null || (delegatesToValue instanceof ClassExpression && delegatesToValue.getType().getName().equals("groovy.lang.DelegatesTo$Target"))) { int j = indexOfDelegatesToTarget(parameters, delegatesToTarget.getText()); if (j >= 0 && j < arguments.size()) { Expression target = arguments.get(j); - ClassNode targetType = target.getType(); // TODO: lookup expression type (unless j is 0 and it's a category method) + ClassNode targetType = target.getType(); // TODO: Look up expression type (unless j is 0 and it's a category method). if (generics != null && generics >= 0 && targetType.isUsingGenerics()) { targetType.getGenericsTypes()[generics].getType(); } @@ -270,6 +270,7 @@ public CallAndType(MethodCallExpression call, ASTNode declaration, ClassNode dec } } } + // TODO: Remove when minimum supported Groovy runtime is 2.5 if (delegatesTo == null) { if (arguments.get(0) instanceof ClosureExpression && methodNode.getName().matches("build|do(Later|Outside)|edt(Builder)?") && @@ -367,29 +368,29 @@ private static class SharedState { } /** - * Null for the top level scope + * Null for the top level scope. */ private VariableScope parent; /** - * Shared with parent scopes + * State shared with all scopes. */ private SharedState shared; /** - * AST node for this scope, typically, a block, closure, or body declaration + * AST node for this scope, typically, a block, closure, or body declaration. */ /*package*/ ASTNode scopeNode; /** - * number of parameters of current method call or -1 if not a method call + * Is the current node not the RHS of a dotted expression? */ private boolean isPrimaryNode; private final boolean isStaticScope; /** - * Category that will be declared in the next scope + * Category that will be declared in the next scope. */ private ClassNode categoryBeingDeclared; @@ -409,8 +410,7 @@ public VariableScope(VariableScope parent, ASTNode enclosingNode, boolean isStat (getEnclosingClosureScope() == null); // if in a closure, items may be found on delegate or owner // determine if scope belongs to script body - if (enclosingNode instanceof ClassNode || - enclosingNode instanceof FieldNode) { + if (enclosingNode instanceof ClassNode || enclosingNode instanceof FieldNode) { this.shared.isRunMethod = false; } else if (enclosingNode instanceof MethodNode) { this.shared.isRunMethod = ((MethodNode) enclosingNode).isScriptBody(); @@ -671,20 +671,11 @@ public int getEnclosingClosureResolveStrategy() { return null; } + // NOTE: JDTClassNode contains very similar methods private static PropertyNode createPropertyNodeForMethodNode(MethodNode methodNode) { - ClassNode propertyType = methodNode.getReturnType(); String methodName = methodNode.getName(); - StringBuffer propertyName = new StringBuffer(); - propertyName.append(Character.toLowerCase(methodName.charAt(3))); - if (methodName.length() > 4) { - propertyName.append(methodName.substring(4)); - } - int mods = methodNode.getModifiers(); - ClassNode declaringClass = methodNode.getDeclaringClass(); - PropertyNode property = new PropertyNode(propertyName.toString(), mods, propertyType, declaringClass, null, null, null); - property.setDeclaringClass(declaringClass); - property.getField().setDeclaringClass(declaringClass); - return property; + String propertyName = Introspector.decapitalize(methodName.substring(methodName.startsWith("is") ? 2 : 3)); + return new PropertyNode(propertyName, methodNode.getModifiers(), methodNode.getReturnType(), methodNode.getDeclaringClass(), null, null, null); } private static void initializeProperties(ClassNode node) { @@ -700,7 +691,7 @@ private static void initializeProperties(ClassNode node) { * Updates the type info of this variable if it already exists in scope, or just adds it if it doesn't */ public void updateOrAddVariable(String name, ClassNode type, ClassNode declaringType) { - if (!internalUpdateVariable(name, type, declaringType)) { + if (!updateVariableImpl(name, type, declaringType)) { addVariable(name, type, declaringType); } } @@ -711,25 +702,23 @@ public void updateOrAddVariable(String name, ClassNode type, ClassNode declaring * @param name identifier to update * @param type type of identifier * @param declaringType declaring type of identifier - * @return true iff the variable exists in scope and was updated */ - public boolean updateVariable(String name, ClassNode type, ClassNode declaringType) { - return internalUpdateVariable(name, type, declaringType); + public void updateVariable(String name, ClassNode type, ClassNode declaringType) { + updateVariableImpl(name, type, declaringType); } /** - * Return true if the type has been udpated, false otherwise + * @return {@code true} if the type has been udpated, {@code false} otherwise */ - private boolean internalUpdateVariable(String name, ClassNode type, ClassNode declaringType) { + private boolean updateVariableImpl(String name, ClassNode type, ClassNode declaringType) { VariableInfo info = lookupNameInCurrentScope(name); if (info != null) { - nameVariableMap.put(name, new VariableInfo(name, type, declaringType == null ? info.declaringType : declaringType)); + nameVariableMap.put(name, new VariableInfo(name, type, declaringType != null ? declaringType : info.declaringType)); return true; } else if (parent != null) { - return parent.internalUpdateVariable(name, type, declaringType); - } else { - return false; + return parent.updateVariableImpl(name, type, declaringType); } + return false; } public static ClassNode resolveTypeParameterization(GenericsMapper mapper, ClassNode type) { @@ -1162,11 +1151,10 @@ public static ClassNode extractElementType(ClassNode collectionType) { ClassNode typeToResolve = null; if (iterator == null && collectionType.isInterface()) { // could be a type that implements List - if (collectionType.implementsInterface(LIST_CLASS_NODE) && collectionType.getGenericsTypes() != null - && collectionType.getGenericsTypes().length == 1) { + if (collectionType.implementsInterface(LIST_CLASS_NODE) && collectionType.getGenericsTypes() != null && collectionType.getGenericsTypes().length == 1) { typeToResolve = collectionType; - } else if (collectionType.declaresInterface(ITERATOR_CLASS) || collectionType.equals(ITERATOR_CLASS) - || collectionType.declaresInterface(ENUMERATION_CLASS) || collectionType.equals(ENUMERATION_CLASS)) { + } else if (collectionType.declaresInterface(ITERATOR_CLASS) || collectionType.equals(ITERATOR_CLASS) || + collectionType.declaresInterface(ENUMERATION_CLASS) || collectionType.equals(ENUMERATION_CLASS)) { // if the type is an iterator or an enumeration, then resolve the type parameter typeToResolve = collectionType; } else if (collectionType.declaresInterface(MAP_CLASS_NODE) || collectionType.equals(MAP_CLASS_NODE)) { @@ -1194,8 +1182,8 @@ public static ClassNode extractElementType(ClassNode collectionType) { } // this is hardcoded from DGM - if (collectionType.declaresInterface(INPUT_STREAM_CLASS) || collectionType.declaresInterface(DATA_INPUT_STREAM_CLASS) - || collectionType.equals(INPUT_STREAM_CLASS) || collectionType.equals(DATA_INPUT_STREAM_CLASS)) { + if (collectionType.declaresInterface(INPUT_STREAM_CLASS) || collectionType.declaresInterface(DATA_INPUT_STREAM_CLASS) || + collectionType.equals(INPUT_STREAM_CLASS) || collectionType.equals(DATA_INPUT_STREAM_CLASS)) { return BYTE_CLASS_NODE; } @@ -1223,8 +1211,6 @@ public static boolean isThisOrSuper(Variable var) { } public static boolean isVoidOrObject(ClassNode type) { - return type != null && (type.getName().equals(VOID_CLASS_NODE.getName()) || - type.getName().equals(VOID_WRAPPER_CLASS_NODE.getName()) || - type.getName().equals(OBJECT_CLASS_NODE.getName())); + return VOID_CLASS_NODE.equals(type) || OBJECT_CLASS_NODE.equals(type); } }