From 38729705c87548713e3cad3fa8d835aabaf11932 Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Mon, 6 Oct 2025 10:53:33 +0200 Subject: [PATCH] Java: don't report uninitialized fields when constructor has incorrect name (IDEA-380080) GitOrigin-RevId: c83193e8b72f9730e1102220f7a12c35e2722223 --- .../JavaCompilationErrorBundle.properties | 3 +- .../highlighting/MethodChecker.java | 11 +++++-- .../highlighting/errors/JavaErrorKinds.java | 5 ++- .../analysis/DefaultJavaErrorFixProvider.java | 9 ++++-- .../psi/controlFlow/ControlFlowUtil.java | 22 +++++++++++-- .../com/intellij/psi/impl/PsiImplUtil.java | 5 +-- .../advHighlighting/FinalFieldInit.java | 31 +++++++++++++++++++ .../advHighlighting/InvalidExpressions.java | 4 +-- .../ReturnFromConstructor.java | 2 +- ...edFunctionalInterfaceMethodReturnType.java | 4 +-- .../IncompleteMethodInInterface.java | 2 +- .../beforeInterface.java | 4 +++ ...dClassPrimaryConstructorUsages.results.txt | 1 + ...ibraryPrimaryConstructorUsages.results.txt | 1 + 14 files changed, 86 insertions(+), 18 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/renameConstructorMatchingClass/beforeInterface.java diff --git a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties index 310a23a28dfd..fe43a4b69a5b 100644 --- a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties +++ b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties @@ -224,7 +224,8 @@ method.inheritance.clash.unrelated.return.types={0}; methods have unrelated retu method.inheritance.clash.incompatible.return.types={0}; incompatible return type method.inheritance.clash.does.not.throw={0}; overridden method does not throw ''{1}'' method.no.parameter.list=Parameter list expected -method.missing.return.type=Invalid method declaration; return type required +method.missing.return.type.not.constructor=Method return type missing +method.missing.return.type=Method return type missing or constructor name does not match class name method.generic.same.erasure={0}; both methods have same erasure method.generic.same.erasure.override={0}; both methods have same erasure, yet neither overrides the other method.generic.same.erasure.hide={0}; both methods have same erasure, yet neither hides the other diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/MethodChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/MethodChecker.java index 55fee98293dc..0f36e894c552 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/MethodChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/MethodChecker.java @@ -7,7 +7,6 @@ import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; import com.intellij.java.codeserver.core.JavaPsiMethodUtil; import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError; import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds; -import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Couple; import com.intellij.openapi.util.TextRange; import com.intellij.pom.java.JavaFeature; @@ -415,8 +414,14 @@ final class MethodChecker { void checkConstructorName(PsiMethod method) { PsiClass aClass = method.getContainingClass(); if (aClass != null) { - String className = aClass instanceof PsiAnonymousClass ? null : aClass.getName(); - if (className == null || !Comparing.strEqual(method.getName(), className)) { + String className = aClass.getName(); + if (method.getName().equals(className)) { + return; + } + if (aClass.isInterface() || aClass instanceof PsiAnonymousClass || className == null) { + myVisitor.report(JavaErrorKinds.METHOD_MISSING_RETURN_TYPE_NOT_CONSTRUCTOR.create(method)); + } + else { myVisitor.report(JavaErrorKinds.METHOD_MISSING_RETURN_TYPE.create(method, className)); } } 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 04b3329dff57..865a5d8c6cee 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 @@ -765,9 +765,12 @@ public final class JavaErrorKinds { .withDescription((cls, ctx) -> message("method.inheritance.clash.does.not.throw", formatClashMethodMessage(ctx.method(), ctx.superMethod()), formatType(ctx.exceptionType()))); - public static final Parameterized METHOD_MISSING_RETURN_TYPE = + public static final Parameterized METHOD_MISSING_RETURN_TYPE = parameterized(PsiMethod.class, String.class, "method.missing.return.type") .withAnchor(method -> requireNonNullElse(method.getNameIdentifier(), method)); + public static final Simple METHOD_MISSING_RETURN_TYPE_NOT_CONSTRUCTOR = + error(PsiMethod.class, "method.missing.return.type.not.constructor") + .withAnchor(method -> requireNonNullElse(method.getNameIdentifier(), method)); public static final Parameterized CONSTRUCTOR_AMBIGUOUS_IMPLICIT_CALL = parameterized(PsiMember.class, AmbiguousImplicitConstructorCallContext.class, "constructor.ambiguous.implicit.call") 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 0f8390695c42..b039e41fe85e 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 @@ -318,12 +318,15 @@ public final class DefaultJavaErrorFixProvider extends AbstractJavaErrorFixProvi } }); fix(VARARG_CSTYLE_DECLARATION, error -> new NormalizeBracketsFix(error.psi())); + fix(METHOD_MISSING_RETURN_TYPE_NOT_CONSTRUCTOR, error -> { + PsiMethod method = error.psi(); + PsiType expectedType = HighlightFixUtil.determineReturnType(method); + return expectedType != null ? myFactory.createMethodReturnFix(method, expectedType, true, true) : null; + }); fixes(METHOD_MISSING_RETURN_TYPE, (error, sink) -> { String className = error.context(); PsiMethod method = error.psi(); - if (className != null) { - sink.accept(myFactory.createRenameElementFix(method, className)); - } + sink.accept(myFactory.createRenameElementFix(method, className)); PsiType expectedType = HighlightFixUtil.determineReturnType(method); if (expectedType != null) { sink.accept(myFactory.createMethodReturnFix(method, expectedType, true, true)); diff --git a/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java b/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java index 1f8ebb960138..6e81f382d6e4 100644 --- a/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java +++ b/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java @@ -145,6 +145,9 @@ public final class ControlFlowUtil { PsiMethod[] constructors = aClass.getConstructors(); if (constructors.length == 0) return false; + boolean initializedInCorrectlyNamedConstructor = false; + boolean initializedInBadlyNamedConstructor = false; + boolean uninitializedInBadlyNamedConstructor = false; nextConstructor: for (PsiMethod constructor : constructors) { PsiCodeBlock ctrBody = constructor.getBody(); @@ -154,11 +157,26 @@ public final class ControlFlowUtil { if (body != null && variableDefinitelyAssignedIn(field, body, true)) continue nextConstructor; } if (!ctrBody.isValid() || variableDefinitelyAssignedIn(field, ctrBody, true)) { + if (constructor.getName().equals(aClass.getName())) { + initializedInCorrectlyNamedConstructor = true; + } + else { + initializedInBadlyNamedConstructor = true; + } continue; } - return false; + if (constructor.getName().equals(aClass.getName())) { + // always report error when constructor with correct name doesn't assign + return false; + } + else { + uninitializedInBadlyNamedConstructor = true; + } } - return true; + // don't report initialization error when all correctly named constructors assign -> badly named constructor is probably method without type declared + // don't report initialization error when no correctly named constructor present and all badly named constructors assign -> class name edited manually? + // report initialization error when not all badly named constructors assign + return initializedInCorrectlyNamedConstructor || (initializedInBadlyNamedConstructor && !uninitializedInBadlyNamedConstructor); } /** diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java b/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java index c220fde2e949..ff14b82c5b6b 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java @@ -1,4 +1,4 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.psi.impl; import com.intellij.codeInsight.AnnotationTargetUtil; @@ -52,9 +52,10 @@ public final class PsiImplUtil { private PsiImplUtil() { } public static PsiMethod @NotNull [] getConstructors(@NotNull PsiClass aClass) { + if (aClass instanceof PsiAnonymousClass) return PsiMethod.EMPTY_ARRAY; List result = null; for (PsiMethod method : aClass.getMethods()) { - if (method.isConstructor() && method.getName().equals(aClass.getName())) { + if (method.isConstructor()) { if (result == null) result = new SmartList<>(); result.add(method); } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/FinalFieldInit.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/FinalFieldInit.java index ba51b93cc568..222b96218ca4 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/FinalFieldInit.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/FinalFieldInit.java @@ -392,4 +392,35 @@ class Key { private static final int COUNT; private static final int count = COUNT++; +} +// don't report initialization error when all correctly named constructors assign -> badly named constructor is probably method without type declared +// don't report initialization error when no correctly named constructor present and all badly named constructors assign -> class name edited manually? +// report initialization error when not all badly named constructors assign +class Definitely { + // intentionally no "might not have been initialized" error reporting here, because probably only the constructor name is incorrect + private final int x; + + Unquestionably() { + x = 1; + } + + class One { + // intentionally no error reporting here, because probably only the type is missing from the method + private final int x; + One() { + x = 1; + } + run () { + + } + } + class Two { + private final int x; + TwoPrime() { + x = 1; + } + TwoPrime(int i) { + // no assgnment to x + } + } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/InvalidExpressions.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/InvalidExpressions.java index 743b3682163c..29d6a67d2182 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/InvalidExpressions.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/InvalidExpressions.java @@ -103,12 +103,12 @@ public class a12 { int[] arr() { return new int[0]; } - public foo() { + public foo() { } { new Object() { - Object() {} + Object() {} }; } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ReturnFromConstructor.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ReturnFromConstructor.java index 7e8188fcb654..43f1496f0975 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ReturnFromConstructor.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/ReturnFromConstructor.java @@ -4,7 +4,7 @@ class C { return 1; } - x() { + x() { return 1; } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/incomplete/MissedFunctionalInterfaceMethodReturnType.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/incomplete/MissedFunctionalInterfaceMethodReturnType.java index 574268764010..4ae6db5eda77 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/incomplete/MissedFunctionalInterfaceMethodReturnType.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/incomplete/MissedFunctionalInterfaceMethodReturnType.java @@ -9,7 +9,7 @@ class Test { static abstract class D implements A, B {} interface I { - m(T arg); + m(T arg); } void bar(C c) { @@ -24,7 +24,7 @@ class Test { class Test2 { interface F { - m(); + m(); } void g() {} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/mostSpecific/IncompleteMethodInInterface.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/mostSpecific/IncompleteMethodInInterface.java index 8d71a82cc6b4..5689a83818e6 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/mostSpecific/IncompleteMethodInInterface.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/mostSpecific/IncompleteMethodInInterface.java @@ -1,6 +1,6 @@ class Test { interface I { Object in - voke(); } + voke(); } interface IStr { String foo(); } public static void call(IStr str) {} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/renameConstructorMatchingClass/beforeInterface.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/renameConstructorMatchingClass/beforeInterface.java new file mode 100644 index 000000000000..dd6206eb8ded --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/renameConstructorMatchingClass/beforeInterface.java @@ -0,0 +1,4 @@ +// "Rename 'Foo' to 'Bar'" "false" +interface Ludicrous { + Foo() {} +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/findUsages/libraryUsages/kotlinLibrary/LibraryNestedClassPrimaryConstructorUsages.results.txt b/plugins/kotlin/idea/tests/testData/findUsages/libraryUsages/kotlinLibrary/LibraryNestedClassPrimaryConstructorUsages.results.txt index 87cdbae8bdf6..ffdde707b062 100644 --- a/plugins/kotlin/idea/tests/testData/findUsages/libraryUsages/kotlinLibrary/LibraryNestedClassPrimaryConstructorUsages.results.txt +++ b/plugins/kotlin/idea/tests/testData/findUsages/libraryUsages/kotlinLibrary/LibraryNestedClassPrimaryConstructorUsages.results.txt @@ -2,6 +2,7 @@ [LibraryNestedClassPrimaryConstructorUsages.0.kt] New instance creation 19 val aa = A.T(1) [LibraryNestedClassPrimaryConstructorUsages.0.kt] Supertype 15 class Y() : A.T(1) [LibraryNestedClassPrimaryConstructorUsages.1.java] New instance creation 12 A.T aa = new A.T(1); +[LibraryNestedClassPrimaryConstructorUsages.1.java] Unclassified 7 super(n); [library.kt] Constructor delegation reference 7 constructor() : this(1) [library.kt] New instance creation 63 val t = A.T(1) [library.kt] Supertype 28 class V() : A.T(1) diff --git a/plugins/kotlin/idea/tests/testData/findUsages/libraryUsages/kotlinLibrary/LibraryPrimaryConstructorUsages.results.txt b/plugins/kotlin/idea/tests/testData/findUsages/libraryUsages/kotlinLibrary/LibraryPrimaryConstructorUsages.results.txt index dd706f7f3040..410568f00e8f 100644 --- a/plugins/kotlin/idea/tests/testData/findUsages/libraryUsages/kotlinLibrary/LibraryPrimaryConstructorUsages.results.txt +++ b/plugins/kotlin/idea/tests/testData/findUsages/libraryUsages/kotlinLibrary/LibraryPrimaryConstructorUsages.results.txt @@ -2,6 +2,7 @@ [LibraryPrimaryConstructorUsages.0.kt] New instance creation 19 val aa = A(1) [LibraryPrimaryConstructorUsages.0.kt] Supertype 15 class Y() : A(1) [LibraryPrimaryConstructorUsages.1.java] New instance creation 12 A aa = new A(1); +[LibraryPrimaryConstructorUsages.1.java] Unclassified 7 super(n); [library.kt] Constructor delegation reference 4 constructor() : this(1) [library.kt] New instance creation 57 val a = A(1) [library.kt] Supertype 27 class C() : A(1) {