Java: report local class instantiation from a different static context (IDEA-372971)

GitOrigin-RevId: 6bc936ee3dd849db2db187b03edac58f4c9b2c73
This commit is contained in:
Bas Leijdekkers
2025-06-20 18:38:03 +00:00
committed by intellij-monorepo-bot
parent 7ca512a863
commit 109ff355e9
6 changed files with 123 additions and 4 deletions
@@ -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}''
@@ -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)) {
@@ -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<PsiElement, LocalClassInstantiationErrorContext> 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<PsiElement, ClassStaticReferenceErrorContext> 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 |
@@ -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);
@@ -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
<error descr="Local class 'Local' cannot be instantiated from a different static context">new Local()</error>; // should be highlighted as an error
}
static Runnable r = () -> {
<error descr="Local class 'Local' cannot be instantiated from a different static context">new Local()</error>; // should be highlighted as an error
};
}
}
// local class in lambda
static Runnable foo = () -> {
Object there = "";
class Local {
{
there.hashCode();
}
static {
<error descr="Local class 'Local' cannot be instantiated from a different static context">new Local()</error>; // should be highlighted as an error
}
static Runnable r = () -> {
<error descr="Local class 'Local' cannot be instantiated from a different static context">new Local()</error>; // 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 {
<error descr="Local class 'Local' cannot be instantiated from a different static context">new Local()</error>; // should be highlighted as an error
}
static Runnable r = () -> {
<error descr="Local class 'Local' cannot be instantiated from a different static context">new Local()</error>; // should be highlighted as an error
};
}
yield r;
}
};
// local class in instance init
{
Object there = "";
class Local {
{
there.hashCode();
}
static {
<error descr="'LocalInstantiation.this' cannot be referenced from a static context">new Local()</error>; // should be highlighted as an error
}
static Runnable r = () -> {
<error descr="'LocalInstantiation.this' cannot be referenced from a static context">new Local()</error>; // should be highlighted as an error
};
}
}
// local class in static init
static {
Object there = "";
class Local {
{
there.hashCode();
}
static {
<error descr="Local class 'Local' cannot be instantiated from a different static context">new Local()</error>; // should be highlighted as an error
}
static Runnable r = () -> {
<error descr="Local class 'Local' cannot be instantiated from a different static context">new Local()</error>; // should be highlighted as an error
};
}
}
}
@@ -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();