mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Sealed classes: completion in permits list (IDEA-243906)
GitOrigin-RevId: 0a72bf3a7baa7dc1550e8e4308431d78eb753eb6
This commit is contained in:
committed by
intellij-monorepo-bot
parent
95629c0874
commit
2d58fc74e7
+56
-16
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
-14
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+31
-35
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
public class Parent permits Ch<caret> {}
|
||||
|
||||
class Child1 extends Parent {}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
public class Parent permits Child1 {}
|
||||
|
||||
class Child1 extends Parent {}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
sealed class Parent permits Ch<caret> {}
|
||||
|
||||
final class A extends Parent {}
|
||||
|
||||
final class Child extends Parent {}
|
||||
+5
@@ -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 {}
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
sealed class Parent permits Foo, Bar {}
|
||||
|
||||
final class Foo extends Parent {}
|
||||
|
||||
final class Bar extends Parent {}
|
||||
|
||||
+12
@@ -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 {}')
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -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() }
|
||||
}
|
||||
Reference in New Issue
Block a user