From f0e1e069ffb9e064e2205c1ffac48176dca63430 Mon Sep 17 00:00:00 2001 From: Jan Lahoda <jlahoda@openjdk.org> Date: Mon, 15 Aug 2016 08:28:26 +0200 Subject: [PATCH] 8129421: JShell: unacceptable suggestions in 'extends', 'implements' in smart completion 8129422: JShell: methods and fields of uncompleted expressions should be suggested Fixing several completion bugs Reviewed-by: rfield --- .../jdk/jshell/SourceCodeAnalysisImpl.java | 96 ++++++++++++++++++- .../jdk/jshell/CompletionSuggestionTest.java | 31 +++++- 2 files changed, 120 insertions(+), 7 deletions(-) diff --git a/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java index 422b8ef7a61..bc6cf9170c1 100644 --- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java +++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java @@ -27,6 +27,7 @@ package jdk.jshell; 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.NewClassTree; 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.nio.file.attribute.BasicFileAttributes; 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 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { 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 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { 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 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { 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 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { ((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 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { 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 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { 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 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { }; @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) diff --git a/langtools/test/jdk/jshell/CompletionSuggestionTest.java b/langtools/test/jdk/jshell/CompletionSuggestionTest.java index beb5a783a00..93767169bed 100644 --- a/langtools/test/jdk/jshell/CompletionSuggestionTest.java +++ b/langtools/test/jdk/jshell/CompletionSuggestionTest.java @@ -415,7 +415,7 @@ public class CompletionSuggestionTest extends KullaTesting { 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 @@ public class CompletionSuggestionTest extends KullaTesting { 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 @@ public class CompletionSuggestionTest extends KullaTesting { 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() {