[java-highlighting] checkVariableMustBeFinal migrated

Also: intermediate refactoring inside HighlightControlFlowUtil related to init-before-use
Also: ErrorFixExtensionPoint now works for any kind (intermediate solution)
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: 7a712a841a52a99bac4faafaf1164921c1150a56
This commit is contained in:
Tagir Valeev
2025-02-05 12:41:14 +00:00
committed by intellij-monorepo-bot
parent 32eeb4ee9f
commit 913cade2a5
20 changed files with 176 additions and 195 deletions
@@ -374,6 +374,11 @@ pattern.cannot.infer.type=Cannot infer pattern type: {0}
pattern.instanceof.supertype=Pattern type ''{0}'' is a supertype of expression type ''{1}''
pattern.instanceof.equals=Pattern type ''{0}'' is the same as expression type
variable.must.be.final=Variable ''{0}'' is accessed from within inner class, needs to be declared final
variable.must.be.effectively.final=Variable ''{0}'' is accessed from within inner class, needs to be final or effectively final
variable.must.be.effectively.final.lambda=Variable used in lambda expression should be final or effectively final
variable.must.be.effectively.final.guard=Variable used in guard expression should be final or effectively final
instanceof.type.parameter=Class or array expected
instanceof.illegal.generic.type=Illegal generic type for instanceof
instanceof.unsafe.cast=''{0}'' cannot be safely cast to ''{1}''
@@ -3,6 +3,7 @@ package com.intellij.java.codeserver.highlighting;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.augment.PsiAugmentProvider;
import com.intellij.psi.controlFlow.*;
@@ -94,6 +95,34 @@ final class ControlFlowChecker {
}
}
void checkVariableMustBeFinal(@NotNull PsiVariable variable, @NotNull PsiJavaCodeReferenceElement context) {
if (variable.hasModifierProperty(PsiModifier.FINAL)) return;
PsiElement scope = ControlFlowUtil.getScopeEnforcingEffectiveFinality(variable, context);
if (scope == null) return;
if (scope instanceof PsiClass) {
if (variable instanceof PsiParameter parameter) {
PsiElement parent = variable.getParent();
if (parent instanceof PsiParameterList && parent.getParent() instanceof PsiLambdaExpression &&
ControlFlowUtil.isEffectivelyFinal(variable, parameter.getDeclarationScope())) {
return;
}
}
boolean isToBeEffectivelyFinal = myVisitor.isApplicable(JavaFeature.EFFECTIVELY_FINAL);
if (isToBeEffectivelyFinal && ControlFlowUtil.isEffectivelyFinal(variable, scope, context)) return;
var kind = isToBeEffectivelyFinal ? JavaErrorKinds.VARIABLE_MUST_BE_EFFECTIVELY_FINAL : JavaErrorKinds.VARIABLE_MUST_BE_FINAL;
myVisitor.report(kind.create(context, variable));
return;
} else if (scope instanceof PsiLambdaExpression) {
if (ControlFlowUtil.isEffectivelyFinal(variable, scope, context)) return;
myVisitor.report(JavaErrorKinds.VARIABLE_MUST_BE_EFFECTIVELY_FINAL_LAMBDA.create(context, variable));
} else if (scope instanceof PsiSwitchLabelStatementBase) {
// Reported separately in ExpressionChecker.checkOutsideDeclaredCantBeAssignmentInGuard
if (context instanceof PsiReferenceExpression ref && PsiUtil.isAccessedForWriting(ref)) return;
if (ControlFlowUtil.isEffectivelyFinal(variable, scope, context)) return;
myVisitor.report(JavaErrorKinds.VARIABLE_MUST_BE_EFFECTIVELY_FINAL_GUARD.create(context, variable));
}
}
/**
* @return field that has initializer with this element as subexpression or null if not found
*/
@@ -28,7 +28,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import static java.util.Objects.*;
import static java.util.Objects.requireNonNullElse;
final class ExpressionChecker {
private final @NotNull JavaErrorVisitor myVisitor;
@@ -919,6 +919,9 @@ final class ExpressionChecker {
!PsiUtil.isFromDefaultPackage(myVisitor.file()))) {
myVisitor.report(JavaErrorKinds.REFERENCE_CLASS_IN_DEFAULT_PACKAGE.create(ref, psiClass));
}
if ((resolved instanceof PsiLocalVariable || resolved instanceof PsiParameter) && !(resolved instanceof ImplicitVariable)) {
myVisitor.myControlFlowChecker.checkVariableMustBeFinal((PsiVariable)resolved, ref);
}
}
private static boolean favorParentReport(@NotNull PsiCall methodCall, @NotNull String errorMessage) {
@@ -53,7 +53,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
final @NotNull TypeChecker myTypeChecker = new TypeChecker(this);
final @NotNull MethodChecker myMethodChecker = new MethodChecker(this);
private final @NotNull ReceiverChecker myReceiverChecker = new ReceiverChecker(this);
private final @NotNull ControlFlowChecker myControlFlowChecker = new ControlFlowChecker(this);
final @NotNull ControlFlowChecker myControlFlowChecker = new ControlFlowChecker(this);
private final @NotNull FunctionChecker myFunctionChecker = new FunctionChecker(this);
final @NotNull PatternChecker myPatternChecker = new PatternChecker(this);
final @NotNull ModifierChecker myModifierChecker = new ModifierChecker(this);
@@ -1235,6 +1235,17 @@ public final class JavaErrorKinds {
public static final Parameterized<PsiReturnStatement, PsiMethodCallExpression> RETURN_BEFORE_EXPLICIT_CONSTRUCTOR_CALL =
parameterized(PsiReturnStatement.class, PsiMethodCallExpression.class, "return.before.explicit.constructor.call")
.withRawDescription((psi, call) -> message("return.before.explicit.constructor.call", call.getMethodExpression().getText() + "()"));
public static final Parameterized<PsiJavaCodeReferenceElement, PsiVariable> VARIABLE_MUST_BE_FINAL =
parameterized(PsiJavaCodeReferenceElement.class, PsiVariable.class, "variable.must.be.final")
.withRawDescription((ref, var) -> message("variable.must.be.final", var.getName()));
public static final Parameterized<PsiJavaCodeReferenceElement, PsiVariable> VARIABLE_MUST_BE_EFFECTIVELY_FINAL =
parameterized(PsiJavaCodeReferenceElement.class, PsiVariable.class, "variable.must.be.effectively.final")
.withRawDescription((ref, var) -> message("variable.must.be.effectively.final", var.getName()));
public static final Parameterized<PsiJavaCodeReferenceElement, PsiVariable> VARIABLE_MUST_BE_EFFECTIVELY_FINAL_LAMBDA =
parameterized(PsiJavaCodeReferenceElement.class, PsiVariable.class, "variable.must.be.effectively.final.lambda");
public static final Parameterized<PsiJavaCodeReferenceElement, PsiVariable> VARIABLE_MUST_BE_EFFECTIVELY_FINAL_GUARD =
parameterized(PsiJavaCodeReferenceElement.class, PsiVariable.class, "variable.must.be.effectively.final.guard");
private static @NotNull <Psi extends PsiElement> Simple<Psi> error(
@NotNull @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String key) {
@@ -5,6 +5,7 @@ import com.intellij.codeInsight.daemon.JavaErrorBundle;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.intention.CommonIntentionAction;
import com.intellij.diagnostic.PluginException;
import com.intellij.java.codeserver.highlighting.JavaCompilationErrorBundle;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.PluginAware;
@@ -27,6 +28,7 @@ public final class ErrorFixExtensionPoint implements PluginAware {
new ExtensionPointName<>("com.intellij.java.error.fix");
@Attribute("errorCode")
@PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE)
public String errorCode;
@Attribute("implementationClass")
@@ -14,8 +14,6 @@ import com.intellij.lang.jvm.actions.MemberRequestsKt;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.util.Predicates;
import com.intellij.openapi.util.TextRange;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.augment.PsiAugmentProvider;
import com.intellij.psi.controlFlow.*;
@@ -26,10 +24,8 @@ import com.intellij.psi.util.*;
import com.intellij.util.JavaPsiConstructorUtil;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.PropertyKey;
import java.util.*;
import java.util.function.Predicate;
@@ -217,78 +213,65 @@ public final class HighlightControlFlowUtil {
}
public static HighlightInfo.Builder checkVariableInitializedBeforeUsage(@NotNull PsiReferenceExpression expression,
@NotNull PsiVariable variable,
@NotNull Map<? super PsiElement, Collection<PsiReferenceExpression>> uninitializedVarProblems,
@NotNull PsiFile containingFile) {
return checkVariableInitializedBeforeUsage(expression, variable, uninitializedVarProblems, containingFile, false);
static HighlightInfo.Builder checkVariableInitializedBeforeUsage(@NotNull PsiReferenceExpression expression,
@NotNull PsiVariable variable,
@NotNull Map<? super PsiElement, Collection<PsiReferenceExpression>> uninitializedVarProblems) {
if (isInitializedBeforeUsage(expression, variable, uninitializedVarProblems, false)) return null;
return createNotInitializedError(expression, variable);
}
public static HighlightInfo.Builder checkVariableInitializedBeforeUsage(@NotNull PsiReferenceExpression expression,
@NotNull PsiVariable variable,
@NotNull Map<? super PsiElement, Collection<PsiReferenceExpression>> uninitializedVarProblems,
@NotNull PsiFile containingFile,
boolean ignoreFinality) {
if (variable instanceof ImplicitVariable) return null;
if (!PsiUtil.isAccessedForReading(expression)) return null;
public static boolean isInitializedBeforeUsage(@NotNull PsiReferenceExpression expression,
@NotNull PsiVariable variable,
@NotNull Map<? super PsiElement, Collection<PsiReferenceExpression>> uninitializedVarProblems,
boolean ignoreFinality) {
if (variable instanceof ImplicitVariable) return true;
if (!PsiUtil.isAccessedForReading(expression)) return true;
int startOffset = expression.getTextRange().getStartOffset();
PsiElement topBlock;
if (variable.hasInitializer()) {
topBlock = PsiUtil.getVariableCodeBlock(variable, variable);
if (topBlock == null) return null;
}
else {
PsiElement scope = variable instanceof PsiField field
? field.getContainingClass()
: variable.getParent() != null ? variable.getParent().getParent() : null;
while (scope instanceof PsiCodeBlock && scope.getParent() instanceof PsiSwitchBlock) {
scope = PsiTreeUtil.getParentOfType(scope, PsiCodeBlock.class);
}
topBlock = FileTypeUtils.isInServerPageFile(scope) && scope instanceof PsiFile ? scope : PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope);
PsiElement topBlock = getTopBlock(expression, variable);
if (topBlock == null) return true;
if (!variable.hasInitializer()) {
if (variable instanceof PsiField field) {
// non-final field already initialized with default value
if (!ignoreFinality && !variable.hasModifierProperty(PsiModifier.FINAL)) return null;
if (!ignoreFinality && !variable.hasModifierProperty(PsiModifier.FINAL)) return true;
// a final field may be initialized in ctor or class initializer only
// if we're inside non-ctr method, skip it
if (PsiUtil.findEnclosingConstructorOrInitializer(expression) == null
&& findEnclosingFieldInitializer(expression) == null) {
return null;
return true;
}
if (topBlock == null) return null;
PsiElement parent = topBlock.getParent();
// access to final fields from inner classes always allowed
if (inInnerClass(expression, field.getContainingClass())) return null;
if (inInnerClass(expression, field.getContainingClass())) return true;
PsiCodeBlock block;
PsiClass aClass;
if (parent instanceof PsiMethod constructor) {
if (!containingFile.getManager().areElementsEquivalent(constructor.getContainingClass(), field.getContainingClass())) return null;
if (!constructor.getManager().areElementsEquivalent(constructor.getContainingClass(), field.getContainingClass())) return true;
// static variables already initialized in class initializers
if (variable.hasModifierProperty(PsiModifier.STATIC)) return null;
if (variable.hasModifierProperty(PsiModifier.STATIC)) return true;
// as a last chance, field may be initialized in this() call
for (PsiMethod redirectedConstructor : JavaPsiConstructorUtil.getChainedConstructors(constructor)) {
// variable must be initialized before its usage
//???
//if (startOffset < redirectedConstructor.getTextRange().getStartOffset()) continue;
if (JavaPsiRecordUtil.isCompactConstructor(redirectedConstructor)) return null;
if (JavaPsiRecordUtil.isCompactConstructor(redirectedConstructor)) return true;
PsiCodeBlock body = redirectedConstructor.getBody();
if (body != null && variableDefinitelyAssignedIn(variable, body, true)) {
return null;
return true;
}
}
block = constructor.getBody();
aClass = constructor.getContainingClass();
}
else if (parent instanceof PsiClassInitializer classInitializer) {
if (!containingFile.getManager().areElementsEquivalent(classInitializer.getContainingClass(), field.getContainingClass())) {
return null;
if (!classInitializer.getManager().areElementsEquivalent(classInitializer.getContainingClass(), field.getContainingClass())) {
return true;
}
block = classInitializer.getBody();
aClass = classInitializer.getContainingClass();
if (aClass == null || isFieldInitializedInOtherFieldInitializer(aClass, field, variable.hasModifierProperty(PsiModifier.STATIC),
f -> startOffset > f.getTextOffset())) {
return null;
return true;
}
}
else {
@@ -299,16 +282,16 @@ public final class HighlightControlFlowUtil {
if (aClass == null ||
isFieldInitializedInOtherFieldInitializer(aClass, field, field.hasModifierProperty(PsiModifier.STATIC),
f -> f != anotherField && startOffset > f.getTextOffset())) {
return null;
return true;
}
if (anotherField != null
&& !anotherField.hasModifierProperty(PsiModifier.STATIC)
&& field.hasModifierProperty(PsiModifier.STATIC)
&& isFieldInitializedInClassInitializer(field, true, aClass.getInitializers())) {
return null;
return true;
}
if (anotherField != null && anotherField.hasInitializer() && !PsiAugmentProvider.canTrustFieldInitializer(anotherField)) {
return null;
return true;
}
int offset = startOffset;
@@ -322,7 +305,7 @@ public final class HighlightControlFlowUtil {
if (offset < constructor.getTextRange().getStartOffset()) continue;
PsiCodeBlock body = constructor.getBody();
if (body != null && variableDefinitelyAssignedIn(variable, body)) {
return null;
return true;
}
// as a last chance, field may be initialized in this() call
for (PsiMethod redirectedConstructor : JavaPsiConstructorUtil.getChainedConstructors(constructor)) {
@@ -330,7 +313,7 @@ public final class HighlightControlFlowUtil {
if (offset < redirectedConstructor.getTextRange().getStartOffset()) continue;
PsiCodeBlock redirectedBody = redirectedConstructor.getBody();
if (redirectedBody != null && variableDefinitelyAssignedIn(variable, redirectedBody)) {
return null;
return true;
}
}
}
@@ -346,13 +329,12 @@ public final class HighlightControlFlowUtil {
boolean shouldCheckInitializerOrder = block == null || block.getParent() instanceof PsiClassInitializer;
if (shouldCheckInitializerOrder && startOffset < initializer.getTextRange().getStartOffset()) continue;
if (initializer.hasModifierProperty(PsiModifier.STATIC) == variable.hasModifierProperty(PsiModifier.STATIC)) {
if (variableDefinitelyAssignedIn(variable, body)) return null;
if (variableDefinitelyAssignedIn(variable, body)) return true;
}
}
}
}
}
if (topBlock == null) return null;
Collection<PsiReferenceExpression> codeBlockProblems = uninitializedVarProblems.get(topBlock);
if (codeBlockProblems == null) {
try {
@@ -364,29 +346,54 @@ public final class HighlightControlFlowUtil {
}
uninitializedVarProblems.put(topBlock, codeBlockProblems);
}
if (codeBlockProblems.contains(expression)) {
String name = expression.getElement().getText();
String description = JavaErrorBundle.message("variable.not.initialized", name);
HighlightInfo.Builder builder =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description);
if (!(variable instanceof LightRecordField)) {
IntentionAction action1 = getQuickFixFactory().createAddVariableInitializerFix(variable);
builder.registerFix(action1, null, null, null, null);
return !codeBlockProblems.contains(expression);
}
private static @Nullable PsiElement getTopBlock(@NotNull PsiReferenceExpression expression, @NotNull PsiVariable variable) {
PsiElement topBlock;
if (variable.hasInitializer()) {
topBlock = PsiUtil.getVariableCodeBlock(variable, variable);
if (topBlock == null) return null;
}
else {
PsiElement scope = variable instanceof PsiField field
? field.getContainingClass()
: variable.getParent() != null ? variable.getParent().getParent() : null;
while (scope instanceof PsiCodeBlock && scope.getParent() instanceof PsiSwitchBlock) {
scope = PsiTreeUtil.getParentOfType(scope, PsiCodeBlock.class);
}
if (variable instanceof PsiLocalVariable) {
topBlock = FileTypeUtils.isInServerPageFile(scope) && scope instanceof PsiFile
? scope
: PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope);
}
return topBlock;
}
private static HighlightInfo.@NotNull Builder createNotInitializedError(@NotNull PsiReferenceExpression expression,
@NotNull PsiVariable variable) {
String name = expression.getElement().getText();
String description = JavaErrorBundle.message("variable.not.initialized", name);
HighlightInfo.Builder builder =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description);
if (!(variable instanceof LightRecordField)) {
IntentionAction action1 = getQuickFixFactory().createAddVariableInitializerFix(variable);
builder.registerFix(action1, null, null, null, null);
}
if (variable instanceof PsiLocalVariable) {
PsiElement topBlock = getTopBlock(expression, variable);
if (topBlock != null) {
IntentionAction action = HighlightFixUtil.createInsertSwitchDefaultFix(variable, topBlock, expression);
if (action != null) {
builder.registerFix(action, null, null, null, null);
}
}
if (variable instanceof PsiField field) {
ChangeModifierRequest request = MemberRequestsKt.modifierRequest(JvmModifier.FINAL, false);
QuickFixAction.registerQuickFixActions(builder, null, JvmElementActionFactories.createModifierActions(field, request));
}
return builder;
}
return null;
if (variable instanceof PsiField field) {
ChangeModifierRequest request = MemberRequestsKt.modifierRequest(JvmModifier.FINAL, false);
QuickFixAction.registerQuickFixActions(builder, null, JvmElementActionFactories.createModifierActions(field, request));
}
return builder;
}
private static boolean inInnerClass(@NotNull PsiElement psiElement, @Nullable PsiClass containingClass) {
@@ -558,96 +565,6 @@ public final class HighlightControlFlowUtil {
return codeBlockProblems;
}
static HighlightInfo.Builder checkVariableMustBeFinal(@NotNull PsiVariable variable,
@NotNull PsiJavaCodeReferenceElement context,
@NotNull LanguageLevel languageLevel) {
if (variable.hasModifierProperty(PsiModifier.FINAL)) return null;
PsiElement scope = ControlFlowUtil.getScopeEnforcingEffectiveFinality(variable, context);
if (scope instanceof PsiClass) {
if (variable instanceof PsiParameter parameter) {
PsiElement parent = variable.getParent();
if (parent instanceof PsiParameterList && parent.getParent() instanceof PsiLambdaExpression &&
!VariableAccessUtils.variableIsAssigned(variable, parameter.getDeclarationScope())) {
return null;
}
}
boolean isToBeEffectivelyFinal = JavaFeature.EFFECTIVELY_FINAL.isSufficient(languageLevel);
if (isToBeEffectivelyFinal && ControlFlowUtil.isEffectivelyFinal(variable, scope, context)) {
return null;
}
String description = JavaErrorBundle
.message(isToBeEffectivelyFinal ? "variable.must.be.final.or.effectively.final" : "variable.must.be.final", context.getText());
HighlightInfo.Builder highlightInfo =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context).descriptionAndTooltip(description);
IntentionAction action = getQuickFixFactory().createVariableAccessFromInnerClassFix(variable, scope);
highlightInfo.registerFix(action, null, null, null, null);
return highlightInfo;
}
HighlightInfo.Builder finalInsideLambdaInfo = checkWriteToFinalInsideLambda(variable, context);
if (finalInsideLambdaInfo != null) {
return finalInsideLambdaInfo;
}
return checkFinalUsageInsideGuardedPattern(variable, context);
}
private static @Nullable HighlightInfo.Builder checkWriteToFinalInsideLambda(@NotNull PsiVariable variable, @NotNull PsiJavaCodeReferenceElement context) {
PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(context, PsiLambdaExpression.class);
if (lambdaExpression != null && !PsiTreeUtil.isAncestor(lambdaExpression, variable, true)) {
PsiElement parent = variable.getParent();
if (parent instanceof PsiParameterList && parent.getParent() == lambdaExpression) {
return null;
}
PsiSwitchLabelStatementBase label =
PsiTreeUtil.getParentOfType(context, PsiSwitchLabelStatementBase.class, true, PsiLambdaExpression.class);
if (label != null && PsiTreeUtil.isAncestor(label.getGuardExpression(), context, false)) {
return null;
}
HighlightInfo.Builder builder = checkVariableMustBeEffectivelyFinal(variable, context, lambdaExpression, "lambda.variable.must.be.final");
if (builder != null) return builder;
}
return null;
}
private static HighlightInfo.Builder checkVariableMustBeEffectivelyFinal(@NotNull PsiVariable variable,
@NotNull PsiJavaCodeReferenceElement context,
@NotNull PsiElement scope,
@NotNull @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String messageKey) {
if (!ControlFlowUtil.isEffectivelyFinal(variable, scope, context)) {
String text = JavaErrorBundle.message(messageKey);
HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context).descriptionAndTooltip(text);
IntentionAction action1 = getQuickFixFactory().createVariableAccessFromInnerClassFix(variable, scope);
builder.registerFix(action1, null, null, null, null);
IntentionAction action2 = getQuickFixFactory().createMakeVariableEffectivelyFinalFix(variable);
if (action2 != null) {
builder.registerFix(action2, null, null, null, null);
}
ErrorFixExtensionPoint.registerFixes(builder, context, messageKey);
return builder;
}
return null;
}
/**
* 14.30.1 Kinds of Patterns
* <p>Any variable that is used but not declared in the guarding expression of a guarded pattern must either be final or effectively final.
*/
private static @Nullable HighlightInfo.Builder checkFinalUsageInsideGuardedPattern(@NotNull PsiVariable variable, @NotNull PsiJavaCodeReferenceElement context) {
PsiSwitchLabelStatementBase refLabel = PsiTreeUtil.getParentOfType(context, PsiSwitchLabelStatementBase.class);
if (refLabel == null) return null;
PsiExpression guardExpression = refLabel.getGuardExpression();
if (!PsiTreeUtil.isAncestor(guardExpression, context, false)) return null;
//this assignment is covered by com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil.checkOutsideDeclaredCantBeAssignmentInGuard
boolean isAssignment = context instanceof PsiReferenceExpression ref && PsiUtil.isAccessedForWriting(ref);
if (!isAssignment && !PsiTreeUtil.isAncestor(guardExpression, variable, false)) {
HighlightInfo.Builder builder = checkVariableMustBeEffectivelyFinal(variable, context, refLabel, "guarded.pattern.variable.must.be.final");
if (builder != null) return builder;
}
return null;
}
/**
* A kind of final variable problem returned from {@link #getFinalVariableProblemsInBlock(Map, PsiElement)}
* which designates a final variable which is initialized in a loop.
@@ -958,10 +958,6 @@ public final class HighlightUtil {
}
}
if ((resolved instanceof PsiLocalVariable || resolved instanceof PsiParameter) && !(resolved instanceof ImplicitVariable)) {
return HighlightControlFlowUtil.checkVariableMustBeFinal((PsiVariable)resolved, ref, languageLevel);
}
return null;
}
@@ -5,6 +5,7 @@ import com.intellij.codeInsight.daemon.JavaErrorBundle;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.codeInsight.daemon.impl.HighlightVisitor;
import com.intellij.codeInsight.intention.CommonIntentionAction;
import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider;
import com.intellij.codeInspection.ex.GlobalInspectionContextBase;
import com.intellij.java.codeserver.highlighting.JavaErrorCollector;
@@ -222,7 +223,9 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
} else {
info.range(anchor);
}
errorFixProvider.processFixes(error, fix -> info.registerFix(fix.asIntention(), null, null, null, null));
Consumer<@NotNull CommonIntentionAction> consumer = fix -> info.registerFix(fix.asIntention(), null, null, null, null);
errorFixProvider.processFixes(error, consumer);
ErrorFixExtensionPoint.registerFixes(consumer, error.psi(), error.kind().key());
error.psiForKind(EXPRESSION_EXPECTED, REFERENCE_UNRESOLVED, REFERENCE_AMBIGUOUS)
.or(() -> error.psiForKind(TYPE_UNKNOWN_CLASS).map(PsiTypeElement::getInnermostComponentReferenceElement))
.or(() -> error.psiForKind(CALL_AMBIGUOUS_NO_MATCH, CALL_UNRESOLVED).map(PsiMethodCallExpression::getMethodExpression))
@@ -490,7 +493,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
if (!hasErrorResults()) {
try {
add(HighlightControlFlowUtil.checkVariableInitializedBeforeUsage(expression, variable, myUninitializedVarProblems, myFile));
add(HighlightControlFlowUtil.checkVariableInitializedBeforeUsage(expression, variable, myUninitializedVarProblems));
}
catch (IndexNotReadyException ignored) {
}
@@ -47,7 +47,8 @@ import java.util.function.Consumer;
import java.util.stream.Stream;
import static com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds.*;
import static java.util.Objects.*;
import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNullElse;
/**
* Fixes attached to error messages provided by {@link JavaErrorCollector}.
@@ -277,7 +278,6 @@ final class JavaErrorFixProvider {
else if (element instanceof PsiClass cls) {
sink.accept(myFactory.createCreateConstructorMatchingSuperFix(cls));
}
ErrorFixExtensionPoint.registerFixes(sink, element, "unhandled.exceptions");
});
fixes(EXCEPTION_UNHANDLED_CLOSE, (error, sink) -> HighlightFixUtil.registerUnhandledExceptionFixes(error.psi(), sink));
}
@@ -375,6 +375,18 @@ final class JavaErrorFixProvider {
fix(UNNAMED_VARIABLE_WITHOUT_INITIALIZER, error -> myFactory.createAddVariableInitializerFix(error.psi()));
fixes(LVTI_NO_INITIALIZER, (error, sink) -> HighlightFixUtil.registerSpecifyVarTypeFix(error.psi(), sink));
fixes(LVTI_NULL, (error, sink) -> HighlightFixUtil.registerSpecifyVarTypeFix(error.psi(), sink));
JavaFixProvider<PsiJavaCodeReferenceElement, PsiVariable> innerClassAccessFix = error -> {
PsiVariable variable = error.context();
PsiElement scope = requireNonNull(ControlFlowUtil.getScopeEnforcingEffectiveFinality(variable, error.psi()));
return myFactory.createVariableAccessFromInnerClassFix(variable, scope);
};
fix(VARIABLE_MUST_BE_FINAL, innerClassAccessFix);
fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL, innerClassAccessFix);
fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL_LAMBDA, innerClassAccessFix);
fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL_GUARD, innerClassAccessFix);
fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL, error -> myFactory.createMakeVariableEffectivelyFinalFix(error.context()));
fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL_LAMBDA, error -> myFactory.createMakeVariableEffectivelyFinalFix(error.context()));
fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL_GUARD, error -> myFactory.createMakeVariableEffectivelyFinalFix(error.context()));
}
private void createExpressionFixes() {
@@ -57,9 +57,10 @@ public final class FinalUtils {
Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems) {
if (!(e instanceof PsiReferenceExpression ref)) return true;
if (!ref.isReferenceTo(variable)) return true;
HighlightInfo.Builder highlightInfo = HighlightControlFlowUtil
.checkVariableInitializedBeforeUsage(ref, variable, uninitializedVarProblems, variable.getContainingFile(), true);
if (highlightInfo != null) return false;
if (!HighlightControlFlowUtil.isInitializedBeforeUsage(
ref, variable, uninitializedVarProblems, true)) {
return false;
}
if (!PsiUtil.isAccessedForWriting(ref)) return true;
if (!LocalsOrMyInstanceFieldsControlFlowPolicy.isLocalOrMyInstanceReference(ref)) return false;
if (ControlFlowUtil.isVariableAssignedInLoop(ref, variable)) return false;
@@ -939,7 +939,7 @@
<jvm.logging implementation="com.intellij.lang.logging.UnspecifiedLogger"/>
<javaModuleSystem implementation="com.intellij.psi.impl.JavaPlatformModuleSystem"/>
<java.error.fix errorCode="lambda.variable.must.be.final"
<java.error.fix errorCode="variable.must.be.effectively.final.lambda"
implementationClass="com.intellij.codeInspection.streamMigration.SimplifyForEachInspection$ForEachNonFinalFix"/>
<lang.jvm.actions.jvmElementActionsFactory implementation="com.intellij.codeInsight.intention.impl.JavaElementActionsFactory"/>
<longLineInspectionPolicy implementation="com.intellij.codeInspection.JavaLongLineInspectionPolicy"/>
@@ -1580,9 +1580,9 @@
<lang.namesValidator language="JAVA" implementationClass="com.intellij.lang.refactoring.JavaNamesValidator"/>
<actionPromoter implementation="com.intellij.codeInsight.editorActions.JavaMethodOverloadSwitchActionPromoter"/>
<actionPromoter implementation="com.intellij.codeInsight.editorActions.JavaNextParameterActionPromoter"/>
<java.error.fix errorCode="lambda.variable.must.be.final"
<java.error.fix errorCode="variable.must.be.effectively.final.lambda"
implementationClass="com.intellij.codeInsight.daemon.impl.quickfix.VariableAccessFromInnerClassJava10Fix"/>
<java.error.fix errorCode="guarded.pattern.variable.must.be.final"
<java.error.fix errorCode="variable.must.be.effectively.final.guard"
implementationClass="com.intellij.codeInsight.daemon.impl.quickfix.VariableAccessFromInnerClassJava10Fix"/>
<stripTrailingSpacesFilterFactory implementation="com.intellij.codeEditor.JavaStripTrailingSpacesFilterFactory"/>
@@ -170,7 +170,7 @@ public final class BringVariableIntoScopeFix implements ModCommandAction {
outOfScopeVariable.delete();
}
if (HighlightControlFlowUtil.checkVariableInitializedBeforeUsage(reference, addedVar, new HashMap<>(), file) != null) {
if (!HighlightControlFlowUtil.isInitializedBeforeUsage(reference, addedVar, new HashMap<>(), false)) {
initialize(addedVar);
}
}
@@ -317,10 +317,12 @@ public class VariableAccessFromInnerClassFix implements IntentionAction {
Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems = new HashMap<>();
for (PsiReferenceExpression expression : references) {
if (ControlFlowUtil.isVariableAssignedInLoop(expression, variable)) return false;
HighlightInfo.Builder highlightInfo = HighlightControlFlowUtil.checkVariableInitializedBeforeUsage(expression, variable, uninitializedVarProblems,
variable.getContainingFile());
if (highlightInfo != null) return false;
highlightInfo = HighlightControlFlowUtil.checkFinalVariableMightAlreadyHaveBeenAssignedTo(variable, expression, finalVarProblems);
if (!HighlightControlFlowUtil.isInitializedBeforeUsage(
expression, variable, uninitializedVarProblems, false)) {
return false;
}
HighlightInfo.Builder highlightInfo =
HighlightControlFlowUtil.checkFinalVariableMightAlreadyHaveBeenAssignedTo(variable, expression, finalVarProblems);
if (highlightInfo != null) return false;
if (variable instanceof PsiParameter && PsiUtil.isAccessedForWriting(expression)) return false;
}
@@ -25,7 +25,7 @@ public final class ControlFlowUtil {
* @param variable variable
* @param context the context that references to the variable
* @return the scope around context that enforces variable to be effectively final. Currently, it could be
* an inner class, lambda expression, or switch guard. Returns null if there's no such scope,
* an inner class, lambda expression, or {@link PsiSwitchLabelStatementBase} for switch guard. Returns null if there's no such scope,
* or the variable declaration is within the same scope, so it should not be effectively final.
* Note that if null is returned, it doesn't mean that the variable could be modified, as another reference from
* another place might exist.
@@ -3,20 +3,20 @@ import java.util.function.*;
class Test {
void test1(Object o, int mode) {
switch (o) {
case Integer i when i == <error descr="Variable used in guarded pattern should be final or effectively final">mode</error> -> System.out.println();
case Integer i when i == <error descr="Variable used in guard expression should be final or effectively final">mode</error> -> System.out.println();
default -> {}
}
switch (o) {
case Integer i when (switch (o) {
case Integer ii when ii != <error descr="Variable used in guarded pattern should be final or effectively final">mode</error> -> 2;
case Integer ii when ii != <error descr="Variable used in guard expression should be final or effectively final">mode</error> -> 2;
default -> 1;
}) == <error descr="Variable used in guarded pattern should be final or effectively final">mode</error> -> System.out.println();
}) == <error descr="Variable used in guard expression should be final or effectively final">mode</error> -> System.out.println();
default -> {}
}
switch (o) {
case Integer i when (i = <error descr="Variable used in guarded pattern should be final or effectively final">mode</error>) > 0 -> System.out.println();
case Integer i when (i = <error descr="Variable used in guard expression should be final or effectively final">mode</error>) > 0 -> System.out.println();
default -> {}
}
mode = 0;
@@ -24,7 +24,7 @@ class Test {
void test2(Object o, final int mode) {
switch (o) {
case Integer i when (switch (<error descr="Variable used in guarded pattern should be final or effectively final">o</error>) {
case Integer i when (switch (<error descr="Variable used in guard expression should be final or effectively final">o</error>) {
case Integer ii when ii != mode -> 2;
default -> 1;
}) == mode -> o = null;
@@ -60,7 +60,7 @@ class Test {
switch (o) {
case Integer mode when (<error descr="Cannot assign a value to variable 'mode', because it is declared outside the guard">mode</error> = 42) > 9:
switch (o) {
case Integer i when (i = <error descr="Variable used in guarded pattern should be final or effectively final">mode</error>) > 0 -> System.out.println();
case Integer i when (i = <error descr="Variable used in guard expression should be final or effectively final">mode</error>) > 0 -> System.out.println();
default -> System.out.println();
}
default : break;
@@ -69,7 +69,7 @@ class Test {
str = switch (o) {
case Integer mode when (<error descr="Cannot assign a value to variable 'mode', because it is declared outside the guard">mode</error> = 42) > 9 ->
switch (o) {
case Integer i when (i = <error descr="Variable used in guarded pattern should be final or effectively final">mode</error>) > 0 -> "";
case Integer i when (i = <error descr="Variable used in guard expression should be final or effectively final">mode</error>) > 0 -> "";
default -> "";
};
default -> "";
@@ -77,21 +77,21 @@ class Test {
str = switch (o) {
case Integer mode when (<error descr="Cannot assign a value to variable 'mode', because it is declared outside the guard">mode</error> = 42) > 9:
yield switch (o) {
case Integer i when (i = <error descr="Variable used in guarded pattern should be final or effectively final">mode</error>) > 0 -> "";
case Integer i when (i = <error descr="Variable used in guard expression should be final or effectively final">mode</error>) > 0 -> "";
default -> "";
};
default: yield "";
};
// lambdas
str = switch (o) {
case Integer i when (i = <error descr="Variable used in guarded pattern should be final or effectively final">in</error>) > 0:
case Integer i when (i = <error descr="Variable used in guard expression should be final or effectively final">in</error>) > 0:
yield ((Function<Integer, String>)(x) -> (<error descr="Variable used in lambda expression should be final or effectively final">in</error> = 5) > 0 ? "" : null).apply(in);
default:
yield "";
};
Consumer<Integer> c = (mode) -> {
switch (o) {
case Integer i when (i = <error descr="Variable used in guarded pattern should be final or effectively final">in</error>) > 0 -> System.out.println();
case Integer i when (i = <error descr="Variable used in guard expression should be final or effectively final">in</error>) > 0 -> System.out.println();
default -> System.out.println();
}
<error descr="Variable used in lambda expression should be final or effectively final">in</error> = 1;
@@ -110,7 +110,7 @@ class Test {
switch (o) {
case Integer i -> {
switch (o) {
case Integer ii when ii > <error descr="Variable used in guarded pattern should be final or effectively final">mode</error>:
case Integer ii when ii > <error descr="Variable used in guard expression should be final or effectively final">mode</error>:
break;
default:
break;
@@ -174,7 +174,7 @@ class Test {
public static void testWhenReassigned() {
Object object = "1234";
switch (object) {
case String s when <error descr="Variable used in guarded pattern should be final or effectively final">s</error>.length()==2 -> {
case String s when <error descr="Variable used in guard expression should be final or effectively final">s</error>.length()==2 -> {
s = null;
}
default -> {
@@ -35,13 +35,13 @@ class Main {
void nestedStatement(Object o, Object o2, int p) {
int m = 0;
switch (o) {
case Integer n when <error descr="Variable used in guarded pattern should be final or effectively final">n</error> < 1:
case Integer n when <error descr="Variable used in guard expression should be final or effectively final">n</error> < 1:
n ++;
case Integer n when n > 1:
switch(o2) {
case Integer <error descr="Variable 'm' is already defined in the scope">m</error> when <error descr="Variable used in guarded pattern should be final or effectively final">m</error> > 0:
case Integer <error descr="Variable 'm' is already defined in the scope">m</error> when <error descr="Variable used in guard expression should be final or effectively final">m</error> > 0:
m += n;
case Integer <error descr="Variable 'p' is already defined in the scope">p</error> when <error descr="Variable used in guarded pattern should be final or effectively final">p</error> > 0:
case Integer <error descr="Variable 'p' is already defined in the scope">p</error> when <error descr="Variable used in guard expression should be final or effectively final">p</error> > 0:
p += n + m;
break;
case Integer p1:
@@ -47,6 +47,7 @@
groupKey="group.names.language.level.specific.issues.and.migration.aids8" enabledByDefault="false" level="WARNING"
implementationClass="com.intellij.refactoring.typeMigration.inspections.GuavaInspection"
bundle="messages.TypeMigrationBundle" key="inspection.guava.name"/>
<java.error.fix errorCode="lambda.variable.must.be.final" implementationClass="com.intellij.refactoring.typeMigration.intentions.ConvertFieldToAtomicIntention$ConvertNonFinalLocalToAtomicFix"/>
<java.error.fix errorCode="variable.must.be.effectively.final.lambda"
implementationClass="com.intellij.refactoring.typeMigration.intentions.ConvertFieldToAtomicIntention$ConvertNonFinalLocalToAtomicFix"/>
</extensions>
</idea-plugin>
@@ -22,8 +22,7 @@
<descriptionDirectoryName>customDescriptionDirectory</descriptionDirectoryName>
</intentionAction>
<java.error.fix errorCode="unhandled.exceptions"
implementationClass="MyJavaErrorFix"/>
<java.error.fix errorCode="exception.unhandled" implementationClass="MyJavaErrorFix"/>
</extensions>
</idea-plugin>
@@ -83,7 +83,7 @@
<fileBasedIndex implementation="de.plushnikov.intellij.plugin.lombokconfig.LombokConfigIndex"/>
<custom.exception.handler implementation="de.plushnikov.intellij.plugin.handler.SneakyThrowsExceptionHandler"/>
<java.error.fix errorCode="unhandled.exceptions" implementationClass="de.plushnikov.intellij.plugin.handler.AddSneakyThrowsAnnotationCommandAction"/>
<java.error.fix errorCode="exception.unhandled" implementationClass="de.plushnikov.intellij.plugin.handler.AddSneakyThrowsAnnotationCommandAction"/>
<implicit.resource.closer
implementation="de.plushnikov.intellij.plugin.extension.LombokCleanUpImplicitResourceCloser"/>