[java-highlighting] The rest of control-flow-related stuff (checkFinalVariableMightAlreadyHaveBeenAssignedTo) migrated

More utility methods inside ControlFlowUtil
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: d4c294ce18da2032a0686f66794a7f377549edd2
This commit is contained in:
Tagir Valeev
2025-02-06 10:35:32 +00:00
committed by intellij-monorepo-bot
parent cc643a5ae4
commit 1cd294e40d
20 changed files with 268 additions and 282 deletions
@@ -379,6 +379,11 @@ variable.must.be.effectively.final=Variable ''{0}'' is accessed from within inne
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
variable.not.initialized=Variable ''{0}'' might not have been initialized
variable.already.assigned=Variable ''{0}'' might already have been assigned to
variable.already.assigned.constructor=Cannot assign final field ''{0}'' after chained constructor call
variable.already.assigned.field=Final field ''{0}'' is already initialized in another field initializer
variable.already.assigned.initializer=Final field ''{0}'' is already initialized in a class initializer
variable.assigned.in.loop=Variable ''{0}'' might be assigned in loop
field.not.initialized=Field ''{0}'' might not have been initialized
instanceof.type.parameter=Class or array expected
@@ -120,7 +120,6 @@ final class ControlFlowChecker {
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));
@@ -139,13 +138,28 @@ final class ControlFlowChecker {
myVisitor.report(JavaErrorKinds.FIELD_NOT_INITIALIZED.create(field));
}
void checkVariableInitializedBeforeUsage(@NotNull PsiReferenceExpression expression, @NotNull PsiVariable variable) {
void checkVariableInitializedBeforeUsage(@NotNull PsiVariable variable, @NotNull PsiReferenceExpression expression) {
if (ControlFlowUtil.isInitializedBeforeUsage(expression, variable, myUninitializedVarProblems, false)) {
return;
}
myVisitor.report(JavaErrorKinds.VARIABLE_NOT_INITIALIZED.create(expression, variable));
}
void checkFinalVariableMightAlreadyHaveBeenAssignedTo(@NotNull PsiVariable variable, @NotNull PsiReferenceExpression expression) {
ControlFlowUtil.DoubleInitializationProblem
problem = ControlFlowUtil.findFinalVariableAlreadyInitializedProblem(variable, expression, myFinalVarProblems);
var kind = switch (problem) {
case NORMAL -> JavaErrorKinds.VARIABLE_ALREADY_ASSIGNED;
case IN_LOOP -> JavaErrorKinds.VARIABLE_ASSIGNED_IN_LOOP;
case IN_CONSTRUCTOR -> JavaErrorKinds.VARIABLE_ALREADY_ASSIGNED_CONSTRUCTOR;
case IN_FIELD_INITIALIZER -> JavaErrorKinds.VARIABLE_ALREADY_ASSIGNED_FIELD;
case IN_INITIALIZER -> JavaErrorKinds.VARIABLE_ALREADY_ASSIGNED_INITIALIZER;
case NO_PROBLEM -> null;
};
if (kind == null) return;
myVisitor.report(kind.create(expression, variable));
}
/**
* @return field that has initializer with this element as subexpression or null if not found
*/
@@ -706,7 +706,11 @@ final class JavaErrorVisitor extends JavaElementVisitor {
if (!hasErrorResults() && resolved instanceof PsiLocalVariable localVariable) {
myExpressionChecker.checkVarTypeSelfReferencing(localVariable, expression);
}
if (!hasErrorResults()) myControlFlowChecker.checkVariableInitializedBeforeUsage(expression, variable);
boolean isFinal = variable.hasModifierProperty(PsiModifier.FINAL);
if (isFinal && !variable.hasInitializer() && !(variable instanceof PsiPatternVariable)) {
if (!hasErrorResults()) myControlFlowChecker.checkFinalVariableMightAlreadyHaveBeenAssignedTo(variable, expression);
}
if (!hasErrorResults()) myControlFlowChecker.checkVariableInitializedBeforeUsage(variable, expression);
}
if (parent instanceof PsiMethodCallExpression methodCallExpression &&
methodCallExpression.getMethodExpression() == expression &&
@@ -1243,6 +1243,21 @@ public final class JavaErrorKinds {
public static final Parameterized<PsiReferenceExpression, PsiVariable> VARIABLE_NOT_INITIALIZED =
parameterized(PsiReferenceExpression.class, PsiVariable.class, "variable.not.initialized")
.withRawDescription((ref, var) -> message("variable.not.initialized", var.getName()));
public static final Parameterized<PsiReferenceExpression, PsiVariable> VARIABLE_ALREADY_ASSIGNED =
parameterized(PsiReferenceExpression.class, PsiVariable.class, "variable.already.assigned")
.withRawDescription((ref, var) -> message("variable.already.assigned", var.getName()));
public static final Parameterized<PsiReferenceExpression, PsiVariable> VARIABLE_ALREADY_ASSIGNED_CONSTRUCTOR =
parameterized(PsiReferenceExpression.class, PsiVariable.class, "variable.already.assigned.constructor")
.withRawDescription((ref, var) -> message("variable.already.assigned.constructor", var.getName()));
public static final Parameterized<PsiReferenceExpression, PsiVariable> VARIABLE_ALREADY_ASSIGNED_FIELD =
parameterized(PsiReferenceExpression.class, PsiVariable.class, "variable.already.assigned.field")
.withRawDescription((ref, var) -> message("variable.already.assigned.field", var.getName()));
public static final Parameterized<PsiReferenceExpression, PsiVariable> VARIABLE_ALREADY_ASSIGNED_INITIALIZER =
parameterized(PsiReferenceExpression.class, PsiVariable.class, "variable.already.assigned.initializer")
.withRawDescription((ref, var) -> message("variable.already.assigned.initializer", var.getName()));
public static final Parameterized<PsiReferenceExpression, PsiVariable> VARIABLE_ASSIGNED_IN_LOOP =
parameterized(PsiReferenceExpression.class, PsiVariable.class, "variable.assigned.in.loop")
.withRawDescription((ref, var) -> message("variable.assigned.in.loop", var.getName()));
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()));
@@ -1,30 +1,20 @@
// 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.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.daemon.JavaErrorBundle;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.*;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.JavaPsiConstructorUtil;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiVariable;
import com.intellij.psi.controlFlow.AnalysisCanceledException;
import com.intellij.psi.controlFlow.ControlFlow;
import com.intellij.psi.controlFlow.ControlFlowFactory;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @deprecated all the methods are deprecated
*/
@Deprecated
public final class HighlightControlFlowUtil {
private static QuickFixFactory getQuickFixFactory() {
return QuickFixFactory.getInstance();
}
private HighlightControlFlowUtil() { }
/**
@@ -42,192 +32,4 @@ public final class HighlightControlFlowUtil {
public static boolean variableDefinitelyAssignedIn(@NotNull PsiVariable variable, @NotNull PsiElement context) {
return ControlFlowUtil.variableDefinitelyAssignedIn(variable, context);
}
private static @NotNull ControlFlow getControlFlow(@NotNull PsiElement context) throws AnalysisCanceledException {
LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance();
return ControlFlowFactory.getControlFlow(context, policy, ControlFlowOptions.create(true, true, true));
}
public static boolean isAssigned(@NotNull PsiParameter parameter) {
ParamWriteProcessor processor = new ParamWriteProcessor();
ReferencesSearch.search(parameter, new LocalSearchScope(parameter.getDeclarationScope()), true).forEach(processor);
return processor.isWriteRefFound();
}
private static class ParamWriteProcessor implements Processor<PsiReference> {
private volatile boolean myIsWriteRefFound;
@Override
public boolean process(@NotNull PsiReference reference) {
PsiElement element = reference.getElement();
if (element instanceof PsiReferenceExpression ref && PsiUtil.isAccessedForWriting(ref)) {
myIsWriteRefFound = true;
return false;
}
return true;
}
private boolean isWriteRefFound() {
return myIsWriteRefFound;
}
}
private static boolean variableDefinitelyNotAssignedIn(@NotNull PsiVariable variable, @NotNull PsiElement context) {
try {
return ControlFlowUtil.isVariableDefinitelyNotAssigned(variable, getControlFlow(context));
}
catch (AnalysisCanceledException e) {
return true;
}
}
/**
* @param variable variable to check
* @param finalVarProblems cache map to reuse information
* @return true if variable is reassigned
*/
public static boolean isReassigned(@NotNull PsiVariable variable,
@NotNull Map<? super PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems) {
if (variable instanceof PsiLocalVariable) {
PsiElement parent = variable.getParent();
if (parent == null) return false;
PsiElement declarationScope = parent.getParent();
if (declarationScope == null) return false;
Collection<ControlFlowUtil.VariableInfo> codeBlockProblems = getFinalVariableProblemsInBlock(finalVarProblems, declarationScope);
return codeBlockProblems.contains(new ControlFlowUtil.VariableInfo(variable, null));
}
if (variable instanceof PsiParameter parameter) {
return isAssigned(parameter);
}
return false;
}
public static HighlightInfo.Builder checkFinalVariableMightAlreadyHaveBeenAssignedTo(@NotNull PsiVariable variable,
@NotNull PsiReferenceExpression expression,
@NotNull Map<? super PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems) {
if (!PsiUtil.isAccessedForWriting(expression)) return null;
PsiElement scope = variable instanceof PsiField
? variable.getParent()
: variable.getParent() == null ? null : variable.getParent().getParent();
PsiElement codeBlock = PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope);
if (codeBlock == null) return null;
Collection<ControlFlowUtil.VariableInfo> codeBlockProblems = getFinalVariableProblemsInBlock(finalVarProblems, codeBlock);
boolean inLoop = false;
boolean canDefer = false;
ControlFlowUtil.VariableInfo variableInfo = ContainerUtil.find(codeBlockProblems, vi -> vi.expression == expression);
if (variableInfo != null) {
inLoop = variableInfo instanceof InitializedInLoopProblemInfo;
canDefer = !inLoop;
}
else if (!(variable instanceof PsiField field && isFieldInitializedInAnotherMember(field, expression, codeBlock))) {
return null;
}
String description =
JavaErrorBundle.message(inLoop ? "variable.assigned.in.loop" : "variable.already.assigned", variable.getName());
HighlightInfo.Builder highlightInfo =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description);
if (canDefer) {
IntentionAction action = getQuickFixFactory().createDeferFinalAssignmentFix(variable, expression);
highlightInfo.registerFix(action, null, null, null, null);
}
HighlightFixUtil.registerMakeNotFinalAction(variable, highlightInfo);
return highlightInfo;
}
private static boolean isFieldInitializedInAnotherMember(@NotNull PsiField field,
@NotNull PsiReferenceExpression expression,
@NotNull PsiElement codeBlock) {
PsiClass aClass = field.getContainingClass();
if (aClass == null) return false;
boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC);
PsiMember enclosingConstructorOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expression);
if (!isFieldStatic) {
// constructor that delegates to another constructor cannot assign final fields
if (enclosingConstructorOrInitializer instanceof PsiMethod method) {
PsiMethodCallExpression chainedCall = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(method);
if (JavaPsiConstructorUtil.isChainedConstructorCall(chainedCall)) {
return true;
}
}
}
// field can get assigned in other field initializers or in class initializers
List<PsiMember> members = new ArrayList<>(Arrays.asList(aClass.getFields()));
if (enclosingConstructorOrInitializer != null
&& aClass.getManager().areElementsEquivalent(enclosingConstructorOrInitializer.getContainingClass(), aClass)) {
members.addAll(Arrays.asList(aClass.getInitializers()));
members.sort(PsiUtil.BY_POSITION);
}
for (PsiMember member : members) {
if (member == field) continue;
PsiElement context = member instanceof PsiField f ? f.getInitializer() : ((PsiClassInitializer)member).getBody();
if (context != null
&& member.hasModifierProperty(PsiModifier.STATIC) == isFieldStatic
&& !variableDefinitelyNotAssignedIn(field, context)) {
return context != codeBlock;
}
}
return false;
}
private static @NotNull Collection<ControlFlowUtil.VariableInfo> getFinalVariableProblemsInBlock(@NotNull Map<? super PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems,
@NotNull PsiElement codeBlock) {
Collection<ControlFlowUtil.VariableInfo> codeBlockProblems = finalVarProblems.get(codeBlock);
if (codeBlockProblems == null) {
try {
ControlFlow controlFlow = getControlFlow(codeBlock);
codeBlockProblems = ControlFlowUtil.getInitializedTwice(controlFlow);
codeBlockProblems = addReassignedInLoopProblems(codeBlockProblems, controlFlow);
}
catch (AnalysisCanceledException e) {
codeBlockProblems = Collections.emptyList();
}
finalVarProblems.put(codeBlock, codeBlockProblems);
}
return codeBlockProblems;
}
private static Collection<ControlFlowUtil.VariableInfo> addReassignedInLoopProblems(
@NotNull Collection<ControlFlowUtil.VariableInfo> codeBlockProblems,
@NotNull ControlFlow controlFlow) {
List<Instruction> instructions = controlFlow.getInstructions();
for (int index = 0; index < instructions.size(); index++) {
Instruction instruction = instructions.get(index);
if (instruction instanceof WriteVariableInstruction wvi) {
PsiVariable variable = wvi.variable;
if (variable instanceof PsiLocalVariable || variable instanceof PsiField) {
PsiElement anchor = controlFlow.getElement(index);
if (anchor instanceof PsiAssignmentExpression assignment) {
PsiExpression ref = PsiUtil.skipParenthesizedExprDown(assignment.getLExpression());
if (ref instanceof PsiReferenceExpression) {
ControlFlowUtil.VariableInfo varInfo = new InitializedInLoopProblemInfo(variable, ref);
if (!codeBlockProblems.contains(varInfo) && ControlFlowUtil.isInstructionReachable(controlFlow, index, index)) {
if (!(codeBlockProblems instanceof HashSet)) {
codeBlockProblems = new HashSet<>(codeBlockProblems);
}
codeBlockProblems.add(varInfo);
}
}
}
}
}
}
return codeBlockProblems;
}
/**
* A kind of final variable problem returned from {@link #getFinalVariableProblemsInBlock(Map, PsiElement)}
* which designates a final variable which is initialized in a loop.
*/
private static class InitializedInLoopProblemInfo extends ControlFlowUtil.VariableInfo {
InitializedInLoopProblemInfo(@NotNull PsiVariable variable, @Nullable PsiElement expression) {
super(variable, expression);
}
}
}
@@ -2,7 +2,6 @@
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.quickfix.*;
import com.intellij.codeInsight.intention.CommonIntentionAction;
import com.intellij.codeInsight.intention.IntentionAction;
@@ -41,7 +40,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Consumer;
import static java.util.Objects.*;
import static java.util.Objects.requireNonNullElse;
public final class HighlightFixUtil {
private static final Logger LOG = Logger.getInstance(HighlightFixUtil.class);
@@ -184,12 +183,6 @@ public final class HighlightFixUtil {
return qname == null || !Character.isLowerCase(qname.charAt(0));
}
static void registerChangeVariableTypeFixes(@NotNull PsiVariable parameter,
@Nullable PsiType itemType,
@Nullable HighlightInfo.Builder highlightInfo) {
registerChangeVariableTypeFixes(parameter, itemType, HighlightUtil.asConsumer(highlightInfo));
}
static void registerChangeVariableTypeFixes(@NotNull PsiVariable parameter,
@Nullable PsiType itemType,
@NotNull Consumer<? super CommonIntentionAction> info) {
@@ -351,21 +344,6 @@ public final class HighlightFixUtil {
return null;
}
static void registerMakeNotFinalAction(@NotNull PsiVariable var, @Nullable HighlightInfo.Builder highlightInfo) {
if (var instanceof PsiField) {
QuickFixAction.registerQuickFixActions(
highlightInfo, null,
JvmElementActionFactories.createModifierActions((PsiField)var, MemberRequestsKt.modifierRequest(JvmModifier.FINAL, false))
);
}
else {
IntentionAction action = QuickFixFactory.getInstance().createModifierListFix(var, PsiModifier.FINAL, false, false);
if (highlightInfo != null) {
highlightInfo.registerFix(action, null, null, null, null);
}
}
}
public static void registerFixesForExpressionStatement(@NotNull PsiElement statement, @NotNull Consumer<? super CommonIntentionAction> info) {
if (!(statement instanceof PsiExpressionStatement)) return;
PsiCodeBlock block = ObjectUtils.tryCast(statement.getParent(), PsiCodeBlock.class);
@@ -475,15 +475,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
JavaResolveResult result = results.length == 1 ? results[0] : JavaResolveResult.EMPTY;
PsiElement resolved = result.getElement();
if (resolved instanceof PsiVariable variable && resolved.getContainingFile() == expression.getContainingFile()) {
boolean isFinal = variable.hasModifierProperty(PsiModifier.FINAL);
if (isFinal && !variable.hasInitializer() && !(variable instanceof PsiPatternVariable)) {
if (!hasErrorResults()) {
add(HighlightControlFlowUtil.checkFinalVariableMightAlreadyHaveBeenAssignedTo(variable, expression, myFinalVarProblems));
}
}
}
if (!hasErrorResults()) add(HighlightUtil.checkClassReferenceAfterQualifier(expression, resolved));
PsiExpression qualifierExpression = expression.getQualifierExpression();
if (!hasErrorResults() && myJavaModule == null && qualifierExpression != null) {
@@ -388,6 +388,13 @@ final class JavaErrorFixProvider {
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()));
fix(VARIABLE_ALREADY_ASSIGNED, error -> myFactory.createDeferFinalAssignmentFix(error.context(), error.psi()));
fix(VARIABLE_ALREADY_ASSIGNED, error -> removeModifierFix(error.context(), PsiModifier.FINAL));
fix(VARIABLE_ALREADY_ASSIGNED_FIELD, error -> removeModifierFix(error.context(), PsiModifier.FINAL));
fix(VARIABLE_ALREADY_ASSIGNED_CONSTRUCTOR, error -> removeModifierFix(error.context(), PsiModifier.FINAL));
fix(VARIABLE_ALREADY_ASSIGNED_INITIALIZER, error -> removeModifierFix(error.context(), PsiModifier.FINAL));
fix(VARIABLE_ASSIGNED_IN_LOOP, error -> removeModifierFix(error.context(), PsiModifier.FINAL));
fixes(FIELD_NOT_INITIALIZED, (error, sink) -> {
PsiField field = error.psi();
sink.accept(myFactory.createCreateConstructorParameterFromFieldFix(field));
@@ -1,10 +1,10 @@
// 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.codeInspection;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.java.JavaBundle;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
@@ -42,7 +42,7 @@ public final class ReassignedVariableInspection extends AbstractBaseJavaLocalIns
PsiIdentifier nameIdentifier = variable.getNameIdentifier();
if (nameIdentifier != null &&
!variable.hasModifierProperty(PsiModifier.FINAL) &&
HighlightControlFlowUtil.isReassigned(variable, myLocalVariableProblems)) {
ControlFlowUtil.isReassigned(variable, myLocalVariableProblems)) {
myHolder.registerProblem(nameIdentifier, getReassignedMessage(variable));
return true;
}
@@ -60,13 +60,12 @@ public final class ReassignedVariableInspection extends AbstractBaseJavaLocalIns
!((PsiVariable)resolved).hasModifierProperty(PsiModifier.FINAL) &&
!SuppressionUtil.inspectionResultSuppressed(resolved, ReassignedVariableInspection.this)) {
if (resolved instanceof PsiLocalVariable) {
if (HighlightControlFlowUtil.isReassigned((PsiVariable)resolved, myLocalVariableProblems)) {
if (ControlFlowUtil.isReassigned((PsiVariable)resolved, myLocalVariableProblems)) {
myHolder.registerProblem(referenceNameElement, getReassignedMessage((PsiVariable)resolved));
}
}
else {
Boolean isReassigned = myParameterIsReassigned.computeIfAbsent((PsiParameter)resolved,
HighlightControlFlowUtil::isAssigned);
Boolean isReassigned = myParameterIsReassigned.computeIfAbsent((PsiParameter)resolved, VariableAccessUtils::variableIsAssigned);
if (isReassigned) {
myHolder.registerProblem(referenceNameElement, getReassignedMessage((PsiVariable)resolved));
}
@@ -5,7 +5,6 @@ import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInsight.NullabilityAnnotationInfo;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.codeInspection.dataFlow.interpreter.RunnerResult;
import com.intellij.codeInspection.dataFlow.interpreter.StandardDataFlowInterpreter;
@@ -37,6 +36,7 @@ import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.DeepestSuperMethodsSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
@@ -231,7 +231,7 @@ public final class DfaPsiUtil {
refExpr.resolve() instanceof PsiParameter parameter &&
parameter.getParent() instanceof PsiForeachStatement targetLoop &&
PsiTreeUtil.isAncestor(targetLoop, loop, true) &&
!HighlightControlFlowUtil.isReassigned(parameter, new HashMap<>())) {
!ControlFlowUtil.isReassigned(parameter, new HashMap<>())) {
iteratedType = inferLoopParameterTypeWithNullability(targetLoop);
}
return JavaGenericsUtil.getCollectionItemType(iteratedType, iteratedValue.getResolveScope());
@@ -1,8 +1,6 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.psiutils;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.intellij.psi.controlFlow.LocalsOrMyInstanceFieldsControlFlowPolicy;
@@ -69,8 +67,7 @@ public final class FinalUtils {
PsiElement innerScope = ControlFlowUtil.getScopeEnforcingEffectiveFinality(variable, ref);
if (innerScope != null && innerScope != ((PsiField)variable).getContainingClass()) return false;
}
HighlightInfo.Builder random =
HighlightControlFlowUtil.checkFinalVariableMightAlreadyHaveBeenAssignedTo(variable, ref, finalVarProblems);
return random == null;
return ControlFlowUtil.findFinalVariableAlreadyInitializedProblem(variable, ref, finalVarProblems) ==
ControlFlowUtil.DoubleInitializationProblem.NO_PROBLEM;
}
}
@@ -2,8 +2,6 @@
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.codeInsight.intention.FileModifier;
import com.intellij.codeInsight.intention.IntentionAction;
@@ -321,9 +319,10 @@ public class VariableAccessFromInnerClassFix implements IntentionAction {
expression, variable, uninitializedVarProblems, false)) {
return false;
}
HighlightInfo.Builder highlightInfo =
HighlightControlFlowUtil.checkFinalVariableMightAlreadyHaveBeenAssignedTo(variable, expression, finalVarProblems);
if (highlightInfo != null) return false;
if (ControlFlowUtil.findFinalVariableAlreadyInitializedProblem(variable, expression, finalVarProblems) !=
ControlFlowUtil.DoubleInitializationProblem.NO_PROBLEM) {
return false;
}
if (variable instanceof PsiParameter && PsiUtil.isAccessedForWriting(expression)) return false;
}
return true;
@@ -5,7 +5,6 @@ import com.intellij.codeInsight.AutoPopupController;
import com.intellij.codeInsight.TailTypes;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.daemon.impl.JavaColorProvider;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.codeInsight.daemon.impl.quickfix.BringVariableIntoScopeFix;
import com.intellij.codeInsight.lookup.impl.JavaElementLookupRenderer;
import com.intellij.codeInspection.dataFlow.jvm.descriptors.PlainDescriptor;
@@ -306,7 +305,7 @@ public class VariableLookupItem extends LookupItem<PsiVariable> implements Typed
}
if (ControlFlowUtil.getScopeEnforcingEffectiveFinality(variable, place) != null &&
!HighlightControlFlowUtil.isReassigned(variable, new HashMap<>())) {
!ControlFlowUtil.isReassigned(variable, new HashMap<>())) {
PsiUtil.setModifierProperty(variable, PsiModifier.FINAL, true);
}
}
@@ -1,12 +1,12 @@
// 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.usages.impl.rules;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiSuperMethodImplUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.usages.PsiElementUsageTarget;
import com.intellij.usages.UsageTarget;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -78,7 +78,7 @@ public final class JavaUsageTypeProvider implements UsageTypeProviderEx {
}
for (PsiParameter parameter : parameters) {
if (HighlightControlFlowUtil.isAssigned(parameter)) return false;
if (VariableAccessUtils.variableIsAssigned(parameter)) return false;
}
return true;
@@ -5,7 +5,6 @@ import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.ExpectedTypesProvider;
import com.intellij.codeInsight.completion.JavaCompletionUtil;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.lang.LanguageRefactoringSupport;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.diagnostic.Logger;
@@ -22,6 +21,7 @@ import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.*;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
import com.intellij.psi.impl.source.codeStyle.javadoc.CommentFormatter;
import com.intellij.psi.javadoc.PsiDocComment;
@@ -457,7 +457,7 @@ public final class CommonJavaRefactoringUtil {
public static boolean canBeDeclaredFinal(@NotNull PsiVariable variable) {
LOG.assertTrue(variable instanceof PsiLocalVariable || variable instanceof PsiParameter);
final boolean isReassigned = HighlightControlFlowUtil
final boolean isReassigned = ControlFlowUtil
.isReassigned(variable, new HashMap<>());
return !isReassigned;
}
@@ -435,6 +435,172 @@ public final class ControlFlowUtil {
return false;
}
private static boolean variableDefinitelyNotAssignedIn(@NotNull PsiVariable variable, @NotNull PsiElement context) {
ControlFlow flow = getControlFlow(context);
return flow == null || isVariableDefinitelyNotAssigned(variable, flow);
}
/**
* Kind of double initialization problem
*
* @see #findFinalVariableAlreadyInitializedProblem(PsiVariable, PsiReferenceExpression, Map)
*/
public enum DoubleInitializationProblem {
NO_PROBLEM,
/**
* Final variable is reassigned normally
*/
NORMAL,
/**
* Final variable is reassigned in loop
*/
IN_LOOP,
/**
* Double initialization of a final field due to chained constructor call
*/
IN_CONSTRUCTOR,
/**
* Double initialization of a final field in other initializer
*/
IN_INITIALIZER,
/**
* Double initialization of a final field in other field initializer
*/
IN_FIELD_INITIALIZER
}
/**
* @param variable final variable to check
* @param expression variable reference (write location)
* @param finalVarProblems a map to cache the results
* @return DoubleInitializationProblem object that depicts the problem kind
*/
public static @NotNull DoubleInitializationProblem findFinalVariableAlreadyInitializedProblem(@NotNull PsiVariable variable,
@NotNull PsiReferenceExpression expression,
@NotNull Map<PsiElement, Collection<VariableInfo>> finalVarProblems) {
if (!PsiUtil.isAccessedForWriting(expression)) return DoubleInitializationProblem.NO_PROBLEM;
PsiElement scope = variable instanceof PsiField
? variable.getParent()
: variable.getParent() == null ? null : variable.getParent().getParent();
PsiElement codeBlock = PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope);
if (codeBlock == null) return DoubleInitializationProblem.NO_PROBLEM;
Collection<VariableInfo> codeBlockProblems = getFinalVariableProblemsInBlock(finalVarProblems, codeBlock);
VariableInfo variableInfo = ContainerUtil.find(codeBlockProblems, vi -> vi.expression == expression);
if (variableInfo == null) {
if (variable instanceof PsiField) {
DoubleInitializationProblem problem = isFieldInitializedInAnotherMember((PsiField)variable, expression, codeBlock);
if (problem != null) {
return problem;
}
}
return DoubleInitializationProblem.NO_PROBLEM;
}
return variableInfo instanceof InitializedInLoopProblemInfo ? DoubleInitializationProblem.IN_LOOP : DoubleInitializationProblem.NORMAL;
}
private static DoubleInitializationProblem isFieldInitializedInAnotherMember(@NotNull PsiField field,
@NotNull PsiReferenceExpression expression,
@NotNull PsiElement codeBlock) {
PsiClass aClass = field.getContainingClass();
if (aClass == null) return null;
boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC);
PsiMember enclosingConstructorOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expression);
if (!isFieldStatic) {
// constructor that delegates to another constructor cannot assign final fields
if (enclosingConstructorOrInitializer instanceof PsiMethod) {
PsiMethodCallExpression chainedCall = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(
(PsiMethod)enclosingConstructorOrInitializer);
if (JavaPsiConstructorUtil.isChainedConstructorCall(chainedCall)) {
return DoubleInitializationProblem.IN_CONSTRUCTOR;
}
}
}
// field can get assigned in other field initializers or in class initializers
List<PsiMember> members = new ArrayList<>(Arrays.asList(aClass.getFields()));
if (enclosingConstructorOrInitializer != null
&& aClass.getManager().areElementsEquivalent(enclosingConstructorOrInitializer.getContainingClass(), aClass)) {
members.addAll(Arrays.asList(aClass.getInitializers()));
members.sort(PsiUtil.BY_POSITION);
}
for (PsiMember member : members) {
if (member == field) continue;
PsiElement context = member instanceof PsiField ? ((PsiField)member).getInitializer() : ((PsiClassInitializer)member).getBody();
if (context != null
&& member.hasModifierProperty(PsiModifier.STATIC) == isFieldStatic
&& !variableDefinitelyNotAssignedIn(field, context)) {
return context == codeBlock ? null :
member instanceof PsiField ? DoubleInitializationProblem.IN_FIELD_INITIALIZER :
DoubleInitializationProblem.IN_INITIALIZER;
}
}
return null;
}
private static @NotNull Collection<VariableInfo> getFinalVariableProblemsInBlock(
@NotNull Map<PsiElement, Collection<VariableInfo>> finalVarProblems, @NotNull PsiElement codeBlock) {
Collection<VariableInfo> codeBlockProblems =
finalVarProblems.computeIfAbsent(codeBlock, cb -> {
ControlFlow controlFlow = getControlFlow(codeBlock);
return controlFlow == null ? Collections.emptyList() : addReassignedInLoopProblems(getInitializedTwice(controlFlow), controlFlow);
});
return codeBlockProblems;
}
private static Collection<VariableInfo> addReassignedInLoopProblems(
@NotNull Collection<VariableInfo> codeBlockProblems,
@NotNull ControlFlow controlFlow) {
List<Instruction> instructions = controlFlow.getInstructions();
for (int index = 0; index < instructions.size(); index++) {
Instruction instruction = instructions.get(index);
if (instruction instanceof WriteVariableInstruction) {
PsiVariable variable = ((WriteVariableInstruction)instruction).variable;
if (variable instanceof PsiLocalVariable || variable instanceof PsiField) {
PsiElement anchor = controlFlow.getElement(index);
if (anchor instanceof PsiAssignmentExpression) {
PsiExpression ref = PsiUtil.skipParenthesizedExprDown(((PsiAssignmentExpression)anchor).getLExpression());
if (ref instanceof PsiReferenceExpression) {
VariableInfo varInfo = new InitializedInLoopProblemInfo(variable, ref);
if (!codeBlockProblems.contains(varInfo) && isInstructionReachable(controlFlow, index, index)) {
if (!(codeBlockProblems instanceof HashSet)) {
codeBlockProblems = new HashSet<>(codeBlockProblems);
}
codeBlockProblems.add(varInfo);
}
}
}
}
}
}
return codeBlockProblems;
}
/**
* @param variable variable to check (local variable or parameter)
* @param finalVarProblems cache map to reuse information
* @return true if the variable is reassigned
*/
public static boolean isReassigned(@NotNull PsiVariable variable, @NotNull Map<PsiElement, Collection<VariableInfo>> finalVarProblems) {
if (variable instanceof PsiLocalVariable) {
PsiElement parent = variable.getParent();
if (parent == null) return false;
PsiElement declarationScope = parent.getParent();
if (declarationScope == null) return false;
Collection<VariableInfo> codeBlockProblems = getFinalVariableProblemsInBlock(finalVarProblems, declarationScope);
return codeBlockProblems.contains(new VariableInfo(variable, null));
}
if (variable instanceof PsiParameter) {
PsiParameter parameter = (PsiParameter)variable;
return variableIsAssigned(parameter, parameter.getDeclarationScope());
}
return false;
}
private static class SSAInstructionState {
private final int myWriteCount;
private final int myInstructionIdx;
@@ -2303,6 +2469,16 @@ public final class ControlFlowUtil {
}
}
/**
* A kind of final variable problem returned from {@link #getFinalVariableProblemsInBlock(Map, PsiElement)}
* which designates a final variable which is initialized in a loop.
*/
private static class InitializedInLoopProblemInfo extends VariableInfo {
InitializedInLoopProblemInfo(@NotNull PsiVariable variable, @Nullable PsiElement expression) {
super(variable, expression);
}
}
private static void merge(int offset, CopyOnWriteList source, CopyOnWriteList @NotNull [] target) {
if (source != null) {
CopyOnWriteList existing = target[offset];
@@ -5,7 +5,7 @@ class Foo {
final int k;
final int ff = 5;
Foo(int i) {
<error descr="Variable 'k' might already have been assigned to">k</error> =1;
<error descr="Final field 'k' is already initialized in a class initializer">k</error> =1;
}
{
k=0;
@@ -21,7 +21,7 @@ class c2 {
int i = k;
}
static {
<error descr="Variable 'k' might already have been assigned to">k</error> =1;
<error descr="Final field 'k' is already initialized in a class initializer">k</error> =1;
}
}
@@ -34,7 +34,7 @@ class c3 {
int i = k;
}
{
<error descr="Variable 'k' might already have been assigned to">k</error> =1;
<error descr="Final field 'k' is already initialized in a class initializer">k</error> =1;
}
}
@@ -45,11 +45,11 @@ class c4 {
}
c4(int i) {
if (false)
<error descr="Variable 'k' might already have been assigned to">k</error> =1;
<error descr="Final field 'k' is already initialized in a class initializer">k</error> =1;
}
c4() {
this(0);
<error descr="Variable 'k' might already have been assigned to">k</error> =1;
<error descr="Cannot assign final field 'k' after chained constructor call">k</error> =1;
}
}
// redirected ctrs
@@ -60,7 +60,7 @@ class c5 {
}
c5() {
this(0);
<error descr="Variable 'k' might already have been assigned to">k</error> =1;
<error descr="Cannot assign final field 'k' after chained constructor call">k</error> =1;
}
@@ -68,11 +68,11 @@ class c5 {
}
c5(int i, int j) {
this('c');
<error descr="Variable 'k' might already have been assigned to">k</error> = 5;
<error descr="Cannot assign final field 'k' after chained constructor call">k</error> = 5;
}
c5(String s) {
this(0,0);
<error descr="Variable 'k' might already have been assigned to">k</error> =1;
<error descr="Cannot assign final field 'k' after chained constructor call">k</error> =1;
}
}
@@ -113,6 +113,6 @@ class c7 {
}
{
<error descr="Variable 'y' might already have been assigned to">y</error> = ""+i;
<error descr="Final field 'y' is already initialized in a class initializer">y</error> = ""+i;
}
}
@@ -40,6 +40,6 @@ class X {
X() {
<error descr="Recursive constructor call">this()</error>;
<error descr="Variable 'value' might already have been assigned to">value</error> = 1;
<error descr="Cannot assign final field 'value' after chained constructor call">value</error> = 1;
}
}
@@ -3,10 +3,10 @@ class A {
c = "c";
}
static String b = a = "";
static String d = <error descr="Variable 'c' might already have been assigned to">c</error> = "";
static String d = <error descr="Final field 'c' is already initialized in a class initializer">c</error> = "";
static {
<error descr="Variable 'a' might already have been assigned to">a</error> = "";
<error descr="Final field 'a' is already initialized in another field initializer">a</error> = "";
}
static final String a;
static final String c;
@@ -16,10 +16,10 @@ class B {
c = "c";
}
String b = a = "";
String d = <error descr="Variable 'c' might already have been assigned to">c</error> = "";
String d = <error descr="Final field 'c' is already initialized in a class initializer">c</error> = "";
{
<error descr="Variable 'a' might already have been assigned to">a</error> = "";
<error descr="Final field 'a' is already initialized in another field initializer">a</error> = "";
}
final String a;
final String c;
@@ -35,7 +35,7 @@ record VarArgMismatch2(int[] x) {
record Delegate(int x) {
public Delegate(int x) {
<error descr="Canonical constructor cannot delegate to another constructor">this()</error>;
<error descr="Variable 'x' might already have been assigned to">this.x</error> = 0;
<error descr="Cannot assign final field 'x' after chained constructor call">this.x</error> = 0;
}
public <error descr="Non-canonical record constructor must delegate to another constructor">Delegate</error>() {
@@ -67,7 +67,7 @@ record ImplicitCanonicalConstructor(String s) {
record AssignmentInNonCanonical(int x, int y, long depth) {
public AssignmentInNonCanonical(int x, int y) {
this(x, y, 10);
<error descr="Variable 'x' might already have been assigned to">this.x</error> = x;
<error descr="Cannot assign final field 'x' after chained constructor call">this.x</error> = x;
}
void method() {