Skip to content

8353581: Support for import module in JShell's code completion #24442

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 86 additions & 20 deletions src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java
Original file line number Diff line number Diff line change
@@ -81,6 +81,7 @@
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
@@ -248,13 +249,20 @@ public CompletionInfo analyzeCompletion(String srcInput) {
}

private Tree.Kind guessKind(String code) {
return guessKind(code, null);
}

private Tree.Kind guessKind(String code, boolean[] moduleImport) {
return proc.taskFactory.parse(code, pt -> {
List<? extends Tree> units = pt.units();
if (units.isEmpty()) {
return Tree.Kind.BLOCK;
}
Tree unitTree = units.get(0);
proc.debug(DBG_COMPA, "Kind: %s -- %s\n", unitTree.getKind(), unitTree);
if (moduleImport != null && unitTree.getKind() == Kind.IMPORT) {
moduleImport[0] = ((ImportTree) unitTree).isModule();
}
return unitTree.getKind();
});
}
@@ -290,23 +298,26 @@ private List<Suggestion> completionSuggestionsImpl(String code, int cursor, int[
if (code.trim().isEmpty()) { //TODO: comment handling
code += ";";
}
OuterWrap codeWrap = switch (guessKind(code)) {
case IMPORT -> proc.outerMap.wrapImport(Wrap.simpleWrap(code + "any.any"), null);
boolean[] moduleImport = new boolean[1];
OuterWrap codeWrap = switch (guessKind(code, moduleImport)) {
case IMPORT -> moduleImport[0] ? proc.outerMap.wrapImport(Wrap.simpleWrap(code), null)
: proc.outerMap.wrapImport(Wrap.simpleWrap(code + "any.any"), null);
case CLASS, METHOD -> proc.outerMap.wrapInTrialClass(Wrap.classMemberWrap(code));
default -> proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code));
};
String requiredPrefix = identifier;
return computeSuggestions(codeWrap, cursor, anchor).stream()
.filter(s -> s.continuation().startsWith(requiredPrefix) && !s.continuation().equals(REPL_DOESNOTMATTER_CLASS_NAME))
.filter(s -> s.filteringText.startsWith(requiredPrefix) && !s.continuation().equals(REPL_DOESNOTMATTER_CLASS_NAME))
.sorted(Comparator.comparing(Suggestion::continuation))
.map(s -> (Suggestion) s)
.toList();
}

private List<Suggestion> computeSuggestions(OuterWrap code, int cursor, int[] anchor) {
private List<SuggestionImpl> computeSuggestions(OuterWrap code, int cursor, int[] anchor) {
return proc.taskFactory.analyze(code, at -> {
SourcePositions sp = at.trees().getSourcePositions();
CompilationUnitTree topLevel = at.firstCuTree();
List<Suggestion> result = new ArrayList<>();
List<SuggestionImpl> result = new ArrayList<>();
TreePath tp = pathFor(topLevel, sp, code, cursor);
if (tp != null) {
Scope scope = at.trees().getScope(tp);
@@ -393,6 +404,16 @@ private List<Suggestion> computeSuggestions(OuterWrap code, int cursor, int[] an
TypeMirror site = at.trees().getTypeMirror(exprPath);
boolean staticOnly = isStaticContext(at, exprPath);
ImportTree it = findImport(tp);

if (it != null && it.isModule()) {
String fullCode = code.wrapped();
int selectStart = (int) sp.getStartPosition(topLevel, tp.getLeaf());
int selectEnd = (int) sp.getEndPosition(topLevel, tp.getLeaf());
String qualifiedPrefix = fullCode.substring(selectStart, selectEnd);

addModuleElements(at, qualifiedPrefix, result);
}

boolean isImport = it != null;

List<? extends Element> members = membersOf(at, site, staticOnly && !isImport && tp.getLeaf().getKind() == Kind.MEMBER_SELECT);
@@ -452,18 +473,25 @@ private List<Suggestion> computeSuggestions(OuterWrap code, int cursor, int[] an
}
ImportTree it = findImport(tp);
if (it != null) {
// the context of the identifier is an import, look for
// package names that start with the identifier.
// If and when Java allows imports from the default
// package to the default package which would allow
// JShell to change to use the default package, and that
// change is done, then this should use some variation
// of membersOf(at, at.getElements().getPackageElement("").asType(), false)
addElements(listPackages(at, ""),
it.isStatic()
? STATIC_ONLY.and(accessibility)
: accessibility,
smartFilter, result);
if (it.isModule()) {
addModuleElements(at, "", result);
} else {
// the context of the identifier is an import, look for
// package names that start with the identifier.
// If and when Java allows imports from the default
// package to the default package which would allow
// JShell to change to use the default package, and that
// change is done, then this should use some variation
// of membersOf(at, at.getElements().getPackageElement("").asType(), false)
addElements(listPackages(at, ""),
it.isStatic()
? STATIC_ONLY.and(accessibility)
: accessibility,
smartFilter, result);

//check source level(!)
result.add(new SuggestionImpl("module", false));
}
}
break;
case CLASS: {
@@ -977,10 +1005,18 @@ private Predicate<Element> createAccessibilityFilter(AnalyzeTask at, TreePath tp
private final Function<Boolean, String> DEFAULT_PAREN = hasParams -> hasParams ? "(" : "()";
private final Function<Boolean, String> NO_PAREN = hasParams -> "";

private void addElements(Iterable<? extends Element> elements, Predicate<Element> accept, Predicate<Element> smart, List<Suggestion> result) {
private void addElements(Iterable<? extends Element> elements,
Predicate<Element> accept,
Predicate<Element> smart,
List<SuggestionImpl> result) {
addElements(elements, accept, smart, DEFAULT_PAREN, result);
}
private void addElements(Iterable<? extends Element> elements, Predicate<Element> accept, Predicate<Element> smart, Function<Boolean, String> paren, List<Suggestion> result) {

private void addElements(Iterable<? extends Element> elements,
Predicate<Element> accept,
Predicate<Element> smart,
Function<Boolean, String> paren,
List<SuggestionImpl> result) {
Set<String> hasParams = Util.stream(elements)
.filter(accept)
.filter(IS_CONSTRUCTOR.or(IS_METHOD))
@@ -1012,6 +1048,19 @@ private void addElements(Iterable<? extends Element> elements, Predicate<Element
}
}

private void addModuleElements(AnalyzeTask at,
String prefix,
List<SuggestionImpl> result) {
for (ModuleElement me : at.getElements().getAllModuleElements()) {
if (!me.getQualifiedName().toString().startsWith(prefix)) {
continue;
}
result.add(new SuggestionImpl(me.getQualifiedName().toString(),
me.getSimpleName().toString(),
false));
}
}

private String simpleName(Element el) {
return el.getKind() == ElementKind.CONSTRUCTOR ? el.getEnclosingElement().getSimpleName().toString()
: el.getSimpleName().toString();
@@ -1372,7 +1421,10 @@ private TypeMirror resultTypeOf(Element el) {
};
}

private void addScopeElements(AnalyzeTask at, Scope scope, Function<Element, Iterable<? extends Element>> elementConvertor, Predicate<Element> filter, Predicate<Element> smartFilter, List<Suggestion> result) {
private void addScopeElements(AnalyzeTask at, Scope scope,
Function<Element, Iterable<? extends Element>> elementConvertor,
Predicate<Element> filter, Predicate<Element> smartFilter,
List<SuggestionImpl> result) {
addElements(scopeContent(at, scope, elementConvertor), filter, smartFilter, result);
}

@@ -2153,6 +2205,7 @@ public void waitBackgroundTaskFinished() throws Exception {
private static class SuggestionImpl implements Suggestion {

private final String continuation;
private final String filteringText;
private final boolean matchesType;

/**
@@ -2162,7 +2215,20 @@ private static class SuggestionImpl implements Suggestion {
* @param matchesType does the candidate match the target type
*/
public SuggestionImpl(String continuation, boolean matchesType) {
this(continuation, continuation, matchesType);
}

/**
* Create a {@code Suggestion} instance.
*
* @param continuation a candidate continuation of the user's input
* @param filteringText a filtering prefix
* @param matchesType does the candidate match the target type
*/
public SuggestionImpl(String continuation, String filteringText,
boolean matchesType) {
this.continuation = continuation;
this.filteringText = filteringText;
this.matchesType = matchesType;
}

12 changes: 10 additions & 2 deletions test/langtools/jdk/jshell/CompletionSuggestionTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -23,7 +23,7 @@

/*
* @test
* @bug 8131025 8141092 8153761 8145263 8131019 8175886 8176184 8176241 8176110 8177466 8197439 8221759 8234896 8240658 8278039 8286206 8296789 8314662 8326333
* @bug 8131025 8141092 8153761 8145263 8131019 8175886 8176184 8176241 8176110 8177466 8197439 8221759 8234896 8240658 8278039 8286206 8296789 8314662 8326333 8353581
* @summary Test Completion and Documentation
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
@@ -821,4 +821,12 @@ public void testArray() {
assertCompletion("ints.le|", "length");
assertCompletion("String[].|", "class");
}

//JDK-8353581: completion for module imports:
public void testModuleImport() {
assertCompletionIncludesExcludes("import |", Set.of("module"), Set.of());
assertCompletionIncludesExcludes("import module |", Set.of("java.base"), Set.of("java.", "module"));
assertCompletionIncludesExcludes("import module java.|", Set.of("java.base"), Set.of());
assertCompletion("import module java.ba|", "java.base");
}
}