Sealed classes: completion in permits list (IDEA-243906)

GitOrigin-RevId: 0a72bf3a7baa7dc1550e8e4308431d78eb753eb6
This commit is contained in:
Artemiy Sartakov
2020-09-07 13:11:34 +00:00
committed by intellij-monorepo-bot
parent 95629c0874
commit 2d58fc74e7
12 changed files with 207 additions and 66 deletions
@@ -52,25 +52,25 @@ public final class JavaModuleGraphUtil {
if (file == null) return null;
ProjectFileIndex index = ProjectFileIndex.SERVICE.getInstance(project);
if (index.isInLibrary(file)) {
VirtualFile root = index.getClassRootForFile(file);
if (root != null) {
VirtualFile descriptorFile = JavaModuleNameIndex.descriptorFile(root);
if (descriptorFile != null) {
PsiFile psiFile = PsiManager.getInstance(project).findFile(descriptorFile);
if (psiFile instanceof PsiJavaFile) {
return ((PsiJavaFile)psiFile).getModuleDeclaration();
}
}
else if (root.getFileSystem() instanceof JarFileSystem && "jar".equalsIgnoreCase(root.getExtension())) {
return LightJavaModule.findModule(PsiManager.getInstance(project), root);
return index.isInLibrary(file)
? findLibraryFileModule(project, index, file)
: findDescriptorByModule(index.getModuleForFile(file), index.isInTestSourceContent(file));
}
private static PsiJavaModule findLibraryFileModule(@NotNull Project project, @NotNull ProjectFileIndex index, @NotNull VirtualFile file) {
VirtualFile root = index.getClassRootForFile(file);
if (root != null) {
VirtualFile descriptorFile = JavaModuleNameIndex.descriptorFile(root);
if (descriptorFile != null) {
PsiFile psiFile = PsiManager.getInstance(project).findFile(descriptorFile);
if (psiFile instanceof PsiJavaFile) {
return ((PsiJavaFile)psiFile).getModuleDeclaration();
}
}
else if (root.getFileSystem() instanceof JarFileSystem && "jar".equalsIgnoreCase(root.getExtension())) {
return LightJavaModule.findModule(PsiManager.getInstance(project), root);
}
}
else {
return findDescriptorByModule(index.getModuleForFile(file), index.isInTestSourceContent(file));
}
return null;
}
@@ -365,4 +365,44 @@ public final class JavaModuleGraphUtil {
return myInbound ? Collections.emptyIterator() : myEdges.get(n).iterator();
}
}
public static class JavaModuleScope extends GlobalSearchScope {
private final PsiJavaModule myModule;
private final boolean myIncludeLibraries;
private JavaModuleScope(Project project, PsiJavaModule module, boolean includeLibraries) {
super(project);
myModule = module;
myIncludeLibraries = includeLibraries;
}
@Override
public boolean isSearchInModuleContent(@NotNull Module aModule) {
VirtualFile moduleFile = aModule.getModuleFile();
if (moduleFile == null) return false;
boolean inTests = ProjectFileIndex.SERVICE.getInstance(getProject()).isInTestSourceContent(moduleFile);
return findDescriptorByModule(aModule, inTests) == myModule;
}
@Override
public boolean isSearchInLibraries() {
return myIncludeLibraries;
}
@Override
public boolean contains(@NotNull VirtualFile file) {
Project project = getProject();
if (project == null) return false;
if (!(myModule.getManager().findFile(file) instanceof PsiJavaFile)) return false;
ProjectFileIndex index = ProjectFileIndex.SERVICE.getInstance(project);
if (index.isInLibrary(file)) return myIncludeLibraries && findLibraryFileModule(project, index, file) == myModule;
Module fileModule = index.getModuleForFile(file);
return findDescriptorByModule(fileModule, index.isInTestSourceContent(file)) == myModule;
}
public static @NotNull JavaModuleScope moduleScope(@NotNull PsiJavaModule module, boolean includeLibraries) {
return new JavaModuleScope(module.getProject(), module, includeLibraries);
}
}
}
@@ -6,7 +6,10 @@ import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.ExpectedTypesProvider;
import com.intellij.codeInsight.TailType;
import com.intellij.codeInsight.TailTypes;
import com.intellij.codeInsight.completion.scope.CompletionElement;
import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil.JavaModuleScope;
import com.intellij.codeInsight.daemon.impl.analysis.LambdaHighlightingUtil;
import com.intellij.codeInsight.lookup.*;
import com.intellij.featureStatistics.FeatureUsageTracker;
@@ -48,14 +51,13 @@ import com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl;
import com.intellij.psi.impl.source.PsiLabelReference;
import com.intellij.psi.scope.ElementClassFilter;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PackageScope;
import com.intellij.psi.search.ProjectScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.Consumer;
import com.intellij.util.DocumentUtil;
import com.intellij.util.ProcessingContext;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
@@ -65,6 +67,7 @@ import java.util.*;
import static com.intellij.codeInsight.completion.ReferenceExpressionCompletionContributor.getSpace;
import static com.intellij.patterns.PsiJavaPatterns.*;
import static com.intellij.util.ObjectUtils.tryCast;
/**
* @author peter
@@ -107,8 +110,17 @@ public class JavaCompletionContributor extends CompletionContributor implements
@Nullable
public static ElementFilter getReferenceFilter(PsiElement position) {
if (isInExtendsOrImplementsList(position)) {
return new AndFilter(ElementClassFilter.CLASS, new NotFilter(new AssignableFromContextFilter()));
PsiClass containingClass = PsiTreeUtil.getParentOfType(position, PsiClass.class, false,
PsiCodeBlock.class, PsiMethod.class,
PsiExpressionList.class, PsiVariable.class, PsiAnnotation.class);
if (containingClass != null) {
if (isInPermitsList(position)) {
return new AndFilter(ElementClassFilter.CLASS, new NotFilter(new AssignableFromContextFilter(true)));
}
if (isInExtendsOrImplementsList(position)) {
return new AndFilter(ElementClassFilter.CLASS, new NotFilter(new AssignableFromContextFilter()));
}
}
if (getAnnotationNameIfInside(position) != null) {
@@ -189,14 +201,19 @@ public class JavaCompletionContributor extends CompletionContributor implements
}
private static boolean isInExtendsOrImplementsList(PsiElement position) {
PsiClass containingClass = PsiTreeUtil.getParentOfType(
position, PsiClass.class, false, PsiCodeBlock.class, PsiMethod.class, PsiExpressionList.class, PsiVariable.class, PsiAnnotation.class);
return containingClass != null &&
psiElement().afterLeaf(
psiElement()
.withText(string().oneOf(PsiKeyword.EXTENDS, PsiKeyword.IMPLEMENTS, ",", "&"))
.withParent(PsiReferenceList.class)
).accepts(position);
return psiElement().afterLeaf(
psiElement()
.withText(string().oneOf(PsiKeyword.EXTENDS, PsiKeyword.IMPLEMENTS, ",", "&"))
.withParent(PsiReferenceList.class)
).accepts(position);
}
static boolean isInPermitsList(PsiElement position) {
return psiElement().afterLeaf(
psiElement()
.withText(string().oneOf(PsiKeyword.PERMITS, ","))
.withParent(psiElement(PsiReferenceList.class).withFirstChild(psiElement(PsiKeyword.class).withText(PsiKeyword.PERMITS)))
).accepts(position);
}
private static boolean isInsideAnnotationName(PsiElement position) {
@@ -276,7 +293,16 @@ public class JavaCompletionContributor extends CompletionContributor implements
List<LookupElement> refSuggestions = Collections.emptyList();
if (parent instanceof PsiJavaCodeReferenceElement && mayCompleteReference) {
refSuggestions = completeReference(parameters, (PsiJavaCodeReferenceElement)parent, session, expectedInfos, matcher::prefixMatches);
PsiJavaCodeReferenceElement parentRef = (PsiJavaCodeReferenceElement)parent;
if (isInPermitsList(parent)) {
refSuggestions = completePermitsListReference(parameters, parentRef, matcher.getPrefix());
if (parameters.getInvocationCount() > 1) {
refSuggestions.addAll(completeReference(parameters, parentRef, session, expectedInfos, matcher::prefixMatches));
}
}
else {
refSuggestions = completeReference(parameters, parentRef, session, expectedInfos, matcher::prefixMatches);
}
List<LookupElement> filtered = filterReferenceSuggestions(parameters, expectedInfos, refSuggestions);
hasTypeMatchingSuggestions |= ContainerUtil.exists(filtered, item ->
ReferenceExpressionCompletionContributor.matchesExpectedType(item, expectedInfos));
@@ -629,6 +655,36 @@ public class JavaCompletionContributor extends CompletionContributor implements
return items;
}
private static @NotNull List<LookupElement> completePermitsListReference(@NotNull CompletionParameters parameters,
@NotNull PsiJavaCodeReferenceElement referenceElement,
@NotNull String prefix) {
List<LookupElement> lookupElements = new SmartList<>();
ElementFilter filter = getReferenceFilter(parameters.getPosition());
if (filter == null) return lookupElements;
PsiJavaFile psiJavaFile = tryCast(referenceElement.getContainingFile(), PsiJavaFile.class);
if (psiJavaFile == null) return lookupElements;
GlobalSearchScope scope = findScope(psiJavaFile);
if (scope == null) return lookupElements;
PlainPrefixMatcher prefixMatcher = new PlainPrefixMatcher(prefix);
AllClassesGetter.processJavaClasses(prefixMatcher, psiJavaFile.getProject(), scope, (Processor<? super PsiClass>)psiClass -> {
if (filter.isAcceptable(psiClass, referenceElement)) {
CompletionElement completionElement = new CompletionElement(psiClass, PsiSubstitutor.EMPTY);
Iterable<? extends LookupElement> elements = JavaCompletionUtil.createLookupElements(completionElement, referenceElement);
elements.forEach(e -> lookupElements.add(e));
}
return true;
});
return lookupElements;
}
private static @Nullable GlobalSearchScope findScope(@NotNull PsiJavaFile psiJavaFile) {
PsiJavaModule javaModule = JavaModuleGraphUtil.findDescriptorByElement(psiJavaFile.getOriginalElement());
if (javaModule != null) return JavaModuleScope.moduleScope(javaModule, false);
String packageName = psiJavaFile.getPackageName();
PsiPackage psiPackage = JavaPsiFacade.getInstance(psiJavaFile.getProject()).findPackage(packageName);
return psiPackage == null ? null : PackageScope.packageScopeWithoutLibraries(psiPackage, false);
}
static boolean shouldInsertSemicolon(PsiElement position) {
return position.getParent() instanceof PsiMethodReferenceExpression &&
LambdaHighlightingUtil.insertSemicolon(position.getParent().getParent());
@@ -73,7 +73,8 @@ public class JavaNoVariantsDelegator extends CompletionContributor implements Du
if (parameters.getCompletionType() == CompletionType.BASIC &&
parameters.getInvocationCount() <= 1 &&
JavaCompletionContributor.mayStartClassName(result) &&
JavaCompletionContributor.isClassNamePossible(parameters)) {
JavaCompletionContributor.isClassNamePossible(parameters) &&
!JavaCompletionContributor.isInPermitsList(parameters.getPosition())) {
suggestNonImportedClasses(parameters, JavaCompletionSorting.addJavaSorting(parameters, result.withPrefixMatcher(tracker.betterMatcher)), tracker.session);
}
}
@@ -1,55 +1,51 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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.
*/
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi.filters.classes;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.filters.ElementFilter;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.reference.SoftReference;
import com.intellij.util.ReflectionUtil;
public class AssignableFromContextFilter implements ElementFilter{
import static com.intellij.util.ObjectUtils.tryCast;
public class AssignableFromContextFilter implements ElementFilter {
private final boolean myCheckIfContextIsInheritor;
private SoftReference<PsiElement> myCurrentContext = new SoftReference<>(null);
private SoftReference<PsiClass> myCachedClass = new SoftReference<>(null);
public AssignableFromContextFilter() {
myCheckIfContextIsInheritor = false;
}
public AssignableFromContextFilter(boolean checkIfContextIsInheritor) {
myCheckIfContextIsInheritor = checkIfContextIsInheritor;
}
@Override
public boolean isClassAcceptable(Class hintClass){
public boolean isClassAcceptable(Class hintClass) {
return ReflectionUtil.isAssignable(PsiClass.class, hintClass);
}
private SoftReference myCurrentContext = new SoftReference(null);
private SoftReference myCachedClass = new SoftReference(null);
@Override
public boolean isAcceptable(Object element, PsiElement context){
if(myCurrentContext.get() != context){
myCurrentContext = new SoftReference(context);
PsiElement cachedClass = context;
while(cachedClass != null && !(cachedClass instanceof PsiClass))
cachedClass = cachedClass.getContext();
myCachedClass = new SoftReference(cachedClass);
public boolean isAcceptable(Object element, PsiElement context) {
if (myCurrentContext.get() != context) {
myCurrentContext = new SoftReference<>(context);
myCachedClass = new SoftReference<>(PsiTreeUtil.getContextOfType(context, false, PsiClass.class));
}
if(myCachedClass.get() instanceof PsiClass && element instanceof PsiClass){
final String qualifiedName = ((PsiClass)myCachedClass.get()).getQualifiedName();
return qualifiedName != null && (qualifiedName.equals(((PsiClass)element).getQualifiedName())
|| ((PsiClass)element).isInheritor((PsiClass)myCachedClass.get(), true));
}
return false;
PsiClass curClass = myCachedClass.get();
if (curClass == null) return false;
PsiClass candidate = tryCast(element, PsiClass.class);
if (candidate == null) return false;
String qualifiedName = curClass.getQualifiedName();
return qualifiedName != null &&
(qualifiedName.equals(candidate.getQualifiedName()) ||
(myCheckIfContextIsInheritor ? curClass.isInheritor(candidate, true) : candidate.isInheritor(curClass, true)));
}
public String toString(){
public String toString() {
return "assignable-from-context";
}
}
@@ -0,0 +1,3 @@
public class Parent permits Ch<caret> {}
class Child1 extends Parent {}
@@ -0,0 +1,3 @@
public class Parent permits Child1 {}
class Child1 extends Parent {}
@@ -0,0 +1,5 @@
sealed class Parent permits Ch<caret> {}
final class A extends Parent {}
final class Child extends Parent {}
@@ -0,0 +1,5 @@
sealed class Parent permits Child {}
final class A extends Parent {}
final class Child extends Parent {}
@@ -0,0 +1,6 @@
sealed class Parent permits Foo, B<caret> {}
final class Foo extends Parent {}
final class Bar extends Parent {}
@@ -0,0 +1,6 @@
sealed class Parent permits Foo, Bar {}
final class Foo extends Parent {}
final class Bar extends Parent {}
@@ -18,6 +18,7 @@ import com.intellij.project.IntelliJProjectConfiguration
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.statistics.StatisticsManager
@@ -249,4 +250,15 @@ public static void method(Runnable r) {}
myFixture.type('\n')
}
@NeedsIndex.Full
void testSealedClassInJavaModule() {
myFixture.addFileToProject('module-info.java', 'module Module1 {}')
myFixture.addFileToProject('bar/Child.java',
'package bar;\nimport foo.*;\npublic final class Child implements Foo {}')
myFixture.configureByText('foo/Foo.java',
'package foo;\npublic sealed interface Foo permits <caret> {}')
myFixture.complete(CompletionType.BASIC)
myFixture.type('\n')
myFixture.checkResult('package foo;\n\nimport bar.Child;\n\npublic sealed interface Foo permits Child {}')
}
}
@@ -34,4 +34,12 @@ class Normal14CompletionTest extends NormalCompletionTestCase {
void testTopLevelPublicRecord() { doTest() }
void testTopLevelPublicRecordParenthesisExists() { doTest() }
void testTopLevelPublicRecordBraceExists() { doTest() }
void testSealedClassDifferentPackageInheritor() {
myFixture.addClass("package bar;\nimport foo.*;\npublic final class Child2 extends Parent {}")
doTest()
}
void testSealedClassPermitsReference() { doTest() }
void testSecondPermitsReference() { doTest() }
}