diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java index 6c3f2c9545b9..71ded6325b9e 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -2788,11 +2788,18 @@ public final class HighlightUtil { referencedClass = PsiUtil.resolveClassInType(((PsiThisExpression)qualifier).getType()); } } - if (resolved instanceof PsiField referencedField) { - if (referencedField.hasModifierProperty(PsiModifier.STATIC)) return null; - resolvedName = PsiFormatUtil - .formatVariable(referencedField, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, PsiSubstitutor.EMPTY); - referencedClass = referencedField.getContainingClass(); + if (resolved instanceof PsiField field) { + if (field.hasModifierProperty(PsiModifier.STATIC)) return null; + LanguageLevel languageLevel = PsiUtil.getLanguageLevel(expression); + if (JavaFeature.STATEMENTS_BEFORE_SUPER.isSufficient(languageLevel) && + languageLevel != LanguageLevel.JDK_22_PREVIEW && + isOnSimpleAssignmentLeftHand(expression) && + field.getContainingClass() == PsiTreeUtil.getParentOfType(expression, PsiClass.class, true)) { + return null; + } + resolvedName = + PsiFormatUtil.formatVariable(field, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, PsiSubstitutor.EMPTY); + referencedClass = field.getContainingClass(); } else if (resolved instanceof PsiMethod method) { if (method.hasModifierProperty(PsiModifier.STATIC)) return null; @@ -2913,7 +2920,19 @@ public final class HighlightUtil { } } - HighlightInfo.Builder builder = createMemberReferencedError(resolvedName, expression.getTextRange()); + if (expression instanceof PsiThisExpression) { + LanguageLevel languageLevel = PsiUtil.getLanguageLevel(expression); + if (JavaFeature.STATEMENTS_BEFORE_SUPER.isSufficient(languageLevel) && languageLevel != LanguageLevel.JDK_22_PREVIEW) { + parent = PsiUtil.skipParenthesizedExprUp(parent); + if (isOnSimpleAssignmentLeftHand(parent) && + parent instanceof PsiReferenceExpression ref && + ref.resolve() instanceof PsiField field && + field.getContainingClass() == PsiTreeUtil.getParentOfType(expression, PsiClass.class, true)) { + return null; + } + } + } + HighlightInfo.Builder builder = createMemberReferencedError(resolvedName, expression.getTextRange(), resolved instanceof PsiMethod); if (expression instanceof PsiReferenceExpression ref && PsiUtil.isInnerClass(parentClass)) { String referenceName = ref.getReferenceName(); PsiClass containingClass = parentClass.getContainingClass(); @@ -2927,9 +2946,18 @@ public final class HighlightUtil { return builder; } + private static boolean isOnSimpleAssignmentLeftHand(@NotNull PsiElement expr) { + PsiElement parent = PsiTreeUtil.skipParentsOfType(expr, PsiParenthesizedExpression.class); + return parent instanceof PsiAssignmentExpression assignment && + JavaTokenType.EQ == assignment.getOperationTokenType() && + PsiTreeUtil.isAncestor(assignment.getLExpression(), expr, false); + } + @NotNull - private static HighlightInfo.Builder createMemberReferencedError(@NotNull String resolvedName, @NotNull TextRange textRange) { - String description = JavaErrorBundle.message("member.referenced.before.constructor.called", resolvedName); + private static HighlightInfo.Builder createMemberReferencedError(@NotNull String resolvedName, @NotNull TextRange textRange, boolean methodCall) { + String description = methodCall + ? JavaErrorBundle.message("method.called.before.constructor.called", resolvedName) + : JavaErrorBundle.message("member.referenced.before.constructor.called", resolvedName); return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description); } @@ -2946,12 +2974,12 @@ public final class HighlightUtil { PsiMethod[] constructors = aClass.getConstructors(); if (constructors.length == 0) { TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return createMemberReferencedError(aClass.getName() + ".this", range); + return createMemberReferencedError(aClass.getName() + ".this", range, false); } for (PsiMethod constructor : constructors) { PsiMethodCallExpression call = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor); if (!JavaPsiConstructorUtil.isSuperConstructorCall(call)) { - return createMemberReferencedError(aClass.getName() + ".this", HighlightNamesUtil.getMethodDeclarationTextRange(constructor)); + return createMemberReferencedError(aClass.getName() + ".this", HighlightNamesUtil.getMethodDeclarationTextRange(constructor), false); } } return null; diff --git a/java/java-psi-impl/resources/messages/JavaErrorBundle.properties b/java/java-psi-impl/resources/messages/JavaErrorBundle.properties index bec4a4d5b6fc..bb1399e321d1 100644 --- a/java/java-psi-impl/resources/messages/JavaErrorBundle.properties +++ b/java/java-psi-impl/resources/messages/JavaErrorBundle.properties @@ -309,7 +309,8 @@ illegal.self.reference.enum=Cannot refer to enum constant ''{0}'' from inside it unknown.class=Unknown class: ''{0}'' illegal.type.void=Illegal type: 'void' -member.referenced.before.constructor.called=Cannot reference ''{0}'' before supertype constructor has been called +member.referenced.before.constructor.called=Cannot reference ''{0}'' before superclass constructor is called +method.called.before.constructor.called=Cannot call ''{0}'' before superclass constructor is called label.without.statement=Label without statement duplicate.label=Label ''{0}'' already in use unclosed.comment=Unclosed comment diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ConstantReferencedViaInstance.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ConstantReferencedViaInstance.java index edf1db0c714c..5f52786a6499 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ConstantReferencedViaInstance.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ConstantReferencedViaInstance.java @@ -4,7 +4,7 @@ class Example extends Zuper{ } Example() { - super(new Inner().i); + super(new Inner().i); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/FlexibleConstructorBodies.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/FlexibleConstructorBodies.java new file mode 100644 index 000000000000..a152b11fa494 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/FlexibleConstructorBodies.java @@ -0,0 +1,183 @@ +import java.util.List; + +class A { + int i; + + A() { + i = 10; + (this).i = 11; + (A.this).i = 12; + this.i++; // Error + this.hashCode(); // Error + System.out.print(this); // Error + super(); + } + A(int i) {} +} + +class B extends A { + B() { + int i = 3; + super(); + } + B(int i) { + this(); + super(2); + } + B(char i) { + super(4); + this(); + } + + B(String s) { + try { + super(2); + } + finally { + } + } + B(String s, int i) { + { + super(2); + } + } + B(boolean b, int i) { + super.i = i; + this.i = i; + if (false) return; + super(i); + } + + void f() { + super(); + } + void g() { + this(); + } + +} +class D { + int i; +} + +class E extends D { + + E() { + super.i++; // Error + super(); + } + +} +class F { + + int i; + + F() { + i++; // Error + hashCode(); // Error + super(); + } + +} +class G { + + int b; + + class C { + + int c; + + C() { + G.this.b++; // Allowed - enclosing instance + C.this.c++; // Error - same instance + super(); + } + + } + +} +class Outer { + + void hello() { + System.out.println("Hello"); + } + + class Inner { + + Inner() { + hello(); // Allowed - enclosing instance method + super(); + } + + } + +} +class Outer2 { + + class Inner { + } + + Outer2() { + new Inner(); // Error - 'this' is enclosing instance + super(); + } + +} +class X { + + class S { + } + + X() { + var tmp = new S() { }; // Error + super(); + } + +} +class O { + + class S { + } + + class U { + + U() { + var tmp = new S() { }; // Allowed + super(); + } + + } + +} +class Y { + Y(Object o) { + if (o == null) throw new NullPointerException(); + super(); + } +} +class Z extends Y { + + Z() { + super(this); // Error - refers to 'this' + } + + Z(List list) { + super((T)list.get(0)); // Allowed - refers to 'T' but not 'this' + } + +} +record R(int x, int y) { + R(int x, int y, int z) { + if (z > 1000) throw new IllegalArgumentException(); + this(x, y); + } +} +enum EE { + A, B; + + EE() { + System.out.println(1); + this(1); + } + EE(int i) {} +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/IDEADEV11919.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/IDEADEV11919.java index 8181b03009c5..43fa1233f429 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/IDEADEV11919.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/IDEADEV11919.java @@ -13,7 +13,7 @@ class Inh extends Foo { public Inh() { super(new Bar() { public void DoBar() { - Inh.this.myField.toString(); + Inh.this.myField.toString(); } }); @@ -49,7 +49,7 @@ class Base { class Inhertior extends Base { public Inhertior() { - super("", field.length()); + super("", field.length()); } } //end of IDEADEV-14306 \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/QualifiedSuper.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/QualifiedSuper.java index 24c34449db3e..0c2724ea12b7 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/QualifiedSuper.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/QualifiedSuper.java @@ -15,17 +15,17 @@ class Outer { o.super(Outer.this); } public Inner2(Outer o, int par) { - o.super(this); + o.super(this); } public Inner2(Outer o, Object par) { - this.super(o); + this.super(o); } } class BadInner extends Inner1 { - BadInner() {} + BadInner() {} } - class BadInner2 extends Inner1 { + class BadInner2 extends Inner1 { } class s { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/QualifiedThisBeforeCtrCalled.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/QualifiedThisBeforeCtrCalled.java index 70076e208517..5f3eefbb9f1f 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/QualifiedThisBeforeCtrCalled.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/QualifiedThisBeforeCtrCalled.java @@ -9,7 +9,7 @@ class FirstLevel { ThirdLevel(int i) { super(new ThirdLevel(1) { public void a() { - ThirdLevel.this.hashCode(); + ThirdLevel.this.hashCode(); } }); } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ReferenceMemberBeforeCtrCalled.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ReferenceMemberBeforeCtrCalled.java index 1a8b82dfb15f..6bde8214f51f 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ReferenceMemberBeforeCtrCalled.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ReferenceMemberBeforeCtrCalled.java @@ -13,21 +13,21 @@ class A { class B extends A { int bi; B(int h) { - super(bi); + super(bi); } B() { - this(bi); + this(bi); } B(String s) { - super(db(1) ); + super(db(1) ); } B(int i, int j) { - super(f()); + super(f()); } B(int i, int j, int k) { - super(super.f()); + super(super.f()); } B(String s, int i) { @@ -35,11 +35,11 @@ class B extends A { } B(int s, int i, char j) { - super(super.fi ); + super(super.fi ); } B(double d) { - super(new Inner() ); + super(new Inner() ); } class Inner extends A { Inner(){ @@ -63,7 +63,7 @@ class Enc { } Bb(int i, int j) { - super(Enc.Bb.this.ibb ); + super(Enc.Bb.this.ibb ); } Bb(int i, String s) { @@ -71,12 +71,12 @@ class Enc { } Bb(int i, char j) { - super(this ); + super(this ); } } Enc() { - this(new Bb()); + this(new Bb()); } Enc(Bb b) {} } @@ -117,7 +117,7 @@ class YellinBug extends Base { super(new Callback() { public void call() { - YellinBug.this.f(); + YellinBug.this.f(); } }); } @@ -139,17 +139,17 @@ class Outer { class UseIt extends Inner{ Outer o; UseIt() { - o.super(); + o.super(); } Outer geto() { return null; } UseIt(int x) { - geto().super(); + geto().super(); } UseIt(Outer x) { - this.super(); + this.super(); } } } @@ -188,14 +188,14 @@ class InnerClassRefInsideAnonymous { Child(Foo foo) { super(new Foo() { public String toString() { - AFoo afoo = new AFoo(); + AFoo afoo = new AFoo(); return super.toString(); } }); } Child(String s, Foo foo) { - super(s, new AFoo()); + super(s, new AFoo()); } class AFoo extends Foo {} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/StatementsBeforeSuper.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/StatementsBeforeSuper.java index b29dd6248afe..cc487d3dee93 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/StatementsBeforeSuper.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/StatementsBeforeSuper.java @@ -4,9 +4,9 @@ class A { int i; A() { - this.i++; // Error - this.hashCode(); // Error - System.out.print(this); // Error + this.i++; // Error + this.hashCode(); // Error + System.out.print(this); // Error super(); } A(int i) {} @@ -58,7 +58,7 @@ class D { class E extends D { E() { - super.i++; // Error + super.i++; // Error super(); } @@ -68,8 +68,8 @@ class F { int i; F() { - i++; // Error - hashCode(); // Error + i++; // Error + hashCode(); // Error super(); } @@ -84,7 +84,7 @@ class G { C() { G.this.b++; // Allowed - enclosing instance - C.this.c++; // Error - same instance + C.this.c++; // Error - same instance super(); } @@ -113,7 +113,7 @@ class Outer2 { } Outer2() { - new Inner(); // Error - 'this' is enclosing instance + new Inner(); // Error - 'this' is enclosing instance super(); } @@ -124,7 +124,7 @@ class X { } X() { - var tmp = new S() { }; // Error + var tmp = new S() { }; // Error super(); } @@ -153,7 +153,7 @@ class Y { class Z extends Y { Z() { - super(this); // Error - refers to 'this' + super(this); // Error - refers to 'this' } Z(List list) { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ThisReferencedInnerClass.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ThisReferencedInnerClass.java index 98869fb1da67..0b2794a93ca5 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ThisReferencedInnerClass.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ThisReferencedInnerClass.java @@ -6,7 +6,7 @@ class Outer { class Inner extends Outer { Inner() { - super(hello()); + super(hello()); } } } 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 259bd31332b9..08b7b090d3e0 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 @@ -1,4 +1,4 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.java.codeInsight.daemon; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings; @@ -242,6 +242,7 @@ public class LightAdvHighlightingTest extends LightDaemonAnalyzerTestCase { public void testIgnoreImplicitThisReferenceBeforeSuperSinceJdk7() { doTest(false); } public void testStatementsBeforeSuper() { IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> doTest(false)); } + public void testFlexibleConstructorBodies() { IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_23_PREVIEW, () -> doTest(false)); } public void testCastFromVoid() { doTest(false); } public void testCatchUnknownMethod() { doTest(false); } public void testIDEADEV8822() { doTest(false); }