IDEA-384866 create IncorrectLazyConstantUsageInspection

Merge-request: IJ-MR-191349
Merged-by: Bartek Pacia <bartek.pacia@jetbrains.com>

(cherry picked from commit 3b3bec86fc732114536a3331257a186d1665c05b)

IJ-CR-192061

GitOrigin-RevId: f326fff5ab5de159e7dc9b24d6b14b9d634a8568
This commit is contained in:
Bartek Pacia
2026-02-24 14:03:25 +00:00
committed by intellij-monorepo-bot
parent c2624ebece
commit 0fb3f3ebac
7 changed files with 130 additions and 0 deletions
@@ -360,6 +360,11 @@
implementationClass="com.intellij.codeInspection.CapturingCleanerInspection"
bundle="messages.JavaBundle"
key="inspection.capturing.cleaner.description"/>
<localInspection groupPath="Java" language="JAVA" shortName="IncorrectLazyConstantUsage"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.performance.issues" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.IncorrectLazyConstantUsageInspection"
key="inspection.incorrect.lazy.constant.usage.display.name" bundle="messages.JavaBundle"/>
<localInspection groupPath="Java" language="JAVA" shortName="OverwrittenKey"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.probable.bugs" enabledByDefault="true" level="WARNING"
@@ -0,0 +1,38 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection;
import com.intellij.java.JavaBundle;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
public final class IncorrectLazyConstantUsageInspection extends AbstractBaseJavaLocalInspectionTool {
@Override
public @NotNull Set<@NotNull JavaFeature> requiredFeatures() {
return Set.of(JavaFeature.LAZY_CONSTANTS);
}
@Override
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitField(@NotNull PsiField field) {
if (field.hasModifierProperty(PsiModifier.FINAL)) return;
PsiType type = field.getType();
PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(type);
if (aClass != null && "java.lang.LazyConstant".equals(aClass.getQualifiedName())) {
holder.registerProblem(field.getNameIdentifier(), JavaBundle.message("inspection.incorrect.lazy.constant.usage.message"));
}
}
};
}
}
@@ -0,0 +1,16 @@
<html>
<body>
Reports fields of type <code>java.lang.LazyConstant</code> that are not <code>final</code>.
<p>
Per <a href="https://openjdk.org/jeps/526">JEP 526: Lazy Constants</a>, lazy constants must be stored in a <code>final</code> field to enable constant-folding optimizations by the JVM.
</p>
<p>Correct usage:</p>
<pre><code>
private final LazyConstant&lt;Logger&gt; logger = LazyConstant.of(() -&gt; Logger.create(OrderController.class));
</code></pre>
<p>Incorrect usage (will be reported):</p>
<pre><code>
private LazyConstant&lt;String&gt; x = LazyConstant.of(() -&gt; "expensive string");
</code></pre>
</body>
</html>
@@ -0,0 +1,19 @@
class Main {
static final LazyConstant<String> a = LazyConstant.of(() -> "Hello");
final LazyConstant<String> b;
LazyConstant<String> <warning descr="'LazyConstant' field should be 'final'">f</warning> = LazyConstant.of(() -> "Bye");
static void main() {
LazyConstant<String> c = LazyConstant.of(() -> "Hello");
}
Main() {
b = LazyConstant.of(() -> "World");
LazyConstant<String> d = LazyConstant.of(() -> "Hello");
LazyConstant<String> e = returnLazyConstant(d);
}
LazyConstant<String> returnLazyConstant(LazyConstant<String> lc) {
return LazyConstant.of(() -> lc.<error descr="'get()' is not public in 'java.lang.LazyConstant'. Cannot be accessed from outside package">get</error>());
}
}
@@ -0,0 +1,49 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeInspection;
import com.intellij.JavaTestUtil;
import com.intellij.codeInspection.IncorrectLazyConstantUsageInspection;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
public class IncorrectLazyConstantUsageInspectionTest extends LightJavaCodeInsightFixtureTestCase {
public void testIncorrectLazyConstantUsage() {
doTest();
}
@Override
protected void setUp() throws Exception {
super.setUp();
myFixture.addClass("""
package java.lang;
import java.util.function.Supplier;
public final class LazyConstant<V> {
public static <V> LazyConstant<V> of(Supplier<? extends V> supplier) {
return null;
}
T get();
}
""");
myFixture.enableInspections(new IncorrectLazyConstantUsageInspection());
}
private void doTest() {
myFixture.testHighlighting(true, false, false, getTestName(false) + ".java");
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_26;
}
@NotNull
@Override
protected String getTestDataPath() {
return JavaTestUtil.getJavaTestDataPath() + "/inspection/incorrectLazyConstantUsage";
}
}
@@ -452,6 +452,8 @@ inspection.handle.signature.use.constructor.fix.name=Use constructor ''{0}''
inspection.handle.signature.use.method.fix.family.name=Use one of method overloads
inspection.handle.signature.use.method.fix.name=Use method ''{0}''
inspection.idempotent.loop.body=Idempotent loop body
inspection.incorrect.lazy.constant.usage.display.name=LazyConstant should be used only in final fields
inspection.incorrect.lazy.constant.usage.message='LazyConstant' field should be 'final'
inspection.illegal.character=Illegal character
inspection.suspicious.ternary.in.varargs.display.name=Suspicious ternary operator in varargs method call
inspection.suspicious.ternary.in.varargs.description=Ternary operator in varargs call contains array and non-array branches
@@ -146,6 +146,7 @@ public abstract class LightJavaCodeInsightFixtureTestCase extends UsefulTestCase
public static final @NotNull LightProjectDescriptor JAVA_23 = new ProjectDescriptor(LanguageLevel.JDK_23_PREVIEW);
public static final @NotNull LightProjectDescriptor JAVA_24 = new ProjectDescriptor(LanguageLevel.JDK_24);
public static final @NotNull LightProjectDescriptor JAVA_25 = new ProjectDescriptor(LanguageLevel.JDK_25);
public static final @NotNull LightProjectDescriptor JAVA_26 = new ProjectDescriptor(LanguageLevel.JDK_26_PREVIEW);
public static final @NotNull LightProjectDescriptor JAVA_X = new ProjectDescriptor(LanguageLevel.JDK_X);
/**