diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java index 12fe69080241..403aeb03b82f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java @@ -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); + } + } } \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java index 800c3ce6c929..1504bfcf41f4 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java @@ -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 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 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 completePermitsListReference(@NotNull CompletionParameters parameters, + @NotNull PsiJavaCodeReferenceElement referenceElement, + @NotNull String prefix) { + List 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)psiClass -> { + if (filter.isAcceptable(psiClass, referenceElement)) { + CompletionElement completionElement = new CompletionElement(psiClass, PsiSubstitutor.EMPTY); + Iterable 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()); diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaNoVariantsDelegator.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaNoVariantsDelegator.java index 1251f15f00a0..8fd399aec77b 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaNoVariantsDelegator.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaNoVariantsDelegator.java @@ -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); } } diff --git a/java/java-impl/src/com/intellij/psi/filters/classes/AssignableFromContextFilter.java b/java/java-impl/src/com/intellij/psi/filters/classes/AssignableFromContextFilter.java index c666736d32d6..258e820093b2 100644 --- a/java/java-impl/src/com/intellij/psi/filters/classes/AssignableFromContextFilter.java +++ b/java/java-impl/src/com/intellij/psi/filters/classes/AssignableFromContextFilter.java @@ -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 myCurrentContext = new SoftReference<>(null); + private SoftReference 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"; } } diff --git a/java/java-tests/testData/codeInsight/completion/normal/SealedClassDifferentPackageInheritor.java b/java/java-tests/testData/codeInsight/completion/normal/SealedClassDifferentPackageInheritor.java new file mode 100644 index 000000000000..78bfe2e0646b --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/SealedClassDifferentPackageInheritor.java @@ -0,0 +1,3 @@ +public class Parent permits Ch {} + +class Child1 extends Parent {} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/normal/SealedClassDifferentPackageInheritor_after.java b/java/java-tests/testData/codeInsight/completion/normal/SealedClassDifferentPackageInheritor_after.java new file mode 100644 index 000000000000..90c5121251a4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/SealedClassDifferentPackageInheritor_after.java @@ -0,0 +1,3 @@ +public class Parent permits Child1 {} + +class Child1 extends Parent {} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/normal/SealedClassPermitsReference.java b/java/java-tests/testData/codeInsight/completion/normal/SealedClassPermitsReference.java new file mode 100644 index 000000000000..08321fb03f11 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/SealedClassPermitsReference.java @@ -0,0 +1,5 @@ +sealed class Parent permits Ch {} + +final class A extends Parent {} + +final class Child extends Parent {} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/normal/SealedClassPermitsReference_after.java b/java/java-tests/testData/codeInsight/completion/normal/SealedClassPermitsReference_after.java new file mode 100644 index 000000000000..b0271bb5a66b --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/SealedClassPermitsReference_after.java @@ -0,0 +1,5 @@ +sealed class Parent permits Child {} + +final class A extends Parent {} + +final class Child extends Parent {} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/normal/SecondPermitsReference.java b/java/java-tests/testData/codeInsight/completion/normal/SecondPermitsReference.java new file mode 100644 index 000000000000..8ca8099fffe8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/SecondPermitsReference.java @@ -0,0 +1,6 @@ +sealed class Parent permits Foo, B {} + +final class Foo extends Parent {} + +final class Bar extends Parent {} + diff --git a/java/java-tests/testData/codeInsight/completion/normal/SecondPermitsReference_after.java b/java/java-tests/testData/codeInsight/completion/normal/SecondPermitsReference_after.java new file mode 100644 index 000000000000..3043e0f51aa2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/SecondPermitsReference_after.java @@ -0,0 +1,6 @@ +sealed class Parent permits Foo, Bar {} + +final class Foo extends Parent {} + +final class Bar extends Parent {} + diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/HeavyCompletionTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/HeavyCompletionTest.groovy index 340085cbe0d8..b04bd85da3b7 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/HeavyCompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/HeavyCompletionTest.groovy @@ -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 {}') + myFixture.complete(CompletionType.BASIC) + myFixture.type('\n') + myFixture.checkResult('package foo;\n\nimport bar.Child;\n\npublic sealed interface Foo permits Child {}') + } } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal14CompletionTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal14CompletionTest.groovy index 04957071907c..d957442e1c14 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal14CompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal14CompletionTest.groovy @@ -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() } } \ No newline at end of file