This is an automated email from the ASF dual-hosted git repository. sreimers pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-netbeans.git
The following commit(s) were added to refs/heads/master by this push: new 59dd171 [NETBEANS-1293] First stab at flow typing, tests show use cases implemented 59dd171 is described below commit 59dd171b097cd680680b0cf17321bf6a021e222a Author: Sven Reimers <svenreim...@users.noreply.github.com> AuthorDate: Sun Sep 30 17:25:31 2018 +0200 [NETBEANS-1293] First stab at flow typing, tests show use cases implemented --- .../modules/groovy/editor/api/FindTypeUtils.java | 10 +- .../groovy/editor/api/parser/GroovyParser.java | 22 ++-- .../completion/inference/GroovyTypeAnalyzer.java | 4 +- .../completion/inference/MethodInference.java | 136 +++++++++++++++++++-- .../completion/inference/TypeInferenceVisitor.java | 86 ++++++++++++- .../completion/provider/MetaElementsProvider.java | 44 ++++++- .../groovy/editor/occurrences/TypeVisitor.java | 4 + .../CompletionReturnType1.groovy | 33 +++++ ...1.groovy.testCompletionReturnType1_1.completion | 29 +++++ ...1.groovy.testCompletionReturnType1_2.completion | 29 +++++ ...1.groovy.testCompletionReturnType1_3.completion | 29 +++++ ...1.groovy.testCompletionReturnType1_4.completion | 29 +++++ ...1.groovy.testCompletionReturnType1_5.completion | 29 +++++ ...1.groovy.testCompletionReturnType1_6.completion | 29 +++++ ...1.groovy.testCompletionReturnType1_7.completion | 29 +++++ .../groovy/editor/api/completion/FlowCCTest.java | 64 ++++++++++ 16 files changed, 574 insertions(+), 32 deletions(-) diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/FindTypeUtils.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/FindTypeUtils.java index 328ba36..8ecf2ac 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/FindTypeUtils.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/FindTypeUtils.java @@ -397,10 +397,12 @@ public final class FindTypeUtils { } private static OffsetRange getAnnotationRange(AnnotationNode annotation, BaseDocument doc, int cursorOffset) { - final int offset = ASTUtils.getOffset(doc, annotation.getLineNumber(), annotation.getColumnNumber()); - final OffsetRange range = ASTUtils.getNextIdentifierByName(doc, annotation.getClassNode().getNameWithoutPackage(), offset); - if (range.containsInclusive(cursorOffset)) { - return range; + if (annotation.getLineNumber() != -1) { + final int offset = ASTUtils.getOffset(doc, annotation.getLineNumber(), annotation.getColumnNumber()); + final OffsetRange range = ASTUtils.getNextIdentifierByName(doc, annotation.getClassNode().getNameWithoutPackage(), offset); + if (range.containsInclusive(cursorOffset)) { + return range; + } } return OffsetRange.NONE; } diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/parser/GroovyParser.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/parser/GroovyParser.java index 6b4306c..f5ca9c2 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/parser/GroovyParser.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/parser/GroovyParser.java @@ -33,11 +33,16 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.event.ChangeListener; import javax.swing.text.BadLocationException; +import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.CompileUnit; import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.classgen.GeneratorContext; +import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.CompilerConfiguration; import org.codehaus.groovy.control.ErrorCollector; import org.codehaus.groovy.control.Phases; +import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.control.messages.Message; import org.codehaus.groovy.control.messages.SimpleMessage; import org.codehaus.groovy.control.messages.SyntaxErrorMessage; @@ -418,12 +423,13 @@ public class GroovyParser extends Parser { ClassPath bootPath = fo == null ? ClassPath.EMPTY : ClassPath.getClassPath(fo, ClassPath.BOOT); ClassPath compilePath = fo == null ? ClassPath.EMPTY : ClassPath.getClassPath(fo, ClassPath.COMPILE); ClassPath sourcePath = fo == null ? ClassPath.EMPTY : ClassPath.getClassPath(fo, ClassPath.SOURCE); - ClassPath cp = ClassPathSupport.createProxyClassPath(bootPath, compilePath, sourcePath); + ClassPath transformPath = ClassPathSupport.createProxyClassPath(bootPath, compilePath); + ClassPath cp = ClassPathSupport.createProxyClassPath(transformPath, sourcePath); CompilerConfiguration configuration = new CompilerConfiguration(); final ClassNodeCache classNodeCache = ClassNodeCache.get(); final GroovyClassLoader classLoader = classNodeCache.createResolveLoader(cp, configuration); - final GroovyClassLoader transformationLoader = classNodeCache.createTransformationLoader(cp,configuration); + final GroovyClassLoader transformationLoader = classNodeCache.createTransformationLoader(transformPath, configuration); ClasspathInfo cpInfo = ClasspathInfo.create( // we should try to load everything by javac instead of classloader, // but for now it is faster to use javac only for sources - not true @@ -475,13 +481,15 @@ public class GroovyParser extends Parser { int line = se.getStartLine(); - if(line < 1 ) + if (line < 1) { line = 1; + } int col = se.getStartColumn(); - if(col < 1 ) + if (col < 1) { col = 1; + } // display Exception information // LOG.log(Level.FINEST, "-----------------------------------------------"); @@ -633,9 +641,9 @@ public class GroovyParser extends Parser { displayName = description; } - Error error = - new GroovyError(key, displayName, description, context.snapshot.getSource().getFileObject(), - startOffset, endOffset, severity, CompilerErrorResolver.getId(description)); + Error error + = new GroovyError(key, displayName, description, context.snapshot.getSource().getFileObject(), + startOffset, endOffset, severity, CompilerErrorResolver.getId(description)); context.errorHandler.error(error); diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/inference/GroovyTypeAnalyzer.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/inference/GroovyTypeAnalyzer.java index 8dc132f..0822fb5 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/inference/GroovyTypeAnalyzer.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/inference/GroovyTypeAnalyzer.java @@ -20,10 +20,12 @@ package org.netbeans.modules.groovy.editor.completion.inference; import java.util.Collections; +import java.util.ListIterator; import java.util.Set; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.expr.VariableExpression; import org.netbeans.editor.BaseDocument; @@ -55,7 +57,7 @@ public class GroovyTypeAnalyzer { } if (caller instanceof MethodCallExpression) { - return Collections.singleton(MethodInference.findCallerType(caller)); + return Collections.singleton(MethodInference.findCallerType(caller, path, document, astOffset)); } return Collections.emptySet(); diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/inference/MethodInference.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/inference/MethodInference.java index 12779e2..1ee0e8d 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/inference/MethodInference.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/inference/MethodInference.java @@ -19,15 +19,27 @@ package org.netbeans.modules.groovy.editor.completion.inference; +import java.util.ArrayList; +import java.util.List; import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.MethodNode; -import org.codehaus.groovy.ast.Variable; +import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.BinaryExpression; +import org.codehaus.groovy.ast.expr.ClassExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.ast.tools.GenericsUtils; import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.editor.BaseDocument; +import org.netbeans.modules.groovy.editor.api.ASTUtils; +import org.netbeans.modules.groovy.editor.api.AstPath; /** * @@ -50,24 +62,38 @@ public final class MethodInference { * @return class type of the caller if found, {@code null} otherwise */ @CheckForNull - public static ClassNode findCallerType(@NonNull ASTNode expression) { + public static ClassNode findCallerType(@NonNull ASTNode expression, @NonNull AstPath path, BaseDocument baseDocument, int offset) { // In case if the method call is chained with another method call // For example: someInteger.toString().^ if (expression instanceof MethodCallExpression) { MethodCallExpression methodCall = (MethodCallExpression) expression; - - ClassNode callerType = findCallerType(methodCall.getObjectExpression()); + + ClassNode callerType = findCallerType(methodCall.getObjectExpression(), path, baseDocument, offset); if (callerType != null) { - return findReturnTypeFor(callerType, methodCall.getMethodAsString(), methodCall.getArguments()); + return findReturnTypeFor(callerType, methodCall.getMethodAsString(), methodCall.getArguments(), path, false, baseDocument, offset); } } // In case if the method call is directly on a variable if (expression instanceof VariableExpression) { - Variable variable = ((VariableExpression) expression).getAccessedVariable(); - if (variable != null) { - return variable.getType(); + int newOffset = ASTUtils.getOffset(baseDocument, expression.getLineNumber(), expression.getColumnNumber()); + AstPath newPath = new AstPath(path.root(), newOffset, baseDocument); + TypeInferenceVisitor tiv = new TypeInferenceVisitor(((ModuleNode)path.root()).getContext(), newPath, baseDocument, newOffset); + tiv.collect(); + return tiv.getGuessedType(); + } + if (expression instanceof ConstantExpression) { + return ((ConstantExpression) expression).getType(); + } + if (expression instanceof ClassExpression) { + return ClassHelper.make(((ClassExpression) expression).getType().getName()); + } + + if (expression instanceof StaticMethodCallExpression) { + StaticMethodCallExpression staticMethodCall = (StaticMethodCallExpression) expression; + + return findReturnTypeFor(staticMethodCall.getOwnerType(), staticMethodCall.getMethod(), staticMethodCall.getArguments(), path, true, baseDocument, offset); } return null; } @@ -76,12 +102,100 @@ public final class MethodInference { private static ClassNode findReturnTypeFor( @NonNull ClassNode callerType, @NonNull String methodName, - @NonNull Expression arguments) { - - MethodNode possibleMethod = callerType.tryFindPossibleMethod(methodName, arguments); + @NonNull Expression arguments, + @NonNull AstPath path, + @NonNull boolean isStatic, + @NonNull BaseDocument baseDocument, + @NonNull int offset + ) { + + List<ClassNode> paramTypes = new ArrayList<>(); + if (arguments instanceof ArgumentListExpression) { + ArgumentListExpression argExpression = (ArgumentListExpression) arguments; + for (Expression e : argExpression.getExpressions()) { + if (e instanceof VariableExpression) { + ModuleNode moduleNode = (ModuleNode) path.root(); + int newOffset = ASTUtils.getOffset(baseDocument, e.getLineNumber(), e.getColumnNumber()); + AstPath newPath = new AstPath(moduleNode, newOffset, baseDocument); + TypeInferenceVisitor tiv = new TypeInferenceVisitor(moduleNode.getContext(), newPath, baseDocument, newOffset); + tiv.collect(); + ClassNode guessedType = tiv.getGuessedType(); + if (null == guessedType) { + System.out.println("Bad guessed type"); + } else { + paramTypes.add(tiv.getGuessedType()); + } + } else if(e instanceof ConstantExpression) { + paramTypes.add(((ConstantExpression)e).getType()); + } else if (e instanceof MethodCallExpression) { + paramTypes.add(findCallerType(e, path, baseDocument, offset)); + } else if (e instanceof BinaryExpression) { + BinaryExpression binExpression = (BinaryExpression) e; + paramTypes.add(binExpression.getType()); + } else if (e instanceof ClassExpression) { + ClassExpression classExpression = (ClassExpression) e; + // This should be Class<classExpression.getType()> + paramTypes.add(GenericsUtils.makeClassSafeWithGenerics(Class.class, classExpression.getType())); + } else { + System.out.println(e.getClass()); + } + } + } + + MethodNode possibleMethod = tryFindPossibleMethod(callerType, methodName, paramTypes, isStatic); if (possibleMethod != null) { return possibleMethod.getReturnType(); } return null; } + + private static MethodNode tryFindPossibleMethod(ClassNode callerType, String methodName, List<ClassNode> paramTypes, boolean isStatic) { + int count = paramTypes.size(); + + MethodNode res = null; + ClassNode node = callerType; + do { + for (MethodNode method : node.getMethods(methodName)) { + if (isStatic && !method.isStatic()) { + continue; + } + if (method.getParameters().length == count) { + boolean match = true; + for (int i = 0; i != count; ++i) { + if (!paramTypes.get(i).isDerivedFrom(method.getParameters()[i].getType())) { + match = false; + break; + } + } + + if (match) { + if (res == null) { + res = method; + } else { + if (res.getParameters().length != count) { + return null; + } + if (node.equals(callerType)) { + return null; + } + + match = true; + for (int i = 0; i != count; ++i) { + if (!res.getParameters()[i].getType().equals(method.getParameters()[i].getType())) { + match = false; + break; + } + } + if (!match) { + return null; + } + } + } + } + } + node = node.getSuperClass(); + } while (node != null); + + return res; + } } diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/inference/TypeInferenceVisitor.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/inference/TypeInferenceVisitor.java index 8b8836d..bc69621 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/inference/TypeInferenceVisitor.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/inference/TypeInferenceVisitor.java @@ -20,17 +20,25 @@ package org.netbeans.modules.groovy.editor.completion.inference; import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.Variable; import org.codehaus.groovy.ast.expr.BinaryExpression; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.ConstructorCallExpression; +import org.codehaus.groovy.ast.expr.DeclarationExpression; +import org.codehaus.groovy.ast.expr.EmptyExpression; import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.ListExpression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.syntax.Types; import org.netbeans.editor.BaseDocument; +import org.netbeans.modules.groovy.editor.api.ASTUtils; import org.netbeans.modules.groovy.editor.api.AstPath; import org.netbeans.modules.groovy.editor.occurrences.TypeVisitor; @@ -79,11 +87,72 @@ public class TypeInferenceVisitor extends TypeVisitor { } } + public void visitField(FieldNode node) { + if (sameVariableName(leaf, node)) { + if (node.hasInitialExpression()){ + Expression expression = node.getInitialExpression(); + if (expression instanceof ConstantExpression + && !expression.getText().equals("null")) { // NOI18N + guessedType = ((ConstantExpression) expression).getType(); + } else if (expression instanceof ConstructorCallExpression) { + guessedType = ((ConstructorCallExpression) expression).getType(); + } else if (expression instanceof MethodCallExpression) { + int newOffset = ASTUtils.getOffset(doc, expression.getLineNumber(), expression.getColumnNumber()); + AstPath newPath = new AstPath(path.root(), newOffset, doc); + guessedType = MethodInference.findCallerType(expression, newPath, doc, newOffset); + } + } + } + } + + @Override - public void visitVariableExpression(VariableExpression expression) { - if (expression == leaf) { - leafReached = true; + public void visitDeclarationExpression(DeclarationExpression expression) { + if (sameVariableName(leaf, expression.getLeftExpression())) { + Expression rightExpression = expression.getRightExpression(); + if (rightExpression instanceof ConstantExpression + && !rightExpression.getText().equals("null")) { // NOI18N + guessedType = ((ConstantExpression) rightExpression).getType(); + } else if (rightExpression instanceof ConstructorCallExpression) { + guessedType = ((ConstructorCallExpression) rightExpression).getType(); + } else if (rightExpression instanceof MethodCallExpression) { + guessedType = MethodInference.findCallerType(rightExpression, path, doc, cursorOffset); + } else if (rightExpression instanceof StaticMethodCallExpression) { + guessedType = MethodInference.findCallerType(rightExpression, path, doc, cursorOffset); + } else if (rightExpression instanceof ListExpression) { + guessedType = ((ListExpression) rightExpression).getType(); + } } + } + + @Override + public void visitVariableExpression(VariableExpression expression) { + if (expression.isSuperExpression()) { + guessedType = expression.getType().getSuperClass(); + } + if (null != expression.getAccessedVariable()) { + Variable accessedVariable = expression.getAccessedVariable(); + + if (accessedVariable.hasInitialExpression()) { + Expression initialExpression = expression.getAccessedVariable().getInitialExpression(); + if (initialExpression instanceof ConstantExpression + && !initialExpression.getText().equals("null")) { // NOI18N + guessedType = ((ConstantExpression) initialExpression).getType(); + } else if (initialExpression instanceof ConstructorCallExpression) { + guessedType = ClassHelper.make(((ConstructorCallExpression) initialExpression).getType().getName()); + } else if (initialExpression instanceof MethodCallExpression) { + int newOffset = ASTUtils.getOffset(doc, initialExpression.getLineNumber(), initialExpression.getColumnNumber()); + AstPath newPath = new AstPath(path.root(), newOffset, doc); + guessedType = MethodInference.findCallerType(initialExpression, newPath, doc, newOffset); + } + } else if (accessedVariable instanceof Parameter) { + Parameter param = (Parameter) accessedVariable; + guessedType = param.getType(); + } + } else if (!expression.getType().getName().equals("java.lang.Object")) { + guessedType = expression.getType(); + + } super.visitVariableExpression(expression); } @@ -99,7 +168,11 @@ public class TypeInferenceVisitor extends TypeVisitor { && !rightExpression.getText().equals("null")) { // NOI18N guessedType = ((ConstantExpression) rightExpression).getType(); } else if (rightExpression instanceof ConstructorCallExpression) { - guessedType = ((ConstructorCallExpression) rightExpression).getType(); + guessedType = ClassHelper.make(((ConstructorCallExpression) rightExpression).getType().getName()); + } else if (rightExpression instanceof MethodCallExpression) { + guessedType = MethodInference.findCallerType(rightExpression, path, doc, cursorOffset); + } else if (rightExpression instanceof StaticMethodCallExpression) { + guessedType = MethodInference.findCallerType(rightExpression, path, doc, cursorOffset); } } } @@ -115,5 +188,10 @@ public class TypeInferenceVisitor extends TypeVisitor { return node1 instanceof VariableExpression && node2 instanceof VariableExpression && ((VariableExpression) node1).getName().equals(((VariableExpression) node2).getName()); } + + private static boolean sameVariableName(ASTNode node1, FieldNode node2) { + return node1 instanceof VariableExpression + && ((VariableExpression) node1).getName().equals(node2.getName()); + } } diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/MetaElementsProvider.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/MetaElementsProvider.java index f04648f..79994e2 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/MetaElementsProvider.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/MetaElementsProvider.java @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.codehaus.groovy.reflection.CachedClass; +import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.modules.groovy.editor.api.completion.CompletionItem.MetaMethodItem; import org.netbeans.modules.groovy.editor.api.completion.FieldSignature; import org.netbeans.modules.groovy.editor.api.completion.MethodSignature; @@ -52,29 +53,47 @@ public final class MetaElementsProvider implements CompletionProvider { @Override public Map<MethodSignature, CompletionItem> getMethods(CompletionContext context) { final Map<MethodSignature, CompletionItem> result = new HashMap<MethodSignature, CompletionItem>(); - final Class clz = loadClass(context.getTypeName()); + final Class clz = loadClass(context); if (clz != null) { final MetaClass metaClz = GroovySystem.getMetaClassRegistry().getMetaClass(clz); if (metaClz != null) { for (MetaMethod method : metaClz.getMetaMethods()) { - populateProposal(clz, method, context.getPrefix(), context.getAnchor(), result, context.isNameOnly()); + if (!method.isStatic()) { + populateProposal(clz, method, context.getPrefix(), context.getAnchor(), result, context.isNameOnly()); + } } } + GroovySystem.getMetaClassRegistry().removeMetaClass(clz); } return result; } @Override public Map<MethodSignature, CompletionItem> getStaticMethods(CompletionContext context) { - return Collections.emptyMap(); + final Map<MethodSignature, CompletionItem> result = new HashMap<MethodSignature, CompletionItem>(); + final Class clz = loadClass(context); + + if (clz != null) { + final MetaClass metaClz = GroovySystem.getMetaClassRegistry().getMetaClass(clz); + + if (metaClz != null) { + for (MetaMethod method : metaClz.getMetaMethods()) { + if (method.isStatic()) { + populateProposal(clz, method, context.getPrefix(), context.getAnchor(), result, context.isNameOnly()); + } + } + } + GroovySystem.getMetaClassRegistry().removeMetaClass(clz); + } + return result; } @Override public Map<FieldSignature, CompletionItem> getFields(CompletionContext context) { final Map<FieldSignature, CompletionItem> result = new HashMap<FieldSignature, CompletionItem>(); - final Class clazz = loadClass(context.getTypeName()); + final Class clazz = loadClass(context); if (clazz != null) { final MetaClass metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(clazz); @@ -91,6 +110,7 @@ public final class MetaElementsProvider implements CompletionProvider { context.getAnchor())); } } + GroovySystem.getMetaClassRegistry().removeMetaClass(clazz); } } @@ -102,6 +122,16 @@ public final class MetaElementsProvider implements CompletionProvider { return Collections.emptyMap(); } + private Class loadClass(CompletionContext context) { + try { + return context.getSurroundingClass().getCompileUnit().getClassLoader().loadClass(context.getTypeName()); + } catch (ClassNotFoundException cnfe) { + return loadClass(context.getTypeName()); + } catch (RuntimeException exception) { + return null; + } + } + private Class loadClass(String className) { try { // FIXME should be loaded by classpath classloader @@ -226,6 +256,10 @@ public final class MetaElementsProvider implements CompletionProvider { return false; } } - return false; + + //preferMethodsAccording to direct inheritance + + return currentMethod.getDeclaringClass().isAssignableFrom(methodToStore.getDeclaringClass().getTheClass()); + } } diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/occurrences/TypeVisitor.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/occurrences/TypeVisitor.java index 7c623d5..2952ccc 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/occurrences/TypeVisitor.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/occurrences/TypeVisitor.java @@ -23,6 +23,7 @@ import java.util.Iterator; import org.codehaus.groovy.ast.*; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.ClosureListExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.ForStatement; import org.codehaus.groovy.control.SourceUnit; @@ -139,6 +140,9 @@ public class TypeVisitor extends ClassCodeVisitorSupport { return; } } + else if (scope instanceof VariableExpression) { + visitVariableExpression((VariableExpression)scope); + } } } diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy new file mode 100644 index 0000000..190a831 --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy @@ -0,0 +1,33 @@ +class Klazz { + + def fieldA = new String("Hello") + def fieldA1 = "Hello" + def fieldB = fieldA.c + def fieldB1 = fieldA1.c + def fieldC = fieldA.concat("b").c + def fieldC1 = fieldA1.concat("b").c + def fieldD = "hi" + def fieldE = fieldA.concat(fieldD) + def fieldE1 = fieldA1.concat(fieldD) + def fieldF = fieldE.c + def fieldF1 = fieldE1.c + + def m() { + + def localA = "Hello" + localA.c + + localA.concat("b").c + + def localB = "hi" + + localA.concat(localB).c + + + def localC = localA.concat(localB) + + localC.c + + } + +} \ No newline at end of file diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_1.completion b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_1.completion new file mode 100644 index 0000000..4fde144 --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_1.completion @@ -0,0 +1,29 @@ +Code completion result for source line: +def fieldB = fieldA.c| +(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true) +------------------------------------ +METHOD capitalize() String +METHOD center(Number) String +METHOD center(Number, CharSequence) String +METHOD charAt(int) [PUBLIC] char +METHOD chars() [PUBLIC] IntStream +METHOD codePointAt(int) [PUBLIC] int +METHOD codePointBefore(int) [PUBLIC] int +METHOD codePointCount(int, int) [PUBLIC] int +METHOD codePoints() [PUBLIC] IntStream +METHOD collect() Collection +METHOD collect(Closure) List +METHOD collect(Collection, Closure) Collection +METHOD collectReplacements(Closure) String +METHOD compareTo(String) [PUBLIC] int +METHOD compareTo(T) [PUBLIC] int +METHOD compareToIgnoreCase(String) [PUBLIC] int +METHOD concat(String) [PUBLIC] String +METHOD contains(CharSequence) [PUBLIC] boolean +METHOD contentEquals(CharSequence) [PUBLIC] boolean +METHOD contentEquals(StringBuffer) [PUBLIC] boolean +METHOD copyValueOf(char[]) [STATIC, String +METHOD copyValueOf(char[], int, int) [STATIC, String +METHOD count(CharSequence) int +FIELD CASE_INSENSITIVE_ORDER [STATIC, Comparator<String> +FIELD class [PUBLIC] Class diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_2.completion b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_2.completion new file mode 100644 index 0000000..53463bc --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_2.completion @@ -0,0 +1,29 @@ +Code completion result for source line: +def fieldB1 = fieldA1.c| +(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true) +------------------------------------ +METHOD capitalize() String +METHOD center(Number) String +METHOD center(Number, CharSequence) String +METHOD charAt(int) [PUBLIC] char +METHOD chars() [PUBLIC] IntStream +METHOD codePointAt(int) [PUBLIC] int +METHOD codePointBefore(int) [PUBLIC] int +METHOD codePointCount(int, int) [PUBLIC] int +METHOD codePoints() [PUBLIC] IntStream +METHOD collect() Collection +METHOD collect(Closure) List +METHOD collect(Collection, Closure) Collection +METHOD collectReplacements(Closure) String +METHOD compareTo(String) [PUBLIC] int +METHOD compareTo(T) [PUBLIC] int +METHOD compareToIgnoreCase(String) [PUBLIC] int +METHOD concat(String) [PUBLIC] String +METHOD contains(CharSequence) [PUBLIC] boolean +METHOD contentEquals(CharSequence) [PUBLIC] boolean +METHOD contentEquals(StringBuffer) [PUBLIC] boolean +METHOD copyValueOf(char[]) [STATIC, String +METHOD copyValueOf(char[], int, int) [STATIC, String +METHOD count(CharSequence) int +FIELD CASE_INSENSITIVE_ORDER [STATIC, Comparator<String> +FIELD class [PUBLIC] Class diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_3.completion b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_3.completion new file mode 100644 index 0000000..5fd3523 --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_3.completion @@ -0,0 +1,29 @@ +Code completion result for source line: +def fieldF = fieldE.c| +(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true) +------------------------------------ +METHOD capitalize() String +METHOD center(Number) String +METHOD center(Number, CharSequence) String +METHOD charAt(int) [PUBLIC] char +METHOD chars() [PUBLIC] IntStream +METHOD codePointAt(int) [PUBLIC] int +METHOD codePointBefore(int) [PUBLIC] int +METHOD codePointCount(int, int) [PUBLIC] int +METHOD codePoints() [PUBLIC] IntStream +METHOD collect() Collection +METHOD collect(Closure) List +METHOD collect(Collection, Closure) Collection +METHOD collectReplacements(Closure) String +METHOD compareTo(String) [PUBLIC] int +METHOD compareTo(T) [PUBLIC] int +METHOD compareToIgnoreCase(String) [PUBLIC] int +METHOD concat(String) [PUBLIC] String +METHOD contains(CharSequence) [PUBLIC] boolean +METHOD contentEquals(CharSequence) [PUBLIC] boolean +METHOD contentEquals(StringBuffer) [PUBLIC] boolean +METHOD copyValueOf(char[]) [STATIC, String +METHOD copyValueOf(char[], int, int) [STATIC, String +METHOD count(CharSequence) int +FIELD CASE_INSENSITIVE_ORDER [STATIC, Comparator<String> +FIELD class [PUBLIC] Class diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_4.completion b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_4.completion new file mode 100644 index 0000000..493a7a6 --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_4.completion @@ -0,0 +1,29 @@ +Code completion result for source line: +localA.c| +(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true) +------------------------------------ +METHOD capitalize() String +METHOD center(Number) String +METHOD center(Number, CharSequence) String +METHOD charAt(int) [PUBLIC] char +METHOD chars() [PUBLIC] IntStream +METHOD codePointAt(int) [PUBLIC] int +METHOD codePointBefore(int) [PUBLIC] int +METHOD codePointCount(int, int) [PUBLIC] int +METHOD codePoints() [PUBLIC] IntStream +METHOD collect() Collection +METHOD collect(Closure) List +METHOD collect(Collection, Closure) Collection +METHOD collectReplacements(Closure) String +METHOD compareTo(String) [PUBLIC] int +METHOD compareTo(T) [PUBLIC] int +METHOD compareToIgnoreCase(String) [PUBLIC] int +METHOD concat(String) [PUBLIC] String +METHOD contains(CharSequence) [PUBLIC] boolean +METHOD contentEquals(CharSequence) [PUBLIC] boolean +METHOD contentEquals(StringBuffer) [PUBLIC] boolean +METHOD copyValueOf(char[]) [STATIC, String +METHOD copyValueOf(char[], int, int) [STATIC, String +METHOD count(CharSequence) int +FIELD CASE_INSENSITIVE_ORDER [STATIC, Comparator<String> +FIELD class [PUBLIC] Class diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_5.completion b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_5.completion new file mode 100644 index 0000000..492308d --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_5.completion @@ -0,0 +1,29 @@ +Code completion result for source line: +localA.concat("b").c| +(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true) +------------------------------------ +METHOD capitalize() String +METHOD center(Number) String +METHOD center(Number, CharSequence) String +METHOD charAt(int) [PUBLIC] char +METHOD chars() [PUBLIC] IntStream +METHOD codePointAt(int) [PUBLIC] int +METHOD codePointBefore(int) [PUBLIC] int +METHOD codePointCount(int, int) [PUBLIC] int +METHOD codePoints() [PUBLIC] IntStream +METHOD collect() Collection +METHOD collect(Closure) List +METHOD collect(Collection, Closure) Collection +METHOD collectReplacements(Closure) String +METHOD compareTo(String) [PUBLIC] int +METHOD compareTo(T) [PUBLIC] int +METHOD compareToIgnoreCase(String) [PUBLIC] int +METHOD concat(String) [PUBLIC] String +METHOD contains(CharSequence) [PUBLIC] boolean +METHOD contentEquals(CharSequence) [PUBLIC] boolean +METHOD contentEquals(StringBuffer) [PUBLIC] boolean +METHOD copyValueOf(char[]) [STATIC, String +METHOD copyValueOf(char[], int, int) [STATIC, String +METHOD count(CharSequence) int +FIELD CASE_INSENSITIVE_ORDER [STATIC, Comparator<String> +FIELD class [PUBLIC] Class diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_6.completion b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_6.completion new file mode 100644 index 0000000..b471cd1 --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_6.completion @@ -0,0 +1,29 @@ +Code completion result for source line: +localA.concat(localB).c| +(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true) +------------------------------------ +METHOD capitalize() String +METHOD center(Number) String +METHOD center(Number, CharSequence) String +METHOD charAt(int) [PUBLIC] char +METHOD chars() [PUBLIC] IntStream +METHOD codePointAt(int) [PUBLIC] int +METHOD codePointBefore(int) [PUBLIC] int +METHOD codePointCount(int, int) [PUBLIC] int +METHOD codePoints() [PUBLIC] IntStream +METHOD collect() Collection +METHOD collect(Closure) List +METHOD collect(Collection, Closure) Collection +METHOD collectReplacements(Closure) String +METHOD compareTo(String) [PUBLIC] int +METHOD compareTo(T) [PUBLIC] int +METHOD compareToIgnoreCase(String) [PUBLIC] int +METHOD concat(String) [PUBLIC] String +METHOD contains(CharSequence) [PUBLIC] boolean +METHOD contentEquals(CharSequence) [PUBLIC] boolean +METHOD contentEquals(StringBuffer) [PUBLIC] boolean +METHOD copyValueOf(char[]) [STATIC, String +METHOD copyValueOf(char[], int, int) [STATIC, String +METHOD count(CharSequence) int +FIELD CASE_INSENSITIVE_ORDER [STATIC, Comparator<String> +FIELD class [PUBLIC] Class diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_7.completion b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_7.completion new file mode 100644 index 0000000..b210308 --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/flow/completionReturnType1/CompletionReturnType1.groovy.testCompletionReturnType1_7.completion @@ -0,0 +1,29 @@ +Code completion result for source line: +localC.c| +(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true) +------------------------------------ +METHOD capitalize() String +METHOD center(Number) String +METHOD center(Number, CharSequence) String +METHOD charAt(int) [PUBLIC] char +METHOD chars() [PUBLIC] IntStream +METHOD codePointAt(int) [PUBLIC] int +METHOD codePointBefore(int) [PUBLIC] int +METHOD codePointCount(int, int) [PUBLIC] int +METHOD codePoints() [PUBLIC] IntStream +METHOD collect() Collection +METHOD collect(Closure) List +METHOD collect(Collection, Closure) Collection +METHOD collectReplacements(Closure) String +METHOD compareTo(String) [PUBLIC] int +METHOD compareTo(T) [PUBLIC] int +METHOD compareToIgnoreCase(String) [PUBLIC] int +METHOD concat(String) [PUBLIC] String +METHOD contains(CharSequence) [PUBLIC] boolean +METHOD contentEquals(CharSequence) [PUBLIC] boolean +METHOD contentEquals(StringBuffer) [PUBLIC] boolean +METHOD copyValueOf(char[]) [STATIC, String +METHOD copyValueOf(char[], int, int) [STATIC, String +METHOD count(CharSequence) int +FIELD CASE_INSENSITIVE_ORDER [STATIC, Comparator<String> +FIELD class [PUBLIC] Class diff --git a/groovy/groovy.editor/test/unit/src/org/netbeans/modules/groovy/editor/api/completion/FlowCCTest.java b/groovy/groovy.editor/test/unit/src/org/netbeans/modules/groovy/editor/api/completion/FlowCCTest.java new file mode 100644 index 0000000..dda6975 --- /dev/null +++ b/groovy/groovy.editor/test/unit/src/org/netbeans/modules/groovy/editor/api/completion/FlowCCTest.java @@ -0,0 +1,64 @@ +/* + * 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.netbeans.modules.groovy.editor.api.completion; + +/** + * + * @author sreimers + */ +public class FlowCCTest extends GroovyCCTestBase { + + public FlowCCTest(String testName) { + super(testName); + } + + @Override + protected String getTestType() { + return "flow"; //NOI18N + } + + public void testCompletionReturnType1_1() throws Exception { + checkCompletion(BASE + "CompletionReturnType1.groovy", "def fieldB = fieldA.c^", false); + } + + public void testCompletionReturnType1_2() throws Exception { + checkCompletion(BASE + "CompletionReturnType1.groovy", "def fieldB1 = fieldA1.c^", false); + } + + public void testCompletionReturnType1_3() throws Exception { + checkCompletion(BASE + "CompletionReturnType1.groovy", "fieldE.c^", false); + } + + public void testCompletionReturnType1_4() throws Exception { + checkCompletion(BASE + "CompletionReturnType1.groovy", "localA.c^", false); + } + + public void testCompletionReturnType1_5() throws Exception { + checkCompletion(BASE + "CompletionReturnType1.groovy", "localA.concat(\"b\").c^", false); + } + + public void testCompletionReturnType1_6() throws Exception { + checkCompletion(BASE + "CompletionReturnType1.groovy", "localA.concat(localB).c^", false); + } + + public void testCompletionReturnType1_7() throws Exception { + checkCompletion(BASE + "CompletionReturnType1.groovy", "localC.c^", false); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org For additional commands, e-mail: commits-h...@netbeans.apache.org For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists