diff --git a/java/java-backend/resources/META-INF/Inspections.xml b/java/java-backend/resources/META-INF/Inspections.xml index b342b0003886..8b3a4509ca61 100644 --- a/java/java-backend/resources/META-INF/Inspections.xml +++ b/java/java-backend/resources/META-INF/Inspections.xml @@ -360,6 +360,11 @@ implementationClass="com.intellij.codeInspection.CapturingCleanerInspection" bundle="messages.JavaBundle" key="inspection.capturing.cleaner.description"/> + 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")); + } + } + }; + } +} diff --git a/java/java-impl/resources/inspectionDescriptions/IncorrectLazyConstantUsage.html b/java/java-impl/resources/inspectionDescriptions/IncorrectLazyConstantUsage.html new file mode 100644 index 000000000000..030363f065b5 --- /dev/null +++ b/java/java-impl/resources/inspectionDescriptions/IncorrectLazyConstantUsage.html @@ -0,0 +1,16 @@ + + +Reports fields of type java.lang.LazyConstant that are not final. +

+ Per JEP 526: Lazy Constants, lazy constants must be stored in a final field to enable constant-folding optimizations by the JVM. +

+

Correct usage:

+

+  private final LazyConstant<Logger> logger = LazyConstant.of(() -> Logger.create(OrderController.class));
+
+

Incorrect usage (will be reported):

+

+  private LazyConstant<String> x = LazyConstant.of(() -> "expensive string");
+
+ + diff --git a/java/java-tests/testData/inspection/incorrectLazyConstantUsage/IncorrectLazyConstantUsage.java b/java/java-tests/testData/inspection/incorrectLazyConstantUsage/IncorrectLazyConstantUsage.java new file mode 100644 index 000000000000..ce5e0011e733 --- /dev/null +++ b/java/java-tests/testData/inspection/incorrectLazyConstantUsage/IncorrectLazyConstantUsage.java @@ -0,0 +1,19 @@ +class Main { + static final LazyConstant a = LazyConstant.of(() -> "Hello"); + final LazyConstant b; + LazyConstant f = LazyConstant.of(() -> "Bye"); + + static void main() { + LazyConstant c = LazyConstant.of(() -> "Hello"); + } + + Main() { + b = LazyConstant.of(() -> "World"); + LazyConstant d = LazyConstant.of(() -> "Hello"); + LazyConstant e = returnLazyConstant(d); + } + + LazyConstant returnLazyConstant(LazyConstant lc) { + return LazyConstant.of(() -> lc.get()); + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/IncorrectLazyConstantUsageInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/IncorrectLazyConstantUsageInspectionTest.java new file mode 100644 index 000000000000..a8611305185f --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/IncorrectLazyConstantUsageInspectionTest.java @@ -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 { + public static LazyConstant of(Supplier 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"; + } +} diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties index 5ce1e309b7ed..97984a1c0703 100644 --- a/java/openapi/resources/messages/JavaBundle.properties +++ b/java/openapi/resources/messages/JavaBundle.properties @@ -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 diff --git a/java/testFramework/src/com/intellij/testFramework/fixtures/LightJavaCodeInsightFixtureTestCase.java b/java/testFramework/src/com/intellij/testFramework/fixtures/LightJavaCodeInsightFixtureTestCase.java index daa8cfc267a3..3352ec45a846 100644 --- a/java/testFramework/src/com/intellij/testFramework/fixtures/LightJavaCodeInsightFixtureTestCase.java +++ b/java/testFramework/src/com/intellij/testFramework/fixtures/LightJavaCodeInsightFixtureTestCase.java @@ -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); /**