diff --git a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties index fbba78919847..9fac7fcba964 100644 --- a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties +++ b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties @@ -123,6 +123,7 @@ class.inherits.type.parameter=Class cannot inherit from its type parameter class.anonymous.extends.sealed=Anonymous classes must not extend sealed classes class.already.imported=''{0}'' is already defined in this compilation unit class.not.enclosing=''{0}'' is not an enclosing class +local.class.cannot.be.instantiated.from.different.static.context=Local class ''{0}'' cannot be instantiated from a different static context class.cannot.be.referenced.from.static.context=''{0}'' cannot be referenced from a static context class.inheritance.different.type.arguments=''{0}'' cannot be inherited with different type arguments: ''{1}'' and ''{2}'' class.inheritance.raw.and.generic=''{0}'' cannot be inherited as a raw type and with generic type arguments ''{1}'' diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java index cb7d9333a5ff..ba019396e5d7 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java @@ -95,9 +95,25 @@ final class ExpressionChecker { void checkCreateInnerClassFromStaticContext(@NotNull PsiElement element, @NotNull PsiElement placeToSearchEnclosingFrom, @NotNull PsiClass aClass) { - if (!PsiUtil.isInnerClass(aClass)) return; + if (aClass.hasModifierProperty(PsiModifier.STATIC)) return; PsiClass outerClass = aClass.getContainingClass(); - if (outerClass == null) return; + if (outerClass == null) { + if (!(aClass.getParent() instanceof PsiDeclarationStatement)) return; + PsiMember scope = PsiTreeUtil.getParentOfType(aClass, PsiMember.class); // local class + if (scope == null) return; + if (scope.hasModifierProperty(PsiModifier.STATIC)) { + PsiModifierListOwner enclosingStaticElement = PsiUtil.getEnclosingStaticElement(element, null); + assert enclosingStaticElement != null; + if (enclosingStaticElement != scope) { + JavaErrorKinds.LocalClassInstantiationErrorContext context = + new JavaErrorKinds.LocalClassInstantiationErrorContext(aClass, enclosingStaticElement); + myVisitor.report(JavaErrorKinds.LOCAL_CLASS_INSTANTIATED_FROM_DIFFERENT_STATIC_CONTEXT.create(element, context)); + } + return; + } + outerClass = scope.getContainingClass(); + if (outerClass == null) return; + } if (outerClass instanceof PsiSyntheticClass || InheritanceUtil.hasEnclosingInstanceInScope(outerClass, placeToSearchEnclosingFrom, true, false)) { diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java index 316c9d2a5ee6..72b3985adfa3 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java @@ -438,6 +438,9 @@ public final class JavaErrorKinds { error(PsiClass.class, "class.sealed.permits.on.non.sealed") .withAnchor(cls -> requireNonNull(cls.getPermitsList()).getFirstChild()) .withDescription(cls -> message("class.sealed.permits.on.non.sealed", cls.getName())); + public static final Parameterized LOCAL_CLASS_INSTANTIATED_FROM_DIFFERENT_STATIC_CONTEXT = + parameterized(PsiElement.class, LocalClassInstantiationErrorContext.class, "local.class.cannot.be.instantiated.from.different.static.context") + .withDescription((psi, ctx) -> message("local.class.cannot.be.instantiated.from.different.static.context", ctx.localClass().getName())); public static final Parameterized CLASS_NOT_ENCLOSING = parameterized(PsiElement.class, ClassStaticReferenceErrorContext.class, "class.not.enclosing") .withDescription((psi, ctx) -> message("class.not.enclosing", formatClass(ctx.outerClass()))); @@ -1688,13 +1691,15 @@ public final class JavaErrorKinds { } public record ClassStaticReferenceErrorContext(@NotNull PsiClass outerClass, - @Nullable PsiClass innerClass, + @Nullable PsiClass innerClass, @NotNull PsiElement place) { public @Nullable PsiModifierListOwner enclosingStaticElement() { return PsiUtil.getEnclosingStaticElement(place, outerClass); } } + public record LocalClassInstantiationErrorContext(@NotNull PsiClass localClass, @NotNull PsiModifierListOwner enclosingStaticElement) {} + /** * A context for {@link #CONSTRUCTOR_AMBIGUOUS_IMPLICIT_CALL} error kind * @param psiClass a class where an ambiguous call is performed @@ -1703,7 +1708,7 @@ public final class JavaErrorKinds { */ public record AmbiguousImplicitConstructorCallContext(@NotNull PsiClass psiClass, @NotNull PsiMethod candidate1, - @NotNull PsiMethod candidate2) { + @NotNull PsiMethod candidate2) { @Nls String description() { String m1 = PsiFormatUtil.formatMethod(candidate1, PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/DefaultJavaErrorFixProvider.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/DefaultJavaErrorFixProvider.java index 847dc1ce74c2..c3abc32d9ad0 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/DefaultJavaErrorFixProvider.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/DefaultJavaErrorFixProvider.java @@ -967,6 +967,8 @@ public final class DefaultJavaErrorFixProvider extends AbstractJavaErrorFixProvi fix(CLASS_CANNOT_BE_REFERENCED_FROM_STATIC_CONTEXT, makeInnerStatic); fix(CLASS_CANNOT_BE_REFERENCED_FROM_STATIC_CONTEXT, error -> removeModifierFix(requireNonNull(error.context().enclosingStaticElement()), PsiModifier.STATIC)); + fix(LOCAL_CLASS_INSTANTIATED_FROM_DIFFERENT_STATIC_CONTEXT, + error -> removeModifierFix(requireNonNull(error.context().enclosingStaticElement()), PsiModifier.STATIC)); fix(CLASS_GENERIC_EXTENDS_EXCEPTION, error -> { PsiJavaCodeReferenceElement ref = error.psi(); PsiMember owner = PsiTreeUtil.getParentOfType(ref, PsiClass.class, PsiMethod.class); diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/LocalClassInstantiation.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/LocalClassInstantiation.java new file mode 100644 index 000000000000..f2bc6cff32a0 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/LocalClassInstantiation.java @@ -0,0 +1,94 @@ +class LocalInstantiation { + + // local class in method + static void foo(Object there) { + class Local { + { + there.hashCode(); + } + static { + //can only be instantiated from its own static context + //cannot be instantiated here + //not allowed to be instantiated heree + //cannot be instantiated outside its static context + new Local(); // should be highlighted as an error + } + static Runnable r = () -> { + new Local(); // should be highlighted as an error + }; + } + } + + // local class in lambda + static Runnable foo = () -> { + Object there = ""; + class Local { + { + there.hashCode(); + } + static { + new Local(); // should be highlighted as an error + } + static Runnable r = () -> { + new Local(); // should be highlighted as an error + }; + } + }; + + // local class in switch + static Object bar = switch (foo) { + case Runnable r -> { + Object there = ""; + class Local { + { + there.hashCode(); + } + + static { + new Local(); // should be highlighted as an error + } + + static Runnable r = () -> { + new Local(); // should be highlighted as an error + }; + } + yield r; + } + }; + + // local class in instance init + { + Object there = ""; + class Local { + { + there.hashCode(); + } + + static { + new Local(); // should be highlighted as an error + } + + static Runnable r = () -> { + new Local(); // should be highlighted as an error + }; + } + } + + // local class in static init + static { + Object there = ""; + class Local { + { + there.hashCode(); + } + + static { + new Local(); // should be highlighted as an error + } + + static Runnable r = () -> { + new Local(); // should be highlighted as an error + }; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAdvHighlightingTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAdvHighlightingTest.java index 3e29bc63be9c..c110e2cd900c 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAdvHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAdvHighlightingTest.java @@ -409,6 +409,7 @@ public class LightAdvHighlightingTest extends LightDaemonAnalyzerTestCase { public void testStatementWithExpression() { doTest(false); } public void testReturnFromConstructor() { doTest(false); } public void testInheritFromFinalLocalClass() { doTest(false); } + public void testLocalClassInstantiation() { IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_24, () -> doTest(false)); } public void testStaticMethodCalls() { doTestFile(BASE_PATH + "/" + getTestName(false) + ".java").checkSymbolNames().test();