8129421: JShell: unacceptable suggestions in 'extends', 'implements' in smart completion
authorjlahoda
Mon, 15 Aug 2016 08:28:26 +0200
changeset 40318 3f7eb1205cee
parent 40317 78115e0f10f3
child 40319 b45dd3eb246d
8129421: JShell: unacceptable suggestions in 'extends', 'implements' in smart completion 8129422: JShell: methods and fields of uncompleted expressions should be suggested Summary: Fixing several completion bugs Reviewed-by: rfield
langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java
langtools/test/jdk/jshell/CompletionSuggestionTest.java
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java	Sat Aug 13 09:42:26 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java	Mon Aug 15 08:28:26 2016 +0200
@@ -27,6 +27,7 @@
 
 import jdk.jshell.SourceCodeAnalysis.Completeness;
 import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.ClassTree;
 import com.sun.source.tree.CompilationUnitTree;
 import com.sun.source.tree.ErroneousTree;
 import com.sun.source.tree.ExpressionTree;
@@ -39,6 +40,7 @@
 import com.sun.source.tree.Scope;
 import com.sun.source.tree.Tree;
 import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.TypeParameterTree;
 import com.sun.source.tree.VariableTree;
 import com.sun.source.util.JavacTask;
 import com.sun.source.util.SourcePositions;
@@ -91,6 +93,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
@@ -267,6 +270,7 @@
             case IMPORT:
                 codeWrap = proc.outerMap.wrapImport(Wrap.simpleWrap(code + "any.any"), null);
                 break;
+            case CLASS:
             case METHOD:
                 codeWrap = proc.outerMap.wrapInTrialClass(Wrap.classMemberWrap(code));
                 break;
@@ -380,10 +384,46 @@
                         addElements(membersOf(at, at.getElements().getPackageElement("").asType(), false), it.isStatic() ? STATIC_ONLY.and(accessibility) : accessibility, smartFilter, result);
                     }
                     break;
-                case ERRONEOUS:
-                case EMPTY_STATEMENT: {
+                case CLASS: {
+                    Predicate<Element> accept = accessibility.and(IS_TYPE);
+                    addScopeElements(at, scope, IDENTITY, accept, smartFilter, result);
+                    addElements(primitivesOrVoid(at), TRUE, smartFilter, result);
+                    break;
+                }
+                case BLOCK:
+                case EMPTY_STATEMENT:
+                case ERRONEOUS: {
                     boolean staticOnly = ReplResolve.isStatic(((JavacScope)scope).getEnv());
                     Predicate<Element> accept = accessibility.and(staticOnly ? STATIC_ONLY : TRUE);
+                    if (isClass(tp)) {
+                        ClassTree clazz = (ClassTree) tp.getParentPath().getLeaf();
+                        if (clazz.getExtendsClause() == tp.getLeaf()) {
+                            accept = accept.and(IS_TYPE);
+                            smartFilter = smartFilter.and(el -> el.getKind() == ElementKind.CLASS);
+                        } else {
+                            Predicate<Element> f = smartFilterFromList(at, tp, clazz.getImplementsClause(), tp.getLeaf());
+                            if (f != null) {
+                                accept = accept.and(IS_TYPE);
+                                smartFilter = f.and(el -> el.getKind() == ElementKind.INTERFACE);
+                            }
+                        }
+                    } else if (isTypeParameter(tp)) {
+                        TypeParameterTree tpt = (TypeParameterTree) tp.getParentPath().getLeaf();
+                        Predicate<Element> f = smartFilterFromList(at, tp, tpt.getBounds(), tp.getLeaf());
+                        if (f != null) {
+                            accept = accept.and(IS_TYPE);
+                            smartFilter = f;
+                            if (!tpt.getBounds().isEmpty() && tpt.getBounds().get(0) != tp.getLeaf()) {
+                                smartFilter = smartFilter.and(el -> el.getKind() == ElementKind.INTERFACE);
+                            }
+                        }
+                    } else if (isVariable(tp)) {
+                        VariableTree var = (VariableTree) tp.getParentPath().getLeaf();
+                        if (var.getType() == tp.getLeaf()) {
+                            accept = accept.and(IS_TYPE);
+                        }
+                    }
+
                     addScopeElements(at, scope, IDENTITY, accept, smartFilter, result);
 
                     Tree parent = tp.getParentPath().getLeaf();
@@ -413,6 +453,23 @@
         return result;
     }
 
+    private static final Set<Kind> CLASS_KINDS = EnumSet.of(
+            Kind.ANNOTATION_TYPE, Kind.CLASS, Kind.ENUM, Kind.INTERFACE
+    );
+
+    private Predicate<Element> smartFilterFromList(AnalyzeTask at, TreePath base, Collection<? extends Tree> types, Tree current) {
+        Set<Element> existingEls = new HashSet<>();
+
+        for (Tree type : types) {
+            if (type == current) {
+                return el -> !existingEls.contains(el);
+            }
+            existingEls.add(at.trees().getElement(new TreePath(base, type)));
+        }
+
+        return null;
+    }
+
     @Override
     public SnippetWrapper wrapper(Snippet snippet) {
         return new SnippetWrapper() {
@@ -516,6 +573,21 @@
                 ((MethodTree)parent).getThrows().contains(tp.getLeaf());
     }
 
+    private boolean isClass(TreePath tp) {
+        return tp.getParentPath() != null &&
+               CLASS_KINDS.contains(tp.getParentPath().getLeaf().getKind());
+    }
+
+    private boolean isTypeParameter(TreePath tp) {
+        return tp.getParentPath() != null &&
+               tp.getParentPath().getLeaf().getKind() == Kind.TYPE_PARAMETER;
+    }
+
+    private boolean isVariable(TreePath tp) {
+        return tp.getParentPath() != null &&
+               tp.getParentPath().getLeaf().getKind() == Kind.VARIABLE;
+    }
+
     private ImportTree findImport(TreePath tp) {
         while (tp != null && tp.getLeaf().getKind() != Kind.IMPORT) {
             tp = tp.getParentPath();
@@ -550,6 +622,7 @@
     private final Predicate<Element> IS_PACKAGE = el -> el.getKind() == ElementKind.PACKAGE;
     private final Predicate<Element> IS_CLASS = el -> el.getKind().isClass();
     private final Predicate<Element> IS_INTERFACE = el -> el.getKind().isInterface();
+    private final Predicate<Element> IS_TYPE = IS_CLASS.or(IS_INTERFACE).or(el -> el.getKind() == ElementKind.TYPE_PARAMETER);
     private final Predicate<Element> IS_VOID = el -> el.asType().getKind() == TypeKind.VOID;
     private final Predicate<Element> STATIC_ONLY = el -> {
         ElementKind kind = el.getKind();
@@ -583,6 +656,11 @@
         for (Element c : elements) {
             if (!accept.test(c))
                 continue;
+            if (c.getKind() == ElementKind.METHOD &&
+                c.getSimpleName().contentEquals(Util.DOIT_METHOD_NAME) &&
+                ((ExecutableElement) c).getParameters().isEmpty()) {
+                continue;
+            }
             String simpleName = simpleName(c);
             if (c.getKind() == ElementKind.CONSTRUCTOR || c.getKind() == ElementKind.METHOD) {
                 simpleName += paren.apply(hasParams.contains(simpleName));
@@ -754,13 +832,25 @@
         };
         @SuppressWarnings("unchecked")
         List<Element> result = Util.stream(scopeIterable)
-                             .flatMap(s -> Util.stream((Iterable<Element>)s.getLocalElements()))
+                             .flatMap(s -> localElements(s))
                              .flatMap(el -> Util.stream((Iterable<Element>)elementConvertor.apply(el)))
                              .collect(toCollection(ArrayList :: new));
         result.addAll(listPackages(at, ""));
         return result;
     }
 
+    private Stream<Element> localElements(Scope scope) {
+        @SuppressWarnings("unchecked")
+        Stream<Element> elements = Util.stream((Iterable<Element>)scope.getLocalElements());
+
+        if (scope.getEnclosingScope() != null &&
+            scope.getEnclosingClass() != scope.getEnclosingScope().getEnclosingClass()) {
+            elements = Stream.concat(elements, scope.getEnclosingClass().getEnclosedElements().stream());
+        }
+
+        return elements;
+    }
+
     @SuppressWarnings("fallthrough")
     private Iterable<TypeMirror> findTargetType(AnalyzeTask at, TreePath forPath) {
         if (forPath.getParentPath() == null)
--- a/langtools/test/jdk/jshell/CompletionSuggestionTest.java	Sat Aug 13 09:42:26 2016 -0700
+++ b/langtools/test/jdk/jshell/CompletionSuggestionTest.java	Mon Aug 15 08:28:26 2016 +0200
@@ -415,7 +415,7 @@
         assertCompletion("new Clazz() {}.defaultM|", "defaultMethod()");
     }
 
-    @Test(enabled = false) // TODO 8129422
+    @Test
     public void testUncompletedDeclaration() {
         assertCompletion("class Clazz { Claz|", "Clazz");
         assertCompletion("class Clazz { class A extends Claz|", "Clazz");
@@ -423,16 +423,18 @@
         assertCompletion("class Clazz { static Clazz clazz; Object o = cla|", "clazz");
         assertCompletion("class Clazz { Clazz clazz; static Object o = cla|", true);
         assertCompletion("class Clazz { void method(Claz|", "Clazz");
-        assertCompletion("class A { int method() { return 0; } int a = meth|", "method");
+        assertCompletion("class A { int method() { return 0; } int a = meth|", "method()");
         assertCompletion("class A { int field = 0; int method() { return fiel|", "field");
-        assertCompletion("class A { static int method() { return 0; } int a = meth|", "method");
+        assertCompletion("class A { static int method() { return 0; } int a = meth|", "method()");
         assertCompletion("class A { static int field = 0; int method() { return fiel|", "field");
         assertCompletion("class A { int method() { return 0; } static int a = meth|", true);
         assertCompletion("class A { int field = 0; static int method() { return fiel|", true);
     }
 
-    @Test(enabled = false) // TODO 8129421
+    @Test
     public void testClassDeclaration() {
+        assertEval("void ClazzM() {}");
+        assertEval("void InterfaceM() {}");
         assertEval("interface Interface {}");
         assertCompletion("interface A extends Interf|", "Interface");
         assertCompletion("class A implements Interf|", "Interface");
@@ -445,6 +447,27 @@
         assertCompletion("interface A implements Inter|");
         assertCompletion("class A implements Claz|", true);
         assertCompletion("class A extends Clazz implements Interface, Interf|", true, "Interface1");
+        assertCompletion("class A extends Clazz implements Interface, Interf|", true, "Interface1");
+        assertEval("class InterfaceClazz {}");
+        assertCompletion("class A <T extends Claz|", "Clazz");
+        assertCompletion("class A <T extends Interf|", "Interface", "Interface1", "InterfaceClazz");
+        assertCompletion("class A <T extends Interface & Interf|", "Interface", "Interface1", "InterfaceClazz");
+        assertCompletion("class A <T extends Clazz & Interf|", "Interface", "Interface1", "InterfaceClazz");
+        assertCompletion("class A <T extends Claz|", true, "Clazz");
+        assertCompletion("class A <T extends Interf|", true, "Interface", "Interface1", "InterfaceClazz");
+        assertCompletion("class A <T extends Interface & Interf|", true, "Interface1");
+        assertCompletion("class A <T extends Clazz & Interf|", true, "Interface", "Interface1");
+    }
+
+    public void testMethodDeclaration() {
+        assertEval("void ClazzM() {}");
+        assertEval("void InterfaceM() {}");
+        assertEval("interface Interface {}");
+        assertCompletion("void m(Interf|", "Interface");
+        assertCompletion("void m(Interface i1, Interf|", "Interface");
+        assertEval("class InterfaceException extends Exception {}");
+        assertCompletion("void m(Interface i1) throws Interf|", "Interface", "InterfaceException");
+        assertCompletion("void m(Interface i1) throws Interf|", true, "InterfaceException");
     }
 
     public void testDocumentationOfUserDefinedMethods() {