diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java
index 312bbd0f4533..aecbf3457688 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightClassUtil.java
@@ -31,9 +31,12 @@ import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.PackageScope;
+import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.*;
import com.intellij.util.JavaPsiConstructorUtil;
+import com.intellij.util.ObjectUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -964,21 +967,6 @@ public class HighlightClassUtil {
return null;
}
- public static HighlightInfo checkWellFormedSealedInheritor(PsiClass psiClass) {
- if (!psiClass.hasModifierProperty(PsiModifier.SEALED) &&
- !psiClass.hasModifierProperty(PsiModifier.NON_SEALED) &&
- !psiClass.hasModifierProperty(PsiModifier.FINAL)) {
- PsiIdentifier nameIdentifier = psiClass.getNameIdentifier();
- if (nameIdentifier == null) return null;
- if (Arrays.stream(psiClass.getSuperTypes()).map(superType -> superType.resolve())
- .anyMatch(superClass -> superClass != null && superClass.hasModifierProperty(PsiModifier.SEALED))) {
- return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameIdentifier)
- .descriptionAndTooltip(JavaErrorBundle.message("sealed.type.inheritor.expected.modifiers", PsiModifier.SEALED, PsiModifier.NON_SEALED, PsiModifier.FINAL)).create();
- }
- }
- return null;
- }
-
public static HighlightInfo checkIllegalInstanceMemberInRecord(PsiMember member) {
if (!member.hasModifierProperty(PsiModifier.STATIC)) {
PsiClass aClass = member.getContainingClass();
@@ -1016,6 +1004,12 @@ public class HighlightClassUtil {
static HighlightInfo checkExtendsSealedClass(PsiClass aClass, PsiClass superClass, PsiJavaCodeReferenceElement elementToHighlight) {
if (superClass.hasModifierProperty(PsiModifier.SEALED)) {
+ if (PsiUtil.isLocalClass(aClass)) {
+ return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
+ .range(elementToHighlight)
+ .descriptionAndTooltip(JavaErrorBundle.message("local.classes.must.extend.sealed.classes")).create();
+ }
+
PsiClassType[] permittedTypes = superClass.getPermitsListTypes();
if (permittedTypes.length > 0) {
if (Arrays.stream(permittedTypes).map(permittedType -> permittedType.resolve()).anyMatch(permittedClass -> aClass.equals(permittedClass))) {
@@ -1037,4 +1031,105 @@ public class HighlightClassUtil {
}
return null;
}
+
+ public static HighlightInfo checkAnonymousSealedProhibited(PsiNewExpression newExpression) {
+ PsiAnonymousClass aClass = newExpression.getAnonymousClass();
+ if (aClass != null) {
+ PsiClass superClass = aClass.getBaseClassType().resolve();
+ if (superClass != null && superClass.hasModifierProperty(PsiModifier.SEALED)) {
+ return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
+ .range(aClass.getBaseClassReference())
+ .descriptionAndTooltip(JavaErrorBundle.message("anonymous.classes.must.extend.sealed.classes")).create();
+ }
+ }
+ return null;
+ }
+
+ static void checkPermitsList(PsiReferenceList list,
+ HighlightInfoHolder holder) {
+ PsiElement parent = list.getParent();
+ if (parent instanceof PsiClass && list.equals(((PsiClass)parent).getPermitsList())) {
+ PsiClass aClass = (PsiClass)parent;
+ PsiIdentifier nameIdentifier = aClass.getNameIdentifier();
+ if (nameIdentifier == null) return;
+ if (!aClass.hasModifierProperty(PsiModifier.SEALED)) {
+ holder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
+ .range(list.getFirstChild())
+ .descriptionAndTooltip(JavaErrorBundle.message("invalid.permits.clause", aClass.getName()))
+ .create());
+ }
+
+ PsiJavaModule currentModule = JavaModuleGraphUtil.findDescriptorByElement(aClass);
+ JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(aClass.getProject());
+ for (PsiJavaCodeReferenceElement permitted : list.getReferenceElements()) {
+ @Nullable PsiElement resolve = permitted.resolve();
+ if (resolve instanceof PsiClass) {
+ PsiClass inheritorClass = (PsiClass)resolve;
+ if (Arrays.stream(inheritorClass.getSuperTypes()).noneMatch(type -> aClass.equals(type.resolve()))) {
+ holder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(permitted)
+ .descriptionAndTooltip(JavaErrorBundle.message("invalid.permits.clause.direct.implementation",
+ inheritorClass.getName(),
+ inheritorClass.isInterface() == aClass.isInterface() ? 1 : 2,
+ aClass.getName()))
+ .create());
+ }
+ else {
+ if (currentModule == null && !psiFacade.arePackagesTheSame(aClass, inheritorClass)) {
+ holder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
+ .range(permitted)
+ .descriptionAndTooltip(JavaErrorBundle.message("class.not.allowed.to.extend.sealed.class.from.another.package"))
+ .create());
+ }
+ else if (currentModule != null && currentModule != JavaModuleGraphUtil.findDescriptorByElement(inheritorClass)) {
+ holder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
+ .range(permitted)
+ .descriptionAndTooltip(JavaErrorBundle.message("class.not.allowed.to.extend.sealed.class.from.another.module"))
+ .create());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public static HighlightInfo checkSealedNonEnumeratedInheritors(PsiClass psiClass) {
+ if (psiClass.hasModifierProperty(PsiModifier.SEALED) && psiClass.getPermitsListTypes().length == 0) {
+ PsiIdentifier nameIdentifier = psiClass.getNameIdentifier();
+ if (nameIdentifier == null) return null;
+ PsiJavaModule currentModule = JavaModuleGraphUtil.findDescriptorByElement(psiClass);
+ GlobalSearchScope searchScope = null;
+ if (currentModule == null) {
+ PsiClassOwner classOwner = ObjectUtils.tryCast(psiClass.getContainingFile(), PsiClassOwner.class);
+ if (classOwner != null) {
+ String packageName = classOwner.getPackageName();
+ PsiPackage aPackage = JavaPsiFacade.getInstance(psiClass.getProject()).findPackage(packageName);
+ searchScope = PackageScope.packageScope(Objects.requireNonNull(aPackage), false);
+ }
+ }
+ else {
+ searchScope = currentModule.getResolveScope();
+ }
+
+ if (searchScope != null && !DirectClassInheritorsSearch.search(psiClass, searchScope).anyMatch(c -> !PsiUtil.isLocalOrAnonymousClass(c))) {
+ return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
+ .range(nameIdentifier)
+ .descriptionAndTooltip(JavaErrorBundle.message("sealed.must.have.inheritors"))
+ .create();
+ }
+ }
+ return null;
+ }
+
+ public static HighlightInfo checkSealedSuper(PsiClass aClass) {
+ PsiIdentifier nameIdentifier = aClass.getNameIdentifier();
+ if (nameIdentifier != null &&
+ !aClass.hasModifierProperty(PsiModifier.SEALED) &&
+ !aClass.hasModifierProperty(PsiModifier.NON_SEALED) &&
+ !aClass.hasModifierProperty(PsiModifier.FINAL) &&
+ Arrays.stream(aClass.getSuperTypes()).map(type -> type.resolve()).anyMatch(superClass -> superClass != null && superClass.hasModifierProperty(PsiModifier.SEALED))) {
+ return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameIdentifier)
+ .descriptionAndTooltip(JavaErrorBundle.message("sealed.type.inheritor.expected.modifiers", PsiModifier.SEALED, PsiModifier.NON_SEALED, PsiModifier.FINAL)).create();
+ }
+ return null;
+ }
}
diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java
index 60a905e6a275..b4a1a2c0905c 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java
@@ -473,7 +473,8 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkClassAndPackageConflict(aClass));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkPublicClassInRightFile(aClass));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkWellFormedRecord(aClass));
- if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkWellFormedSealedInheritor(aClass));
+ if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkSealedNonEnumeratedInheritors(aClass));
+ if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkSealedSuper(aClass));
if (!myHolder.hasErrorResults()) myHolder.add(GenericsHighlightUtil.checkTypeParameterOverrideEquivalentMethods(aClass, myLanguageLevel));
}
@@ -1066,6 +1067,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
myHolder.add(HighlightUtil.checkUnhandledExceptions(expression, classReference != null ? classReference.getTextRange() : null));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkAnonymousInheritFinal(expression));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkAnonymousInheritProhibited(expression));
+ if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkAnonymousSealedProhibited(expression));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkQualifiedNew(expression, type, aClass));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkCreateInnerClassFromStaticContext(expression, type, aClass));
if (!myHolder.hasErrorResults()) myHolder.add(GenericsHighlightUtil.checkTypeParameterInstantiation(expression));
@@ -1649,6 +1651,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkExtendsAllowed(list));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkImplementsAllowed(list));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkClassExtendsOnlyOneClass(list));
+ if (!myHolder.hasErrorResults()) HighlightClassUtil.checkPermitsList(list, myHolder);
if (!myHolder.hasErrorResults()) myHolder.add(GenericsHighlightUtil.checkGenericCannotExtendException(list));
}
}
diff --git a/java/java-psi-impl/src/messages/JavaErrorBundle.properties b/java/java-psi-impl/src/messages/JavaErrorBundle.properties
index ba34d1e2b5bd..7007b6869d10 100644
--- a/java/java-psi-impl/src/messages/JavaErrorBundle.properties
+++ b/java/java-psi-impl/src/messages/JavaErrorBundle.properties
@@ -477,3 +477,10 @@ safevarargs.not.allowed.on.methods.with.fixed.arity=@SafeVarargs is not allowed
functional.interface.must.not.be.sealed.error.description=Functional interface can''t be declared as ''{0}''
sealed.type.inheritor.expected.modifiers={0}, {1} or {2} modifiers expected
not.allowed.in.sealed.hierarchy={0} is not allowed in the sealed hierarchy
+invalid.permits.clause=Invalid permits clause: ''{0}'' must be sealed
+invalid.permits.clause.direct.implementation=Invalid permits clause: ''{0}'' must directly {1, choice, 1#extend|2#implement} ''{2}''
+sealed.must.have.inheritors=Sealed class must have subclasses
+local.classes.must.extend.sealed.classes=Local classes must not extend sealed classes
+anonymous.classes.must.extend.sealed.classes=Anonymous classes must not extend sealed classes
+class.not.allowed.to.extend.sealed.class.from.another.package=Class is not allowed to extend sealed class from another package
+class.not.allowed.to.extend.sealed.class.from.another.module=Class is not allowed to extend sealed class from another module
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingSealedTypes/PermitsList.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingSealedTypes/PermitsList.java
new file mode 100644
index 000000000000..dabe372e9c71
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingSealedTypes/PermitsList.java
@@ -0,0 +1,20 @@
+package p;
+interface I0 permits I {}
+
+sealed interface I extends I0 permits Unresolved, I1, I1{}
+non-sealed interface I1 extends I {}
+
+sealed interface A {}
+class Usage {
+ {
+ class Local implements A {}
+ A a = new A() {};
+ }
+}
+
+
+sealed interface Indirect permits IndirectInheritor, MiddleMan {}
+non-sealed interface MiddleMan extends Indirect {}
+final class IndirectInheritor implements MiddleMan {}
+
+sealed class AnotherPackage permits p1.P1 {}
\ No newline at end of file
diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightSealedTypesHighlightingTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightSealedTypesHighlightingTest.java
index d2b3a14310e5..d88e350ced37 100644
--- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightSealedTypesHighlightingTest.java
+++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightSealedTypesHighlightingTest.java
@@ -21,6 +21,11 @@ public class LightSealedTypesHighlightingTest extends LightJavaCodeInsightFixtur
public void testSealedTypesBasics() { doTest(); }
public void testSealedFunctionalInterface() { doTest(); }
public void testSealedRestrictedTypes() { doTest(); }
+ public void testPermitsList() {
+ myFixture.addClass("package p1; public class P1 extends p.AnotherPackage {}");
+ myFixture.addClass("package p; public class P {}");
+ doTest();
+ }
private void doTest() {
myFixture.configureByFile(getTestName(false) + ".java");