mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-highlighting] More control-flow-related stuff migrated
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only) GitOrigin-RevId: 3c292fdf4869e6d13c16fabbf42e2055ea54f724
This commit is contained in:
committed by
intellij-monorepo-bot
parent
69cdc7a345
commit
cc643a5ae4
@@ -378,6 +378,8 @@ variable.must.be.final=Variable ''{0}'' is accessed from within inner class, nee
|
||||
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
|
||||
variable.not.initialized=Variable ''{0}'' might not have been initialized
|
||||
field.not.initialized=Field ''{0}'' might not have been initialized
|
||||
|
||||
instanceof.type.parameter=Class or array expected
|
||||
instanceof.illegal.generic.type=Illegal generic type for instanceof
|
||||
|
||||
+23
@@ -11,13 +11,22 @@ import com.intellij.psi.impl.light.LightRecordCanonicalConstructor;
|
||||
import com.intellij.psi.util.JavaPsiRecordUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.util.BitUtil;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
final class ControlFlowChecker {
|
||||
private final @NotNull JavaErrorVisitor myVisitor;
|
||||
// map codeBlock->List of PsiReferenceExpression of uninitialized final variables
|
||||
private final Map<PsiElement, Collection<PsiReferenceExpression>> myUninitializedVarProblems = new HashMap<>();
|
||||
// map codeBlock->List of PsiReferenceExpression of extra initialization of final variable
|
||||
private final Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> myFinalVarProblems = new HashMap<>();
|
||||
|
||||
ControlFlowChecker(@NotNull JavaErrorVisitor visitor) { myVisitor = visitor; }
|
||||
|
||||
@@ -123,6 +132,20 @@ final class ControlFlowChecker {
|
||||
}
|
||||
}
|
||||
|
||||
void checkFinalFieldInitialized(@NotNull PsiField field) {
|
||||
if (!field.hasModifierProperty(PsiModifier.FINAL)) return;
|
||||
if (ControlFlowUtil.isFieldInitializedAfterObjectConstruction(field)) return;
|
||||
if (PsiUtilCore.hasErrorElementChild(field)) return;
|
||||
myVisitor.report(JavaErrorKinds.FIELD_NOT_INITIALIZED.create(field));
|
||||
}
|
||||
|
||||
void checkVariableInitializedBeforeUsage(@NotNull PsiReferenceExpression expression, @NotNull PsiVariable variable) {
|
||||
if (ControlFlowUtil.isInitializedBeforeUsage(expression, variable, myUninitializedVarProblems, false)) {
|
||||
return;
|
||||
}
|
||||
myVisitor.report(JavaErrorKinds.VARIABLE_NOT_INITIALIZED.create(expression, variable));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return field that has initializer with this element as subexpression or null if not found
|
||||
*/
|
||||
|
||||
+3
-1
@@ -506,6 +506,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
public void visitField(@NotNull PsiField field) {
|
||||
super.visitField(field);
|
||||
if (!hasErrorResults()) myClassChecker.checkIllegalInstanceMemberInRecord(field);
|
||||
if (!hasErrorResults()) myControlFlowChecker.checkFinalFieldInitialized(field);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -701,10 +702,11 @@ final class JavaErrorVisitor extends JavaElementVisitor {
|
||||
PsiElement resolved = result.getElement();
|
||||
PsiElement parent = expression.getParent();
|
||||
PsiExpression qualifierExpression = expression.getQualifierExpression();
|
||||
if (resolved instanceof PsiVariable && resolved.getContainingFile() == expression.getContainingFile()) {
|
||||
if (resolved instanceof PsiVariable variable && resolved.getContainingFile() == expression.getContainingFile()) {
|
||||
if (!hasErrorResults() && resolved instanceof PsiLocalVariable localVariable) {
|
||||
myExpressionChecker.checkVarTypeSelfReferencing(localVariable, expression);
|
||||
}
|
||||
if (!hasErrorResults()) myControlFlowChecker.checkVariableInitializedBeforeUsage(expression, variable);
|
||||
}
|
||||
if (parent instanceof PsiMethodCallExpression methodCallExpression &&
|
||||
methodCallExpression.getMethodExpression() == expression &&
|
||||
|
||||
+7
@@ -1236,6 +1236,13 @@ public final class JavaErrorKinds {
|
||||
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 Simple<PsiField> FIELD_NOT_INITIALIZED =
|
||||
error(PsiField.class, "field.not.initialized")
|
||||
.withRange(JavaErrorFormatUtil::getFieldDeclarationTextRange)
|
||||
.withRawDescription(var -> message("field.not.initialized", var.getName()));
|
||||
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<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()));
|
||||
|
||||
+14
-358
@@ -4,23 +4,13 @@ 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.daemon.impl.quickfix.QuickFixAction;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.QuickFixFactory;
|
||||
import com.intellij.lang.jvm.JvmModifier;
|
||||
import com.intellij.lang.jvm.actions.ChangeModifierRequest;
|
||||
import com.intellij.lang.jvm.actions.JvmElementActionFactories;
|
||||
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.psi.*;
|
||||
import com.intellij.psi.augment.PsiAugmentProvider;
|
||||
import com.intellij.psi.controlFlow.*;
|
||||
import com.intellij.psi.impl.light.LightRecordField;
|
||||
import com.intellij.psi.search.LocalSearchScope;
|
||||
import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.JavaPsiConstructorUtil;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
@@ -28,7 +18,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public final class HighlightControlFlowUtil {
|
||||
|
||||
@@ -46,106 +35,25 @@ public final class HighlightControlFlowUtil {
|
||||
return ControlFlowFactory.getControlFlowNoConstantEvaluate(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link ControlFlowUtil#variableDefinitelyAssignedIn(PsiVariable, PsiElement)}
|
||||
*/
|
||||
@Deprecated
|
||||
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 isFieldInitializedAfterObjectConstruction(@NotNull PsiField field) {
|
||||
if (field.hasInitializer()) return true;
|
||||
boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC);
|
||||
PsiClass aClass = field.getContainingClass();
|
||||
if (aClass != null) {
|
||||
// field might be assigned in the other field initializers
|
||||
if (isFieldInitializedInOtherFieldInitializer(aClass, field, isFieldStatic, Predicates.alwaysTrue())) return true;
|
||||
}
|
||||
PsiClassInitializer[] initializers;
|
||||
if (aClass != null) {
|
||||
initializers = aClass.getInitializers();
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
if (isFieldInitializedInClassInitializer(field, isFieldStatic, initializers)) return true;
|
||||
if (isFieldStatic) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
// instance field should be initialized at the end of each constructor
|
||||
PsiMethod[] constructors = aClass.getConstructors();
|
||||
|
||||
if (constructors.length == 0) return false;
|
||||
nextConstructor:
|
||||
for (PsiMethod constructor : constructors) {
|
||||
PsiCodeBlock ctrBody = constructor.getBody();
|
||||
if (ctrBody == null) return false;
|
||||
for (PsiMethod redirectedConstructor : JavaPsiConstructorUtil.getChainedConstructors(constructor)) {
|
||||
PsiCodeBlock body = redirectedConstructor.getBody();
|
||||
if (body != null && variableDefinitelyAssignedIn(field, body, true)) continue nextConstructor;
|
||||
}
|
||||
if (!ctrBody.isValid() || variableDefinitelyAssignedIn(field, ctrBody, true)) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isFieldInitializedInClassInitializer(@NotNull PsiField field,
|
||||
boolean isFieldStatic,
|
||||
PsiClassInitializer @NotNull [] initializers) {
|
||||
return ContainerUtil.find(initializers, initializer -> initializer.hasModifierProperty(PsiModifier.STATIC) == isFieldStatic
|
||||
&& variableDefinitelyAssignedIn(field, initializer.getBody(), true)) != null;
|
||||
}
|
||||
|
||||
private static boolean isFieldInitializedInOtherFieldInitializer(@NotNull PsiClass aClass,
|
||||
@NotNull PsiField field,
|
||||
boolean fieldStatic,
|
||||
@NotNull Predicate<? super PsiField> condition) {
|
||||
for (PsiField psiField : aClass.getFields()) {
|
||||
if (psiField != field
|
||||
&& psiField.hasModifierProperty(PsiModifier.STATIC) == fieldStatic
|
||||
&& variableDefinitelyAssignedIn(field, psiField, true)
|
||||
&& condition.test(psiField)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isAssigned(@NotNull PsiParameter parameter) {
|
||||
ParamWriteProcessor processor = new ParamWriteProcessor();
|
||||
ReferencesSearch.search(parameter, new LocalSearchScope(parameter.getDeclarationScope()), true).forEach(processor);
|
||||
return processor.isWriteRefFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return field that has initializer with this element as subexpression or null if not found
|
||||
*/
|
||||
private static PsiField findEnclosingFieldInitializer(@NotNull PsiElement entry) {
|
||||
PsiElement element = entry;
|
||||
while (element != null) {
|
||||
PsiElement parent = element.getParent();
|
||||
if (parent instanceof PsiField field) {
|
||||
if (element == field.getInitializer()) return field;
|
||||
if (field instanceof PsiEnumConstant enumConstant && element == enumConstant.getArgumentList()) return field;
|
||||
}
|
||||
if (element instanceof PsiClass || element instanceof PsiMethod) return null;
|
||||
element = parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static @NotNull TextRange getFixRange(@NotNull PsiElement element) {
|
||||
PsiElement nextSibling = element.getNextSibling();
|
||||
TextRange range = element.getTextRange();
|
||||
if (PsiUtil.isJavaToken(nextSibling, JavaTokenType.SEMICOLON)) {
|
||||
return range.grown(1);
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
private static class ParamWriteProcessor implements Processor<PsiReference> {
|
||||
private volatile boolean myIsWriteRefFound;
|
||||
@Override
|
||||
@@ -163,25 +71,6 @@ public final class HighlightControlFlowUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* see JLS chapter 16
|
||||
* @return true if variable assigned (maybe more than once)
|
||||
*/
|
||||
public static boolean variableDefinitelyAssignedIn(@NotNull PsiVariable variable, @NotNull PsiElement context) {
|
||||
return variableDefinitelyAssignedIn(variable, context, false);
|
||||
}
|
||||
|
||||
private static boolean variableDefinitelyAssignedIn(@NotNull PsiVariable variable,
|
||||
@NotNull PsiElement context,
|
||||
boolean resultOnIncompleteCode) {
|
||||
try {
|
||||
return ControlFlowUtil.isVariableDefinitelyAssigned(variable, getControlFlow(context));
|
||||
}
|
||||
catch (AnalysisCanceledException e) {
|
||||
return resultOnIncompleteCode;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean variableDefinitelyNotAssignedIn(@NotNull PsiVariable variable, @NotNull PsiElement context) {
|
||||
try {
|
||||
return ControlFlowUtil.isVariableDefinitelyNotAssigned(variable, getControlFlow(context));
|
||||
@@ -191,244 +80,11 @@ public final class HighlightControlFlowUtil {
|
||||
}
|
||||
}
|
||||
|
||||
static HighlightInfo.Builder checkFinalFieldInitialized(@NotNull PsiField field) {
|
||||
if (!field.hasModifierProperty(PsiModifier.FINAL)) return null;
|
||||
if (isFieldInitializedAfterObjectConstruction(field)) return null;
|
||||
if (PsiUtilCore.hasErrorElementChild(field)) return null;
|
||||
String description = JavaErrorBundle.message("variable.not.initialized", field.getName());
|
||||
TextRange range = HighlightNamesUtil.getFieldDeclarationTextRange(field);
|
||||
HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(description);
|
||||
IntentionAction action3 = getQuickFixFactory().createCreateConstructorParameterFromFieldFix(field);
|
||||
builder.registerFix(action3, null, null, getFixRange(field), null);
|
||||
IntentionAction action2 = getQuickFixFactory().createInitializeFinalFieldInConstructorFix(field);
|
||||
builder.registerFix(action2, null, null, getFixRange(field), null);
|
||||
IntentionAction action1 = getQuickFixFactory().createAddVariableInitializerFix(field);
|
||||
builder.registerFix(action1, null, null, null, null);
|
||||
PsiClass containingClass = field.getContainingClass();
|
||||
if (containingClass != null && !containingClass.isInterface()) {
|
||||
IntentionAction action = getQuickFixFactory().createModifierListFix(field, PsiModifier.FINAL, false, false);
|
||||
builder.registerFix(action, null, null, null, null);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
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 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 = 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 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 true;
|
||||
}
|
||||
PsiElement parent = topBlock.getParent();
|
||||
// access to final fields from inner classes always allowed
|
||||
if (inInnerClass(expression, field.getContainingClass())) return true;
|
||||
PsiCodeBlock block;
|
||||
PsiClass aClass;
|
||||
if (parent instanceof PsiMethod constructor) {
|
||||
if (!constructor.getManager().areElementsEquivalent(constructor.getContainingClass(), field.getContainingClass())) return true;
|
||||
// static variables already initialized in class initializers
|
||||
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 true;
|
||||
PsiCodeBlock body = redirectedConstructor.getBody();
|
||||
if (body != null && variableDefinitelyAssignedIn(variable, body, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
block = constructor.getBody();
|
||||
aClass = constructor.getContainingClass();
|
||||
}
|
||||
else if (parent instanceof PsiClassInitializer classInitializer) {
|
||||
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 true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// field reference outside code block
|
||||
// check variable initialized before its usage
|
||||
aClass = field.getContainingClass();
|
||||
PsiField anotherField = PsiTreeUtil.getTopmostParentOfType(expression, PsiField.class);
|
||||
if (aClass == null ||
|
||||
isFieldInitializedInOtherFieldInitializer(aClass, field, field.hasModifierProperty(PsiModifier.STATIC),
|
||||
f -> f != anotherField && startOffset > f.getTextOffset())) {
|
||||
return true;
|
||||
}
|
||||
if (anotherField != null
|
||||
&& !anotherField.hasModifierProperty(PsiModifier.STATIC)
|
||||
&& field.hasModifierProperty(PsiModifier.STATIC)
|
||||
&& isFieldInitializedInClassInitializer(field, true, aClass.getInitializers())) {
|
||||
return true;
|
||||
}
|
||||
if (anotherField != null && anotherField.hasInitializer() && !PsiAugmentProvider.canTrustFieldInitializer(anotherField)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int offset = startOffset;
|
||||
if (anotherField != null && anotherField.getContainingClass() == aClass && !field.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
offset = 0;
|
||||
}
|
||||
block = null;
|
||||
// initializers will be checked later
|
||||
for (PsiMethod constructor : aClass.getConstructors()) {
|
||||
// variable must be initialized before its usage
|
||||
if (offset < constructor.getTextRange().getStartOffset()) continue;
|
||||
PsiCodeBlock body = constructor.getBody();
|
||||
if (body != null && variableDefinitelyAssignedIn(variable, body)) {
|
||||
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 (offset < redirectedConstructor.getTextRange().getStartOffset()) continue;
|
||||
PsiCodeBlock redirectedBody = redirectedConstructor.getBody();
|
||||
if (redirectedBody != null && variableDefinitelyAssignedIn(variable, redirectedBody)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (aClass != null) {
|
||||
// field may be initialized in class initializer
|
||||
for (PsiClassInitializer initializer : aClass.getInitializers()) {
|
||||
PsiCodeBlock body = initializer.getBody();
|
||||
if (body == block) break;
|
||||
// variable referenced in initializer must be initialized in initializer preceding assignment
|
||||
// variable referenced in field initializer or in class initializer
|
||||
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 true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Collection<PsiReferenceExpression> codeBlockProblems = uninitializedVarProblems.get(topBlock);
|
||||
if (codeBlockProblems == null) {
|
||||
try {
|
||||
ControlFlow controlFlow = getControlFlow(topBlock);
|
||||
codeBlockProblems = ControlFlowUtil.getReadBeforeWriteLocals(controlFlow);
|
||||
}
|
||||
catch (AnalysisCanceledException | IndexNotReadyException e) {
|
||||
codeBlockProblems = Collections.emptyList();
|
||||
}
|
||||
uninitializedVarProblems.put(topBlock, codeBlockProblems);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private static boolean inInnerClass(@NotNull PsiElement psiElement, @Nullable PsiClass containingClass) {
|
||||
for (PsiElement element = psiElement; element != null; element = element.getParent()) {
|
||||
if (element instanceof PsiClass aClass) {
|
||||
boolean innerClass = !psiElement.getManager().areElementsEquivalent(element, containingClass);
|
||||
if (innerClass) {
|
||||
if (element instanceof PsiAnonymousClass anonymous) {
|
||||
if (PsiTreeUtil.isAncestor(anonymous.getArgumentList(), psiElement, false)) {
|
||||
continue;
|
||||
}
|
||||
return !insideClassInitialization(containingClass, aClass);
|
||||
}
|
||||
PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(psiElement, PsiLambdaExpression.class);
|
||||
return lambdaExpression == null || !insideClassInitialization(containingClass, aClass);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean insideClassInitialization(@Nullable PsiClass containingClass, PsiClass aClass) {
|
||||
PsiMember member = aClass;
|
||||
while (member != null) {
|
||||
if (member.getContainingClass() == containingClass) {
|
||||
return member instanceof PsiField ||
|
||||
member instanceof PsiMethod method && method.isConstructor() ||
|
||||
member instanceof PsiClassInitializer;
|
||||
}
|
||||
member = PsiTreeUtil.getParentOfType(member, PsiMember.class, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
|
||||
-16
@@ -67,8 +67,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
|
||||
private PreviewFeatureUtil.PreviewFeatureVisitor myPreviewFeatureVisitor;
|
||||
|
||||
// map codeBlock->List of PsiReferenceExpression of uninitialized final variables
|
||||
private final Map<PsiElement, Collection<PsiReferenceExpression>> myUninitializedVarProblems = new HashMap<>();
|
||||
// map codeBlock->List of PsiReferenceExpression of extra initialization of final variable
|
||||
private final Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> myFinalVarProblems = new HashMap<>();
|
||||
|
||||
@@ -159,7 +157,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
}
|
||||
}
|
||||
finally {
|
||||
myUninitializedVarProblems.clear();
|
||||
myFinalVarProblems.clear();
|
||||
myJavaModule = null;
|
||||
myFile = null;
|
||||
@@ -274,12 +271,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
if (!hasErrorResults()) GenericsHighlightUtil.checkTypeParameterOverrideEquivalentMethods(aClass, myLanguageLevel, myErrorSink, myOverrideEquivalentMethodsVisitedClasses, myOverrideEquivalentMethodsErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitField(@NotNull PsiField field) {
|
||||
super.visitField(field);
|
||||
if (!hasErrorResults()) add(HighlightControlFlowUtil.checkFinalFieldInitialized(field));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitImportStaticStatement(@NotNull PsiImportStaticStatement statement) {
|
||||
visitElement(statement);
|
||||
@@ -491,13 +482,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
|
||||
add(HighlightControlFlowUtil.checkFinalVariableMightAlreadyHaveBeenAssignedTo(variable, expression, myFinalVarProblems));
|
||||
}
|
||||
}
|
||||
if (!hasErrorResults()) {
|
||||
try {
|
||||
add(HighlightControlFlowUtil.checkVariableInitializedBeforeUsage(expression, variable, myUninitializedVarProblems));
|
||||
}
|
||||
catch (IndexNotReadyException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasErrorResults()) add(HighlightUtil.checkClassReferenceAfterQualifier(expression, resolved));
|
||||
|
||||
+26
@@ -28,6 +28,7 @@ import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.VariableKind;
|
||||
import com.intellij.psi.controlFlow.ControlFlowUtil;
|
||||
import com.intellij.psi.impl.light.LightRecordField;
|
||||
import com.intellij.psi.impl.light.LightRecordMethod;
|
||||
import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
|
||||
import com.intellij.psi.infos.MethodCandidateInfo;
|
||||
@@ -387,6 +388,31 @@ 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()));
|
||||
fixes(FIELD_NOT_INITIALIZED, (error, sink) -> {
|
||||
PsiField field = error.psi();
|
||||
sink.accept(myFactory.createCreateConstructorParameterFromFieldFix(field));
|
||||
sink.accept(myFactory.createInitializeFinalFieldInConstructorFix(field));
|
||||
sink.accept(myFactory.createAddVariableInitializerFix(field));
|
||||
PsiClass containingClass = field.getContainingClass();
|
||||
if (containingClass != null && !containingClass.isInterface()) {
|
||||
sink.accept(removeModifierFix(field, PsiModifier.FINAL));
|
||||
}
|
||||
});
|
||||
fixes(VARIABLE_NOT_INITIALIZED, (error, sink) -> {
|
||||
PsiVariable variable = error.context();
|
||||
if (!(variable instanceof LightRecordField)) {
|
||||
sink.accept(myFactory.createAddVariableInitializerFix(variable));
|
||||
}
|
||||
if (variable instanceof PsiLocalVariable) {
|
||||
PsiElement topBlock = PsiUtil.getVariableCodeBlock(variable, null);
|
||||
if (topBlock != null) {
|
||||
sink.accept(HighlightFixUtil.createInsertSwitchDefaultFix(variable, topBlock, error.psi()));
|
||||
}
|
||||
}
|
||||
if (variable instanceof PsiField field) {
|
||||
sink.accept(removeModifierFix(field, PsiModifier.FINAL));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createExpressionFixes() {
|
||||
|
||||
@@ -23,7 +23,7 @@ public final class FinalUtils {
|
||||
// parameters have an implicit initializer
|
||||
return !VariableAccessUtils.variableIsAssigned(variable);
|
||||
}
|
||||
if (variable instanceof PsiField && !HighlightControlFlowUtil.isFieldInitializedAfterObjectConstruction((PsiField)variable)) {
|
||||
if (variable instanceof PsiField && !ControlFlowUtil.isFieldInitializedAfterObjectConstruction((PsiField)variable)) {
|
||||
return false;
|
||||
}
|
||||
return checkIfElementViolatesFinality(variable);
|
||||
@@ -57,7 +57,7 @@ public final class FinalUtils {
|
||||
Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems) {
|
||||
if (!(e instanceof PsiReferenceExpression ref)) return true;
|
||||
if (!ref.isReferenceTo(variable)) return true;
|
||||
if (!HighlightControlFlowUtil.isInitializedBeforeUsage(
|
||||
if (!ControlFlowUtil.isInitializedBeforeUsage(
|
||||
ref, variable, uninitializedVarProblems, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,7 +4,6 @@ package com.intellij.codeInspection.classCanBeRecord;
|
||||
import com.intellij.codeInsight.AnnotationTargetUtil;
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.ExceptionUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
|
||||
import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.codeInspection.util.IntentionFamilyName;
|
||||
@@ -12,6 +11,7 @@ import com.intellij.java.JavaBundle;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.PsiAnnotation.TargetType;
|
||||
import com.intellij.psi.controlFlow.ControlFlowUtil;
|
||||
import com.intellij.psi.search.searches.ClassInheritorsSearch;
|
||||
import com.intellij.psi.util.JavaPsiRecordUtil;
|
||||
import com.intellij.psi.util.PropertyUtil;
|
||||
@@ -343,7 +343,7 @@ public class ConvertToRecordFix extends InspectionGadgetsFix {
|
||||
myCanonical = false;
|
||||
return;
|
||||
}
|
||||
if (!HighlightControlFlowUtil.variableDefinitelyAssignedIn(instanceField, ctorBody)) {
|
||||
if (!ControlFlowUtil.variableDefinitelyAssignedIn(instanceField, ctorBody)) {
|
||||
myCanonical = false;
|
||||
return;
|
||||
}
|
||||
|
||||
+2
-3
@@ -2,7 +2,6 @@
|
||||
package com.intellij.codeInspection.defUse;
|
||||
|
||||
import com.intellij.codeInsight.ExpressionUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.codeInspection.dataFlow.java.ControlFlowAnalyzer;
|
||||
import com.intellij.codeInspection.dataFlow.java.anchor.JavaExpressionAnchor;
|
||||
@@ -179,7 +178,7 @@ public final class DefUseInspection extends AbstractBaseJavaLocalInspectionTool
|
||||
if (classInitializer.hasModifierProperty(PsiModifier.STATIC) == isStatic) {
|
||||
final List<PsiAssignmentExpression> assignments = collectAssignments(field, classInitializer);
|
||||
if (!assignments.isEmpty()) {
|
||||
boolean isDefinitely = HighlightControlFlowUtil.variableDefinitelyAssignedIn(field, classInitializer.getBody());
|
||||
boolean isDefinitely = ControlFlowUtil.variableDefinitelyAssignedIn(field, classInitializer.getBody());
|
||||
if (isDefinitely) {
|
||||
try {
|
||||
ControlFlow flow = ControlFlowFactory.getControlFlowNoConstantEvaluate(classInitializer.getBody());
|
||||
@@ -229,7 +228,7 @@ public final class DefUseInspection extends AbstractBaseJavaLocalInspectionTool
|
||||
for (PsiMethod constructor : constructors) {
|
||||
if (!JavaPsiConstructorUtil.getChainedConstructors(constructor).isEmpty()) continue;
|
||||
final PsiCodeBlock body = constructor.getBody();
|
||||
if (body == null || !HighlightControlFlowUtil.variableDefinitelyAssignedIn(field, body)) {
|
||||
if (body == null || !ControlFlowUtil.variableDefinitelyAssignedIn(field, body)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
|
||||
+2
-2
@@ -2,7 +2,6 @@
|
||||
package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
|
||||
import com.intellij.codeInsight.intention.PriorityAction;
|
||||
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
|
||||
import com.intellij.codeInspection.util.IntentionName;
|
||||
@@ -14,6 +13,7 @@ import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.controlFlow.ControlFlowUtil;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -170,7 +170,7 @@ public final class BringVariableIntoScopeFix implements ModCommandAction {
|
||||
outOfScopeVariable.delete();
|
||||
}
|
||||
|
||||
if (!HighlightControlFlowUtil.isInitializedBeforeUsage(reference, addedVar, new HashMap<>(), false)) {
|
||||
if (!ControlFlowUtil.isInitializedBeforeUsage(reference, addedVar, new HashMap<>(), false)) {
|
||||
initialize(addedVar);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -5,7 +5,6 @@ import com.intellij.application.options.CodeStyle;
|
||||
import com.intellij.codeInsight.AnnotationTargetUtil;
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
|
||||
import com.intellij.codeInsight.generation.PsiFieldMember;
|
||||
import com.intellij.codeInsight.generation.PsiMethodMember;
|
||||
import com.intellij.codeInsight.intention.impl.AssignFieldFromParameterAction;
|
||||
@@ -16,6 +15,7 @@ import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.*;
|
||||
import com.intellij.psi.controlFlow.ControlFlowUtil;
|
||||
import com.intellij.util.CommonJavaRefactoringUtil;
|
||||
import com.intellij.util.JavaPsiConstructorUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
@@ -103,7 +103,7 @@ public class CreateConstructorParameterFromFieldFix extends PsiBasedModCommandAc
|
||||
if (field == startField ||
|
||||
(!field.hasModifierProperty(PsiModifier.STATIC) &&
|
||||
field.hasModifierProperty(PsiModifier.FINAL) &&
|
||||
!HighlightControlFlowUtil.isFieldInitializedAfterObjectConstruction(field) &&
|
||||
!ControlFlowUtil.isFieldInitializedAfterObjectConstruction(field) &&
|
||||
(constructors.isEmpty() || ContainerUtil.exists(constructors, ctr -> !isFieldAssignedInConstructor(field, ctr))))) {
|
||||
fields.add(field);
|
||||
}
|
||||
|
||||
+10
-2
@@ -2,7 +2,6 @@
|
||||
package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.QuickFixActionRegistrar;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.impl.PriorityIntentionActionWrapper;
|
||||
import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider;
|
||||
@@ -51,7 +50,7 @@ public class DefaultQuickFixProvider extends UnresolvedReferenceQuickFixProvider
|
||||
MoveClassToModuleFix.registerFixes(registrar, ref);
|
||||
|
||||
if (ref instanceof PsiReferenceExpression refExpr) {
|
||||
TextRange fixRange = HighlightControlFlowUtil.getFixRange(ref);
|
||||
TextRange fixRange = getFixRange(ref);
|
||||
registrar.register(fixRange, new RenameWrongRefFix(refExpr), null);
|
||||
PsiExpression qualifier = refExpr.getQualifierExpression();
|
||||
if (qualifier != null) {
|
||||
@@ -186,4 +185,13 @@ public class DefaultQuickFixProvider extends UnresolvedReferenceQuickFixProvider
|
||||
public @NotNull Class<PsiJavaCodeReferenceElement> getReferenceClass() {
|
||||
return PsiJavaCodeReferenceElement.class;
|
||||
}
|
||||
|
||||
private static @NotNull TextRange getFixRange(@NotNull PsiElement element) {
|
||||
PsiElement nextSibling = element.getNextSibling();
|
||||
TextRange range = element.getTextRange();
|
||||
if (PsiUtil.isJavaToken(nextSibling, JavaTokenType.SEMICOLON)) {
|
||||
return range.grown(1);
|
||||
}
|
||||
return range;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -317,7 +317,7 @@ public class VariableAccessFromInnerClassFix implements IntentionAction {
|
||||
Map<PsiElement, Collection<ControlFlowUtil.VariableInfo>> finalVarProblems = new HashMap<>();
|
||||
for (PsiReferenceExpression expression : references) {
|
||||
if (ControlFlowUtil.isVariableAssignedInLoop(expression, variable)) return false;
|
||||
if (!HighlightControlFlowUtil.isInitializedBeforeUsage(
|
||||
if (!ControlFlowUtil.isInitializedBeforeUsage(
|
||||
expression, variable, uninitializedVarProblems, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,7 +1,6 @@
|
||||
// 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.intention.impl;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
|
||||
import com.intellij.codeInsight.generation.RecordConstructorMember;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.modcommand.ActionContext;
|
||||
@@ -9,6 +8,7 @@ import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcommand.Presentation;
|
||||
import com.intellij.modcommand.PsiUpdateModCommandAction;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.controlFlow.ControlFlowUtil;
|
||||
import com.intellij.psi.util.JavaPsiRecordUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
@@ -60,7 +60,7 @@ public final class ConvertCompactConstructorToCanonicalAction extends PsiUpdateM
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(compactConstructor.getProject());
|
||||
for (PsiRecordComponent component : recordClass.getRecordComponents()) {
|
||||
PsiField field = JavaPsiRecordUtil.getFieldForComponent(component);
|
||||
if (field != null && !HighlightControlFlowUtil.variableDefinitelyAssignedIn(field, body)) {
|
||||
if (field != null && !ControlFlowUtil.variableDefinitelyAssignedIn(field, body)) {
|
||||
body.add(factory.createStatementFromText("this." + field.getName() + "=" + field.getName() + ";", body));
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -6,7 +6,6 @@ import com.intellij.codeInsight.NullabilityAnnotationInfo;
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.codeInsight.TestFrameworks;
|
||||
import com.intellij.codeInsight.daemon.impl.UnusedSymbolUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.AddVariableInitializerFix;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.InitializeFinalFieldInConstructorFix;
|
||||
import com.intellij.codeInsight.intention.QuickFixFactory;
|
||||
@@ -19,6 +18,7 @@ import com.intellij.codeInspection.util.InspectionMessage;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.controlFlow.ControlFlowUtil;
|
||||
import com.intellij.util.JavaPsiConstructorUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
@@ -56,7 +56,7 @@ public class NotNullFieldNotInitializedInspection extends AbstractBaseJavaLocalI
|
||||
NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(field);
|
||||
if (info == null || info.getNullability() != Nullability.NOT_NULL) return;
|
||||
|
||||
if (HighlightControlFlowUtil.isFieldInitializedAfterObjectConstruction(field) ||
|
||||
if (ControlFlowUtil.isFieldInitializedAfterObjectConstruction(field) ||
|
||||
isWrittenIndirectly(field)) {
|
||||
return;
|
||||
}
|
||||
@@ -124,7 +124,7 @@ public class NotNullFieldNotInitializedInspection extends AbstractBaseJavaLocalI
|
||||
if (target != null && !target.hasModifierProperty(PsiModifier.STATIC) &&
|
||||
target.getContainingClass() == constructor.getContainingClass() && !target.isConstructor()) {
|
||||
PsiCodeBlock targetBody = target.getBody();
|
||||
if (targetBody != null && HighlightControlFlowUtil.variableDefinitelyAssignedIn(field, targetBody)) {
|
||||
if (targetBody != null && ControlFlowUtil.variableDefinitelyAssignedIn(field, targetBody)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,7 @@ public class NotNullFieldNotInitializedInspection extends AbstractBaseJavaLocalI
|
||||
PsiMethod method = TestFrameworks.getInstance().findSetUpMethod(field.getContainingClass());
|
||||
if (method != null) {
|
||||
PsiCodeBlock body = method.getBody();
|
||||
if (body != null && HighlightControlFlowUtil.variableDefinitelyAssignedIn(field, body)) {
|
||||
if (body != null && ControlFlowUtil.variableDefinitelyAssignedIn(field, body)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,14 @@ package com.intellij.psi.controlFlow;
|
||||
import com.intellij.codeInsight.ExceptionUtil;
|
||||
import com.intellij.codeInsight.ExpressionUtil;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.IndexNotReadyException;
|
||||
import com.intellij.openapi.util.Predicates;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.augment.PsiAugmentProvider;
|
||||
import com.intellij.psi.impl.source.DummyHolder;
|
||||
import com.intellij.psi.util.FileTypeUtils;
|
||||
import com.intellij.psi.util.JavaPsiRecordUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.*;
|
||||
@@ -17,6 +22,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public final class ControlFlowUtil {
|
||||
private static final Logger LOG = Logger.getInstance(ControlFlowUtil.class);
|
||||
@@ -31,20 +37,7 @@ public final class ControlFlowUtil {
|
||||
* another place might exist.
|
||||
*/
|
||||
public static @Nullable PsiElement getScopeEnforcingEffectiveFinality(@NotNull PsiVariable variable, @NotNull PsiElement context) {
|
||||
PsiElement[] scope;
|
||||
if (variable instanceof PsiResourceVariable) {
|
||||
scope = ((PsiResourceVariable)variable).getDeclarationScope();
|
||||
}
|
||||
else if (variable instanceof PsiLocalVariable) {
|
||||
PsiElement parent = variable.getParent();
|
||||
scope = new PsiElement[]{parent != null ? parent.getParent() : null}; // code block or for statement
|
||||
}
|
||||
else if (variable instanceof PsiParameter) {
|
||||
scope = new PsiElement[]{((PsiParameter)variable).getDeclarationScope()};
|
||||
}
|
||||
else {
|
||||
scope = new PsiElement[]{variable.getParent()};
|
||||
}
|
||||
PsiElement[] scope = getVariableScope(variable);
|
||||
if (scope.length < 1 || scope[0] == null || scope[0].getContainingFile() != context.getContainingFile()) return null;
|
||||
PsiElement parent = context.getParent();
|
||||
PsiElement prevParent = context;
|
||||
@@ -68,6 +61,24 @@ public final class ControlFlowUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static PsiElement @NotNull [] getVariableScope(@NotNull PsiVariable variable) {
|
||||
PsiElement[] scope;
|
||||
if (variable instanceof PsiResourceVariable) {
|
||||
scope = ((PsiResourceVariable)variable).getDeclarationScope();
|
||||
}
|
||||
else if (variable instanceof PsiLocalVariable) {
|
||||
PsiElement parent = variable.getParent();
|
||||
scope = new PsiElement[]{parent != null ? parent.getParent() : null}; // code block or for statement
|
||||
}
|
||||
else if (variable instanceof PsiParameter) {
|
||||
scope = new PsiElement[]{((PsiParameter)variable).getDeclarationScope()};
|
||||
}
|
||||
else {
|
||||
scope = new PsiElement[]{variable.getParent()};
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param variable variable to check
|
||||
* @param scope variable scope
|
||||
@@ -90,16 +101,9 @@ public final class ControlFlowUtil {
|
||||
}
|
||||
else {
|
||||
PsiElement codeBlock = PsiUtil.getVariableCodeBlock(variable, context);
|
||||
ControlFlow controlFlow;
|
||||
try {
|
||||
if (codeBlock == null) return true;
|
||||
LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance();
|
||||
controlFlow = ControlFlowFactory.getControlFlow(
|
||||
codeBlock, policy, ControlFlowOptions.create(true, true, true));
|
||||
}
|
||||
catch (AnalysisCanceledException e) {
|
||||
return true;
|
||||
}
|
||||
if (codeBlock == null) return true;
|
||||
ControlFlow controlFlow = getControlFlow(codeBlock);
|
||||
if (controlFlow == null) return true;
|
||||
|
||||
Collection<VariableInfo> initializedTwice = getInitializedTwice(controlFlow);
|
||||
effectivelyFinal = !initializedTwice.contains(new VariableInfo(variable, null));
|
||||
@@ -131,12 +135,306 @@ public final class ControlFlowUtil {
|
||||
return effectivelyFinal;
|
||||
}
|
||||
|
||||
private static @Nullable ControlFlow getControlFlow(PsiElement codeBlock) {
|
||||
try {
|
||||
LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance();
|
||||
return ControlFlowFactory.getControlFlow(
|
||||
codeBlock, policy, ControlFlowOptions.create(true, true, true));
|
||||
}
|
||||
catch (AnalysisCanceledException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean variableIsAssigned(@NotNull PsiVariable variable, @NotNull PsiElement scope) {
|
||||
return !PsiTreeUtil.processElements(scope, PsiReferenceExpression.class, e -> {
|
||||
return !(PsiUtil.isAccessedForWriting(e) && e.isReferenceTo(variable));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field field to check
|
||||
* @return true if the field is initialized (in class initializer, own initializer, another field initializer, or constructor)
|
||||
*/
|
||||
public static boolean isFieldInitializedAfterObjectConstruction(@NotNull PsiField field) {
|
||||
if (field.hasInitializer()) return true;
|
||||
boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC);
|
||||
PsiClass aClass = field.getContainingClass();
|
||||
if (aClass == null) return false;
|
||||
// field might be assigned in the other field initializers
|
||||
if (isFieldInitializedInOtherFieldInitializer(aClass, field, Predicates.alwaysTrue())) return true;
|
||||
if (isFieldInitializedInClassInitializer(field)) return true;
|
||||
if (isFieldStatic) return false;
|
||||
// instance field should be initialized at the end of each constructor
|
||||
PsiMethod[] constructors = aClass.getConstructors();
|
||||
|
||||
if (constructors.length == 0) return false;
|
||||
nextConstructor:
|
||||
for (PsiMethod constructor : constructors) {
|
||||
PsiCodeBlock ctrBody = constructor.getBody();
|
||||
if (ctrBody == null) return false;
|
||||
for (PsiMethod redirectedConstructor : JavaPsiConstructorUtil.getChainedConstructors(constructor)) {
|
||||
PsiCodeBlock body = redirectedConstructor.getBody();
|
||||
if (body != null && variableDefinitelyAssignedIn(field, body, true)) continue nextConstructor;
|
||||
}
|
||||
if (!ctrBody.isValid() || variableDefinitelyAssignedIn(field, ctrBody, true)) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field field to check
|
||||
* @return true if the field is initialized in its class initializers
|
||||
*/
|
||||
private static boolean isFieldInitializedInClassInitializer(@NotNull PsiField field) {
|
||||
PsiClass aClass = field.getContainingClass();
|
||||
if (aClass == null) return false;
|
||||
PsiClassInitializer[] initializers = aClass.getInitializers();
|
||||
boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC);
|
||||
return ContainerUtil.find(initializers, initializer -> initializer.hasModifierProperty(PsiModifier.STATIC) == isFieldStatic
|
||||
&& variableDefinitelyAssignedIn(field, initializer.getBody(), true)) != null;
|
||||
}
|
||||
|
||||
private static boolean isFieldInitializedInOtherFieldInitializer(@NotNull PsiClass aClass,
|
||||
@NotNull PsiField field,
|
||||
@NotNull Predicate<? super PsiField> condition) {
|
||||
boolean fieldStatic = field.hasModifierProperty(PsiModifier.STATIC);
|
||||
for (PsiField psiField : aClass.getFields()) {
|
||||
if (psiField != field
|
||||
&& psiField.hasModifierProperty(PsiModifier.STATIC) == fieldStatic
|
||||
&& variableDefinitelyAssignedIn(field, psiField, true)
|
||||
&& condition.test(psiField)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return field that has initializer with this element as subexpression or null if not found
|
||||
*/
|
||||
private static PsiField findEnclosingFieldInitializer(@NotNull PsiElement entry) {
|
||||
PsiElement element = entry;
|
||||
while (element != null) {
|
||||
PsiElement parent = element.getParent();
|
||||
if (parent instanceof PsiField) {
|
||||
PsiField field = (PsiField)parent;
|
||||
if (element == field.getInitializer()) return field;
|
||||
if (field instanceof PsiEnumConstant && element == ((PsiEnumConstant)field).getArgumentList()) return field;
|
||||
}
|
||||
if (element instanceof PsiClass || element instanceof PsiMethod) return null;
|
||||
element = parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* see JLS chapter 16
|
||||
* @param variable variable to check
|
||||
* @param scope variable scope (code block, field initializer, etc.)
|
||||
* @return true if variable assigned (maybe more than once)
|
||||
*/
|
||||
public static boolean variableDefinitelyAssignedIn(@NotNull PsiVariable variable, @NotNull PsiElement scope) {
|
||||
return variableDefinitelyAssignedIn(variable, scope, false);
|
||||
}
|
||||
|
||||
private static boolean variableDefinitelyAssignedIn(@NotNull PsiVariable variable,
|
||||
@NotNull PsiElement scope,
|
||||
boolean resultOnIncompleteCode) {
|
||||
ControlFlow flow = getControlFlow(scope);
|
||||
return flow == null ? resultOnIncompleteCode : isVariableDefinitelyAssigned(variable, flow);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expression variable reference (usage)
|
||||
* @param variable variable
|
||||
* @param uninitializedVarProblems map to cache results from the same code block
|
||||
* @param treatNonFinalFieldsAsNonInitialized if true, the non-final field will not be considered as initialized with the default value
|
||||
* @return true if the variable is initialized before usage
|
||||
*/
|
||||
public static boolean isInitializedBeforeUsage(@NotNull PsiReferenceExpression expression,
|
||||
@NotNull PsiVariable variable,
|
||||
@NotNull Map<? super PsiElement, Collection<PsiReferenceExpression>> uninitializedVarProblems,
|
||||
boolean treatNonFinalFieldsAsNonInitialized) {
|
||||
if (variable instanceof ImplicitVariable) return true;
|
||||
if (!PsiUtil.isAccessedForReading(expression)) return true;
|
||||
int startOffset = expression.getTextRange().getStartOffset();
|
||||
PsiElement topBlock = getTopBlock(expression, variable);
|
||||
if (topBlock == null) return true;
|
||||
if (!variable.hasInitializer()) {
|
||||
if (variable instanceof PsiField) {
|
||||
PsiField field = (PsiField)variable;
|
||||
// non-final field already initialized with default value
|
||||
if (!treatNonFinalFieldsAsNonInitialized && !variable.hasModifierProperty(PsiModifier.FINAL)) return true;
|
||||
// a final field may be initialized in ctor or class initializer only
|
||||
// if we're inside a non-constructor method, skip it
|
||||
if (PsiUtil.findEnclosingConstructorOrInitializer(expression) == null
|
||||
&& findEnclosingFieldInitializer(expression) == null) {
|
||||
return true;
|
||||
}
|
||||
PsiElement parent = topBlock.getParent();
|
||||
// access to final fields from inner classes always allowed
|
||||
if (inInnerClass(expression, field.getContainingClass())) return true;
|
||||
PsiCodeBlock block;
|
||||
PsiClass aClass;
|
||||
if (parent instanceof PsiMethod) {
|
||||
PsiMethod constructor = (PsiMethod)parent;
|
||||
if (!constructor.getManager().areElementsEquivalent(constructor.getContainingClass(), field.getContainingClass())) return true;
|
||||
// static variables already initialized in class initializers
|
||||
if (variable.hasModifierProperty(PsiModifier.STATIC)) return true;
|
||||
// as a last chance, the 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 true;
|
||||
PsiCodeBlock body = redirectedConstructor.getBody();
|
||||
if (body != null && variableDefinitelyAssignedIn(variable, body, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
block = constructor.getBody();
|
||||
aClass = constructor.getContainingClass();
|
||||
}
|
||||
else if (parent instanceof PsiClassInitializer) {
|
||||
PsiClassInitializer classInitializer = (PsiClassInitializer)parent;
|
||||
if (!classInitializer.getManager().areElementsEquivalent(classInitializer.getContainingClass(), field.getContainingClass())) {
|
||||
return true;
|
||||
}
|
||||
block = classInitializer.getBody();
|
||||
aClass = classInitializer.getContainingClass();
|
||||
|
||||
if (aClass == null || isFieldInitializedInOtherFieldInitializer(aClass, field, f -> startOffset > f.getTextOffset())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// field reference outside code block
|
||||
// check variable initialized before its usage
|
||||
aClass = field.getContainingClass();
|
||||
PsiField anotherField = PsiTreeUtil.getTopmostParentOfType(expression, PsiField.class);
|
||||
if (aClass == null ||
|
||||
isFieldInitializedInOtherFieldInitializer(aClass, field, f -> f != anotherField && startOffset > f.getTextOffset())) {
|
||||
return true;
|
||||
}
|
||||
if (anotherField != null
|
||||
&& !anotherField.hasModifierProperty(PsiModifier.STATIC)
|
||||
&& field.hasModifierProperty(PsiModifier.STATIC)
|
||||
&& isFieldInitializedInClassInitializer(field)) {
|
||||
return true;
|
||||
}
|
||||
if (anotherField != null && anotherField.hasInitializer() && !PsiAugmentProvider.canTrustFieldInitializer(anotherField)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int offset = startOffset;
|
||||
if (anotherField != null && anotherField.getContainingClass() == aClass && !field.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
offset = 0;
|
||||
}
|
||||
block = null;
|
||||
// initializers will be checked later
|
||||
for (PsiMethod constructor : aClass.getConstructors()) {
|
||||
// the variable must be initialized before its usage
|
||||
if (offset < constructor.getTextRange().getStartOffset()) continue;
|
||||
PsiCodeBlock body = constructor.getBody();
|
||||
if (body != null && variableDefinitelyAssignedIn(variable, body)) {
|
||||
return true;
|
||||
}
|
||||
// as a last chance, the field may be initialized in this() call
|
||||
for (PsiMethod redirectedConstructor : JavaPsiConstructorUtil.getChainedConstructors(constructor)) {
|
||||
// the variable must be initialized before its usage
|
||||
if (offset < redirectedConstructor.getTextRange().getStartOffset()) continue;
|
||||
PsiCodeBlock redirectedBody = redirectedConstructor.getBody();
|
||||
if (redirectedBody != null && variableDefinitelyAssignedIn(variable, redirectedBody)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (aClass != null) {
|
||||
// field may be initialized in class initializer
|
||||
for (PsiClassInitializer initializer : aClass.getInitializers()) {
|
||||
PsiCodeBlock body = initializer.getBody();
|
||||
if (body == block) break;
|
||||
// variable referenced in initializer must be initialized in initializer preceding assignment
|
||||
// variable referenced in field initializer or in class initializer
|
||||
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 true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Collection<PsiReferenceExpression> codeBlockProblems = uninitializedVarProblems.get(topBlock);
|
||||
if (codeBlockProblems == null) {
|
||||
try {
|
||||
ControlFlow controlFlow = getControlFlow(topBlock);
|
||||
codeBlockProblems = controlFlow == null ? Collections.emptyList() : getReadBeforeWriteLocals(controlFlow);
|
||||
}
|
||||
catch (IndexNotReadyException e) {
|
||||
codeBlockProblems = Collections.emptyList();
|
||||
}
|
||||
uninitializedVarProblems.put(topBlock, codeBlockProblems);
|
||||
}
|
||||
return !codeBlockProblems.contains(expression);
|
||||
}
|
||||
|
||||
private static @Nullable PsiElement getTopBlock(@NotNull PsiReferenceExpression expression, @NotNull PsiVariable variable) {
|
||||
if (variable.hasInitializer()) {
|
||||
return PsiUtil.getVariableCodeBlock(variable, null);
|
||||
}
|
||||
PsiElement scope = variable instanceof PsiField
|
||||
? ((PsiField)variable).getContainingClass()
|
||||
: variable.getParent() != null ? variable.getParent().getParent() : null;
|
||||
while (scope instanceof PsiCodeBlock && scope.getParent() instanceof PsiSwitchBlock) {
|
||||
scope = PsiTreeUtil.getParentOfType(scope, PsiCodeBlock.class);
|
||||
}
|
||||
|
||||
return FileTypeUtils.isInServerPageFile(scope) && scope instanceof PsiFile
|
||||
? scope
|
||||
: PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope);
|
||||
}
|
||||
|
||||
private static boolean inInnerClass(@NotNull PsiElement psiElement, @Nullable PsiClass containingClass) {
|
||||
for (PsiElement element = psiElement; element != null; element = element.getParent()) {
|
||||
if (element instanceof PsiClass) {
|
||||
PsiClass aClass = (PsiClass)element;
|
||||
boolean innerClass = !psiElement.getManager().areElementsEquivalent(element, containingClass);
|
||||
if (innerClass) {
|
||||
if (element instanceof PsiAnonymousClass) {
|
||||
if (PsiTreeUtil.isAncestor(((PsiAnonymousClass)element).getArgumentList(), psiElement, false)) {
|
||||
continue;
|
||||
}
|
||||
return !insideClassInitialization(containingClass, aClass);
|
||||
}
|
||||
PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(psiElement, PsiLambdaExpression.class);
|
||||
return lambdaExpression == null || !insideClassInitialization(containingClass, aClass);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean insideClassInitialization(@Nullable PsiClass containingClass, PsiClass aClass) {
|
||||
PsiMember member = aClass;
|
||||
while (member != null) {
|
||||
if (member.getContainingClass() == containingClass) {
|
||||
return member instanceof PsiField ||
|
||||
member instanceof PsiMethod && ((PsiMethod)member).isConstructor() ||
|
||||
member instanceof PsiClassInitializer;
|
||||
}
|
||||
member = PsiTreeUtil.getParentOfType(member, PsiMember.class, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class SSAInstructionState {
|
||||
private final int myWriteCount;
|
||||
private final int myInstructionIdx;
|
||||
@@ -387,7 +685,7 @@ public final class ControlFlowUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* If the variable occurs only once in the element and it's read access return that occurrence
|
||||
* If the variable occurs only once in the element, and it's read access return that occurrence
|
||||
*/
|
||||
public static PsiReferenceExpression findSingleReadOccurrence(@NotNull ControlFlow flow,
|
||||
@NotNull PsiElement element,
|
||||
|
||||
+2
-2
@@ -223,7 +223,7 @@ class AssignmentInUnreachablePolyadic {
|
||||
if (true && (<error descr="Variable 'b' might already have been assigned to">b</error> = false)) ;
|
||||
}
|
||||
|
||||
<error descr="Variable 'bb' might not have been initialized">private final boolean bb</error>;
|
||||
<error descr="Field 'bb' might not have been initialized">private final boolean bb</error>;
|
||||
{
|
||||
if (false && (bb = false) && (<error descr="Variable 'bb' might already have been assigned to">bb</error> = true)) ;
|
||||
}
|
||||
@@ -255,7 +255,7 @@ class QualifiedThis {
|
||||
|
||||
class ParenthesizedThis {
|
||||
final int x;
|
||||
final int y = <error descr="Variable '(this).x' might not have been initialized">(this).x</error> + 1;
|
||||
final int y = <error descr="Variable 'x' might not have been initialized">(this).x</error> + 1;
|
||||
|
||||
ParenthesizedThis() {
|
||||
(this).x = 5; // javac disallows this -- probably a bug in javac
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ class c4 {
|
||||
}
|
||||
// redirected ctrs
|
||||
class c5 {
|
||||
<error descr="Variable 'k' might not have been initialized">final int k</error>;
|
||||
<error descr="Field 'k' might not have been initialized">final int k</error>;
|
||||
c5(int i) {
|
||||
k =1;
|
||||
}
|
||||
|
||||
+10
-10
@@ -5,15 +5,15 @@ class a {
|
||||
/**
|
||||
* javadoc should not be highlighted
|
||||
*/
|
||||
<error descr="Variable 'javaDoced' might not have been initialized">final int javaDoced</error>;
|
||||
<error descr="Field 'javaDoced' might not have been initialized">final int javaDoced</error>;
|
||||
|
||||
<error descr="Variable 'sfi1' might not have been initialized">static final int sfi1</error>;
|
||||
<error descr="Variable 'sfi2' might not have been initialized">static final int sfi2</error>;
|
||||
<error descr="Variable 'fi1' might not have been initialized">final int fi1</error>;
|
||||
<error descr="Variable 'fi2' might not have been initialized">final int fi2</error>;
|
||||
<error descr="Field 'sfi1' might not have been initialized">static final int sfi1</error>;
|
||||
<error descr="Field 'sfi2' might not have been initialized">static final int sfi2</error>;
|
||||
<error descr="Field 'fi1' might not have been initialized">final int fi1</error>;
|
||||
<error descr="Field 'fi2' might not have been initialized">final int fi2</error>;
|
||||
|
||||
class inner {
|
||||
<error descr="Variable 'fii' might not have been initialized">final int fii</error>;
|
||||
<error descr="Field 'fii' might not have been initialized">final int fii</error>;
|
||||
}
|
||||
final int fi3;
|
||||
final int fi4;
|
||||
@@ -140,7 +140,7 @@ class c2 {
|
||||
}
|
||||
|
||||
class UninitializedFinal2 {
|
||||
<error descr="Variable 's' might not have been initialized">private final String s</error>;
|
||||
<error descr="Field 's' might not have been initialized">private final String s</error>;
|
||||
|
||||
UninitializedFinal2(){
|
||||
try {
|
||||
@@ -151,7 +151,7 @@ class UninitializedFinal2 {
|
||||
}
|
||||
class UninitedFinalFied {
|
||||
|
||||
<error descr="Variable 'string' might not have been initialized">private final String string</error>;
|
||||
<error descr="Field 'string' might not have been initialized">private final String string</error>;
|
||||
|
||||
public UninitedFinalFied() throws IOException {
|
||||
init();
|
||||
@@ -277,7 +277,7 @@ class InitializedInClassInitializerUsedInTheFollowingFieldInitializer {
|
||||
}
|
||||
|
||||
class AssignInAssert {
|
||||
<error descr="Variable 'b' might not have been initialized">private final boolean b</error>;
|
||||
<error descr="Field 'b' might not have been initialized">private final boolean b</error>;
|
||||
|
||||
AssignInAssert() {
|
||||
assert b = true;
|
||||
@@ -301,7 +301,7 @@ class DefiniteAssignmentInFinally {
|
||||
}
|
||||
class StaticInitializerUsedInAnotherInstanceField {
|
||||
private final int myEnumerationCacheConstant = ENUMERATION_CACHE_SIZE;
|
||||
private static final int ourEnumerationCacheConstant = <error descr="Cannot read value of field 'ENUMERATION_CACHE_SIZE' before the field's definition">ENUMERATION_CACHE_SIZE</error>;
|
||||
private static final int ourEnumerationCacheConstant = <error descr="Variable 'ENUMERATION_CACHE_SIZE' might not have been initialized">ENUMERATION_CACHE_SIZE</error>;
|
||||
|
||||
private static final int ENUMERATION_CACHE_SIZE;
|
||||
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ public class a {
|
||||
|
||||
public String getTreeCellRendererComponent(String value) {
|
||||
if (value instanceof String) {
|
||||
int i = myLabel.length();
|
||||
int i = <error descr="Variable 'myLabel' might not have been initialized">myLabel</error>.length();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ class Unassigned {
|
||||
public int b;
|
||||
|
||||
Unassigned(int value) {
|
||||
b = <error descr="Variable '(this).a' might not have been initialized">(this).a</error>;
|
||||
b = <error descr="Variable 'a' might not have been initialized">(this).a</error>;
|
||||
a = value;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -92,11 +92,11 @@ class TestAnonymousWithRefToTheTopLevelUninitializedField {
|
||||
class TestThisQualified {
|
||||
final String s;
|
||||
|
||||
final Runnable r = () -> System.out.println(<error descr="Variable 'this.s' might not have been initialized">this.s</error>.length());
|
||||
final Runnable r = () -> System.out.println(<error descr="Variable 's' might not have been initialized">this.s</error>.length());
|
||||
final Runnable r2 = () -> System.out.println(this.r2);
|
||||
final Runnable r3;
|
||||
{
|
||||
r3 = () -> System.out.println(<error descr="Variable 'this.r3' might not have been initialized">this.r3</error>);
|
||||
r3 = () -> System.out.println(<error descr="Variable 'r3' might not have been initialized">this.r3</error>);
|
||||
}
|
||||
|
||||
public TestThisQualified() {
|
||||
|
||||
+2
-2
@@ -11,9 +11,9 @@ class Test {
|
||||
|
||||
private final IntFunction multiply = i -> i * Test.this.idx;
|
||||
|
||||
private final int idx3 = <error descr="Variable 'this.idx' might not have been initialized">this.idx</error> + 1;
|
||||
private final int idx3 = <error descr="Variable 'idx' might not have been initialized">this.idx</error> + 1;
|
||||
|
||||
private final IntFunction multiply2 = i -> i * <error descr="Variable 'this.idx' might not have been initialized">this.idx</error>;
|
||||
private final IntFunction multiply2 = i -> i * <error descr="Variable 'idx' might not have been initialized">this.idx</error>;
|
||||
|
||||
public Test(int idx) {
|
||||
this.idx = idx;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
class MyTest {
|
||||
final Runnable lambdaRunnable = () -> {
|
||||
System.out.println(<error descr="Cannot read value of field 'o' before the field's definition">o</error>);
|
||||
System.out.println(<error descr="Variable 'o' might not have been initialized">o</error>);
|
||||
};
|
||||
|
||||
final Object o;
|
||||
|
||||
+2
-2
@@ -69,7 +69,7 @@ class C {
|
||||
|
||||
|
||||
static class FinalFieldAssignedInSomeBranches {
|
||||
<error descr="Variable 'n' might not have been initialized">final int n</error>;
|
||||
<error descr="Field 'n' might not have been initialized">final int n</error>;
|
||||
{
|
||||
switch ((int)Math.random()) {
|
||||
case 1 -> n = 1;
|
||||
@@ -79,7 +79,7 @@ class C {
|
||||
}
|
||||
|
||||
static class FinalFieldAssignedInSomeBranchesNoDefault {
|
||||
<error descr="Variable 'n' might not have been initialized">final int n</error>;
|
||||
<error descr="Field 'n' might not have been initialized">final int n</error>;
|
||||
{
|
||||
switch ((int)Math.random()) {
|
||||
case 1 -> n = 1;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
value class One {
|
||||
|
||||
<error descr="Variable 'value' might not have been initialized">private int value</error>;
|
||||
<error descr="Field 'value' might not have been initialized">private int value</error>;
|
||||
|
||||
<error descr="Modifier 'synchronized' not allowed here">synchronized</error> void x() {}
|
||||
}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ class TypeUseWithTypeHighlighting {
|
||||
|
||||
<warning descr="Missorted modifiers '@ReadOnly private'">@ReadOnly</warning> private String fieldOneLine;
|
||||
|
||||
private <warning descr="Missorted modifiers '@ReadOnly final'">@ReadOnly</warning> <error descr="Variable 'field1' might not have been initialized">final String field1</error>;
|
||||
private <warning descr="Missorted modifiers '@ReadOnly final'">@ReadOnly</warning> <error descr="Field 'field1' might not have been initialized">final String field1</error>;
|
||||
private final @ReadOnly String field2 = "2";
|
||||
}
|
||||
@Target({ElementType.TYPE_USE, ElementType.FIELD})
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ class TypeUseWithTypeHighlighting {
|
||||
|
||||
@ReadOnly private String fieldOneLine;
|
||||
|
||||
private <warning descr="Missorted modifiers '@ReadOnly final'">@ReadOnly</warning> <error descr="Variable 'field1' might not have been initialized">final String field1</error>;
|
||||
private <warning descr="Missorted modifiers '@ReadOnly final'">@ReadOnly</warning> <error descr="Field 'field1' might not have been initialized">final String field1</error>;
|
||||
private final @ReadOnly String field2 = "2";
|
||||
}
|
||||
@Target({ElementType.TYPE_USE, ElementType.FIELD})
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.nj2k
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil
|
||||
import com.intellij.codeInspection.localCanBeFinal.LocalCanBeFinal
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.controlFlow.ControlFlowUtil
|
||||
@@ -186,7 +185,7 @@ private fun fieldConstructionImpliesMutable(field: PsiField): Boolean {
|
||||
val usefulRedirectedConstructors: MutableList<PsiMethod> = ArrayList()
|
||||
for (redirectedConstructor in redirectedConstructors) {
|
||||
val body = redirectedConstructor.getBody()
|
||||
if (body != null && (HighlightControlFlowUtil.variableDefinitelyAssignedIn(field, body) || isValidThisMethodInConstructor(
|
||||
if (body != null && (ControlFlowUtil.variableDefinitelyAssignedIn(field, body) || isValidThisMethodInConstructor(
|
||||
redirectedConstructor
|
||||
))
|
||||
) {
|
||||
@@ -194,7 +193,7 @@ private fun fieldConstructionImpliesMutable(field: PsiField): Boolean {
|
||||
}
|
||||
}
|
||||
if (usefulRedirectedConstructors.isNotEmpty() && usefulRedirectedConstructors.size != redirectedConstructors.size) return true
|
||||
if (ctrBody.isValid() && (HighlightControlFlowUtil.variableDefinitelyAssignedIn(field, ctrBody) || isValidThisMethodInConstructor(
|
||||
if (ctrBody.isValid() && (ControlFlowUtil.variableDefinitelyAssignedIn(field, ctrBody) || isValidThisMethodInConstructor(
|
||||
constructor
|
||||
))
|
||||
) {
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
<error descr="Class contains required fields, you have to force NoArgsConstructor.">@lombok.NoArgsConstructor</error>
|
||||
public class NoArgsConstructorWithRequiredFieldsShouldBeForced {
|
||||
<error descr="Variable 'test' might not have been initialized">private final String test</error>;
|
||||
<error descr="Variable 'test2' might not have been initialized">private final String test2</error>;
|
||||
<error descr="Variable 'test3' might not have been initialized">private final int test3</error>;
|
||||
<error descr="Field 'test' might not have been initialized">private final String test</error>;
|
||||
<error descr="Field 'test2' might not have been initialized">private final String test2</error>;
|
||||
<error descr="Field 'test3' might not have been initialized">private final int test3</error>;
|
||||
}
|
||||
Reference in New Issue
Block a user