diff --git a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties index b2d5370a94ed..963b613c4ff5 100644 --- a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties +++ b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties @@ -374,6 +374,11 @@ pattern.cannot.infer.type=Cannot infer pattern type: {0} pattern.instanceof.supertype=Pattern type ''{0}'' is a supertype of expression type ''{1}'' pattern.instanceof.equals=Pattern type ''{0}'' is the same as expression type +variable.must.be.final=Variable ''{0}'' is accessed from within inner class, needs to be declared final +variable.must.be.effectively.final=Variable ''{0}'' is accessed from within inner class, needs to be final or effectively final +variable.must.be.effectively.final.lambda=Variable used in lambda expression should be final or effectively final +variable.must.be.effectively.final.guard=Variable used in guard expression should be final or effectively final + instanceof.type.parameter=Class or array expected instanceof.illegal.generic.type=Illegal generic type for instanceof instanceof.unsafe.cast=''{0}'' cannot be safely cast to ''{1}'' diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ControlFlowChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ControlFlowChecker.java index 2a19ae9f1958..6570e618ded2 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ControlFlowChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ControlFlowChecker.java @@ -3,6 +3,7 @@ package com.intellij.java.codeserver.highlighting; import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds; import com.intellij.openapi.project.IndexNotReadyException; +import com.intellij.pom.java.JavaFeature; import com.intellij.psi.*; import com.intellij.psi.augment.PsiAugmentProvider; import com.intellij.psi.controlFlow.*; @@ -94,6 +95,34 @@ final class ControlFlowChecker { } } + void checkVariableMustBeFinal(@NotNull PsiVariable variable, @NotNull PsiJavaCodeReferenceElement context) { + if (variable.hasModifierProperty(PsiModifier.FINAL)) return; + PsiElement scope = ControlFlowUtil.getScopeEnforcingEffectiveFinality(variable, context); + if (scope == null) return; + if (scope instanceof PsiClass) { + if (variable instanceof PsiParameter parameter) { + PsiElement parent = variable.getParent(); + if (parent instanceof PsiParameterList && parent.getParent() instanceof PsiLambdaExpression && + ControlFlowUtil.isEffectivelyFinal(variable, parameter.getDeclarationScope())) { + return; + } + } + boolean isToBeEffectivelyFinal = myVisitor.isApplicable(JavaFeature.EFFECTIVELY_FINAL); + if (isToBeEffectivelyFinal && ControlFlowUtil.isEffectivelyFinal(variable, scope, context)) return; + var kind = isToBeEffectivelyFinal ? JavaErrorKinds.VARIABLE_MUST_BE_EFFECTIVELY_FINAL : JavaErrorKinds.VARIABLE_MUST_BE_FINAL; + myVisitor.report(kind.create(context, variable)); + return; + } else if (scope instanceof PsiLambdaExpression) { + if (ControlFlowUtil.isEffectivelyFinal(variable, scope, context)) return; + myVisitor.report(JavaErrorKinds.VARIABLE_MUST_BE_EFFECTIVELY_FINAL_LAMBDA.create(context, variable)); + } else if (scope instanceof PsiSwitchLabelStatementBase) { + // Reported separately in ExpressionChecker.checkOutsideDeclaredCantBeAssignmentInGuard + if (context instanceof PsiReferenceExpression ref && PsiUtil.isAccessedForWriting(ref)) return; + if (ControlFlowUtil.isEffectivelyFinal(variable, scope, context)) return; + myVisitor.report(JavaErrorKinds.VARIABLE_MUST_BE_EFFECTIVELY_FINAL_GUARD.create(context, variable)); + } + } + /** * @return field that has initializer with this element as subexpression or null if not found */ diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java index 13cae4adc33e..54654215c160 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java @@ -28,7 +28,7 @@ import org.jetbrains.annotations.Nullable; import java.util.*; -import static java.util.Objects.*; +import static java.util.Objects.requireNonNullElse; final class ExpressionChecker { private final @NotNull JavaErrorVisitor myVisitor; @@ -919,6 +919,9 @@ final class ExpressionChecker { !PsiUtil.isFromDefaultPackage(myVisitor.file()))) { myVisitor.report(JavaErrorKinds.REFERENCE_CLASS_IN_DEFAULT_PACKAGE.create(ref, psiClass)); } + if ((resolved instanceof PsiLocalVariable || resolved instanceof PsiParameter) && !(resolved instanceof ImplicitVariable)) { + myVisitor.myControlFlowChecker.checkVariableMustBeFinal((PsiVariable)resolved, ref); + } } private static boolean favorParentReport(@NotNull PsiCall methodCall, @NotNull String errorMessage) { diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java index 893d0b6d8cfe..f6b5a321ebfc 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java @@ -53,7 +53,7 @@ final class JavaErrorVisitor extends JavaElementVisitor { final @NotNull TypeChecker myTypeChecker = new TypeChecker(this); final @NotNull MethodChecker myMethodChecker = new MethodChecker(this); private final @NotNull ReceiverChecker myReceiverChecker = new ReceiverChecker(this); - private final @NotNull ControlFlowChecker myControlFlowChecker = new ControlFlowChecker(this); + final @NotNull ControlFlowChecker myControlFlowChecker = new ControlFlowChecker(this); private final @NotNull FunctionChecker myFunctionChecker = new FunctionChecker(this); final @NotNull PatternChecker myPatternChecker = new PatternChecker(this); final @NotNull ModifierChecker myModifierChecker = new ModifierChecker(this); diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java index 8705dbc99f41..b6e363f09902 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java @@ -1235,6 +1235,17 @@ public final class JavaErrorKinds { public static final Parameterized RETURN_BEFORE_EXPLICIT_CONSTRUCTOR_CALL = parameterized(PsiReturnStatement.class, PsiMethodCallExpression.class, "return.before.explicit.constructor.call") .withRawDescription((psi, call) -> message("return.before.explicit.constructor.call", call.getMethodExpression().getText() + "()")); + + public static final Parameterized VARIABLE_MUST_BE_FINAL = + parameterized(PsiJavaCodeReferenceElement.class, PsiVariable.class, "variable.must.be.final") + .withRawDescription((ref, var) -> message("variable.must.be.final", var.getName())); + public static final Parameterized VARIABLE_MUST_BE_EFFECTIVELY_FINAL = + parameterized(PsiJavaCodeReferenceElement.class, PsiVariable.class, "variable.must.be.effectively.final") + .withRawDescription((ref, var) -> message("variable.must.be.effectively.final", var.getName())); + public static final Parameterized VARIABLE_MUST_BE_EFFECTIVELY_FINAL_LAMBDA = + parameterized(PsiJavaCodeReferenceElement.class, PsiVariable.class, "variable.must.be.effectively.final.lambda"); + public static final Parameterized VARIABLE_MUST_BE_EFFECTIVELY_FINAL_GUARD = + parameterized(PsiJavaCodeReferenceElement.class, PsiVariable.class, "variable.must.be.effectively.final.guard"); private static @NotNull Simple error( @NotNull @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String key) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ErrorFixExtensionPoint.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ErrorFixExtensionPoint.java index 9b0b86051a15..227406965a59 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ErrorFixExtensionPoint.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ErrorFixExtensionPoint.java @@ -5,6 +5,7 @@ import com.intellij.codeInsight.daemon.JavaErrorBundle; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.intention.CommonIntentionAction; import com.intellij.diagnostic.PluginException; +import com.intellij.java.codeserver.highlighting.JavaCompilationErrorBundle; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.PluginAware; @@ -27,6 +28,7 @@ public final class ErrorFixExtensionPoint implements PluginAware { new ExtensionPointName<>("com.intellij.java.error.fix"); @Attribute("errorCode") + @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) public String errorCode; @Attribute("implementationClass") diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java index 5a2a1d6f8fd3..59e4adefdafc 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java @@ -14,8 +14,6 @@ import com.intellij.lang.jvm.actions.MemberRequestsKt; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.util.Predicates; import com.intellij.openapi.util.TextRange; -import com.intellij.pom.java.JavaFeature; -import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.augment.PsiAugmentProvider; import com.intellij.psi.controlFlow.*; @@ -26,10 +24,8 @@ import com.intellij.psi.util.*; import com.intellij.util.JavaPsiConstructorUtil; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; -import com.siyeh.ig.psiutils.VariableAccessUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.PropertyKey; import java.util.*; import java.util.function.Predicate; @@ -217,78 +213,65 @@ public final class HighlightControlFlowUtil { } - public static HighlightInfo.Builder checkVariableInitializedBeforeUsage(@NotNull PsiReferenceExpression expression, - @NotNull PsiVariable variable, - @NotNull Map> uninitializedVarProblems, - @NotNull PsiFile containingFile) { - return checkVariableInitializedBeforeUsage(expression, variable, uninitializedVarProblems, containingFile, false); + static HighlightInfo.Builder checkVariableInitializedBeforeUsage(@NotNull PsiReferenceExpression expression, + @NotNull PsiVariable variable, + @NotNull Map> uninitializedVarProblems) { + if (isInitializedBeforeUsage(expression, variable, uninitializedVarProblems, false)) return null; + return createNotInitializedError(expression, variable); } - public static HighlightInfo.Builder checkVariableInitializedBeforeUsage(@NotNull PsiReferenceExpression expression, - @NotNull PsiVariable variable, - @NotNull Map> uninitializedVarProblems, - @NotNull PsiFile containingFile, - boolean ignoreFinality) { - if (variable instanceof ImplicitVariable) return null; - if (!PsiUtil.isAccessedForReading(expression)) return null; + public static boolean isInitializedBeforeUsage(@NotNull PsiReferenceExpression expression, + @NotNull PsiVariable variable, + @NotNull Map> uninitializedVarProblems, + boolean ignoreFinality) { + if (variable instanceof ImplicitVariable) return true; + if (!PsiUtil.isAccessedForReading(expression)) return true; int startOffset = expression.getTextRange().getStartOffset(); - PsiElement topBlock; - if (variable.hasInitializer()) { - topBlock = PsiUtil.getVariableCodeBlock(variable, variable); - if (topBlock == null) return null; - } - else { - PsiElement scope = variable instanceof PsiField field - ? field.getContainingClass() - : variable.getParent() != null ? variable.getParent().getParent() : null; - while (scope instanceof PsiCodeBlock && scope.getParent() instanceof PsiSwitchBlock) { - scope = PsiTreeUtil.getParentOfType(scope, PsiCodeBlock.class); - } - - topBlock = FileTypeUtils.isInServerPageFile(scope) && scope instanceof PsiFile ? scope : PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope); + PsiElement topBlock = getTopBlock(expression, variable); + if (topBlock == null) return true; + if (!variable.hasInitializer()) { if (variable instanceof PsiField field) { // non-final field already initialized with default value - if (!ignoreFinality && !variable.hasModifierProperty(PsiModifier.FINAL)) return null; + if (!ignoreFinality && !variable.hasModifierProperty(PsiModifier.FINAL)) return true; // a final field may be initialized in ctor or class initializer only // if we're inside non-ctr method, skip it if (PsiUtil.findEnclosingConstructorOrInitializer(expression) == null && findEnclosingFieldInitializer(expression) == null) { - return null; + return true; } - if (topBlock == null) return null; PsiElement parent = topBlock.getParent(); // access to final fields from inner classes always allowed - if (inInnerClass(expression, field.getContainingClass())) return null; + if (inInnerClass(expression, field.getContainingClass())) return true; PsiCodeBlock block; PsiClass aClass; if (parent instanceof PsiMethod constructor) { - if (!containingFile.getManager().areElementsEquivalent(constructor.getContainingClass(), field.getContainingClass())) return null; + if (!constructor.getManager().areElementsEquivalent(constructor.getContainingClass(), field.getContainingClass())) return true; // static variables already initialized in class initializers - if (variable.hasModifierProperty(PsiModifier.STATIC)) return null; + if (variable.hasModifierProperty(PsiModifier.STATIC)) return true; // as a last chance, field may be initialized in this() call for (PsiMethod redirectedConstructor : JavaPsiConstructorUtil.getChainedConstructors(constructor)) { // variable must be initialized before its usage //??? //if (startOffset < redirectedConstructor.getTextRange().getStartOffset()) continue; - if (JavaPsiRecordUtil.isCompactConstructor(redirectedConstructor)) return null; + if (JavaPsiRecordUtil.isCompactConstructor(redirectedConstructor)) return true; PsiCodeBlock body = redirectedConstructor.getBody(); if (body != null && variableDefinitelyAssignedIn(variable, body, true)) { - return null; + return true; } } block = constructor.getBody(); aClass = constructor.getContainingClass(); } else if (parent instanceof PsiClassInitializer classInitializer) { - if (!containingFile.getManager().areElementsEquivalent(classInitializer.getContainingClass(), field.getContainingClass())) { - return null; + if (!classInitializer.getManager().areElementsEquivalent(classInitializer.getContainingClass(), field.getContainingClass())) { + return true; } block = classInitializer.getBody(); aClass = classInitializer.getContainingClass(); if (aClass == null || isFieldInitializedInOtherFieldInitializer(aClass, field, variable.hasModifierProperty(PsiModifier.STATIC), f -> startOffset > f.getTextOffset())) { - return null; + return true; } } else { @@ -299,16 +282,16 @@ public final class HighlightControlFlowUtil { if (aClass == null || isFieldInitializedInOtherFieldInitializer(aClass, field, field.hasModifierProperty(PsiModifier.STATIC), f -> f != anotherField && startOffset > f.getTextOffset())) { - return null; + return true; } if (anotherField != null && !anotherField.hasModifierProperty(PsiModifier.STATIC) && field.hasModifierProperty(PsiModifier.STATIC) && isFieldInitializedInClassInitializer(field, true, aClass.getInitializers())) { - return null; + return true; } if (anotherField != null && anotherField.hasInitializer() && !PsiAugmentProvider.canTrustFieldInitializer(anotherField)) { - return null; + return true; } int offset = startOffset; @@ -322,7 +305,7 @@ public final class HighlightControlFlowUtil { if (offset < constructor.getTextRange().getStartOffset()) continue; PsiCodeBlock body = constructor.getBody(); if (body != null && variableDefinitelyAssignedIn(variable, body)) { - return null; + return true; } // as a last chance, field may be initialized in this() call for (PsiMethod redirectedConstructor : JavaPsiConstructorUtil.getChainedConstructors(constructor)) { @@ -330,7 +313,7 @@ public final class HighlightControlFlowUtil { if (offset < redirectedConstructor.getTextRange().getStartOffset()) continue; PsiCodeBlock redirectedBody = redirectedConstructor.getBody(); if (redirectedBody != null && variableDefinitelyAssignedIn(variable, redirectedBody)) { - return null; + return true; } } } @@ -346,13 +329,12 @@ public final class HighlightControlFlowUtil { boolean shouldCheckInitializerOrder = block == null || block.getParent() instanceof PsiClassInitializer; if (shouldCheckInitializerOrder && startOffset < initializer.getTextRange().getStartOffset()) continue; if (initializer.hasModifierProperty(PsiModifier.STATIC) == variable.hasModifierProperty(PsiModifier.STATIC)) { - if (variableDefinitelyAssignedIn(variable, body)) return null; + if (variableDefinitelyAssignedIn(variable, body)) return true; } } } } } - if (topBlock == null) return null; Collection codeBlockProblems = uninitializedVarProblems.get(topBlock); if (codeBlockProblems == null) { try { @@ -364,29 +346,54 @@ public final class HighlightControlFlowUtil { } uninitializedVarProblems.put(topBlock, codeBlockProblems); } - if (codeBlockProblems.contains(expression)) { - String name = expression.getElement().getText(); - String description = JavaErrorBundle.message("variable.not.initialized", name); - HighlightInfo.Builder builder = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description); - if (!(variable instanceof LightRecordField)) { - IntentionAction action1 = getQuickFixFactory().createAddVariableInitializerFix(variable); - builder.registerFix(action1, null, null, null, null); + return !codeBlockProblems.contains(expression); + } + + private static @Nullable PsiElement getTopBlock(@NotNull PsiReferenceExpression expression, @NotNull PsiVariable variable) { + PsiElement topBlock; + if (variable.hasInitializer()) { + topBlock = PsiUtil.getVariableCodeBlock(variable, variable); + if (topBlock == null) return null; + } + else { + PsiElement scope = variable instanceof PsiField field + ? field.getContainingClass() + : variable.getParent() != null ? variable.getParent().getParent() : null; + while (scope instanceof PsiCodeBlock && scope.getParent() instanceof PsiSwitchBlock) { + scope = PsiTreeUtil.getParentOfType(scope, PsiCodeBlock.class); } - if (variable instanceof PsiLocalVariable) { + + topBlock = FileTypeUtils.isInServerPageFile(scope) && scope instanceof PsiFile + ? scope + : PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope); + } + return topBlock; + } + + private static HighlightInfo.@NotNull Builder createNotInitializedError(@NotNull PsiReferenceExpression expression, + @NotNull PsiVariable variable) { + String name = expression.getElement().getText(); + String description = JavaErrorBundle.message("variable.not.initialized", name); + HighlightInfo.Builder builder = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description); + if (!(variable instanceof LightRecordField)) { + IntentionAction action1 = getQuickFixFactory().createAddVariableInitializerFix(variable); + builder.registerFix(action1, null, null, null, null); + } + if (variable instanceof PsiLocalVariable) { + PsiElement topBlock = getTopBlock(expression, variable); + if (topBlock != null) { IntentionAction action = HighlightFixUtil.createInsertSwitchDefaultFix(variable, topBlock, expression); if (action != null) { builder.registerFix(action, null, null, null, null); } } - if (variable instanceof PsiField field) { - ChangeModifierRequest request = MemberRequestsKt.modifierRequest(JvmModifier.FINAL, false); - QuickFixAction.registerQuickFixActions(builder, null, JvmElementActionFactories.createModifierActions(field, request)); - } - return builder; } - - return null; + if (variable instanceof PsiField field) { + ChangeModifierRequest request = MemberRequestsKt.modifierRequest(JvmModifier.FINAL, false); + QuickFixAction.registerQuickFixActions(builder, null, JvmElementActionFactories.createModifierActions(field, request)); + } + return builder; } private static boolean inInnerClass(@NotNull PsiElement psiElement, @Nullable PsiClass containingClass) { @@ -558,96 +565,6 @@ public final class HighlightControlFlowUtil { return codeBlockProblems; } - - static HighlightInfo.Builder checkVariableMustBeFinal(@NotNull PsiVariable variable, - @NotNull PsiJavaCodeReferenceElement context, - @NotNull LanguageLevel languageLevel) { - if (variable.hasModifierProperty(PsiModifier.FINAL)) return null; - PsiElement scope = ControlFlowUtil.getScopeEnforcingEffectiveFinality(variable, context); - if (scope instanceof PsiClass) { - if (variable instanceof PsiParameter parameter) { - PsiElement parent = variable.getParent(); - if (parent instanceof PsiParameterList && parent.getParent() instanceof PsiLambdaExpression && - !VariableAccessUtils.variableIsAssigned(variable, parameter.getDeclarationScope())) { - return null; - } - } - boolean isToBeEffectivelyFinal = JavaFeature.EFFECTIVELY_FINAL.isSufficient(languageLevel); - if (isToBeEffectivelyFinal && ControlFlowUtil.isEffectivelyFinal(variable, scope, context)) { - return null; - } - String description = JavaErrorBundle - .message(isToBeEffectivelyFinal ? "variable.must.be.final.or.effectively.final" : "variable.must.be.final", context.getText()); - - HighlightInfo.Builder highlightInfo = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context).descriptionAndTooltip(description); - IntentionAction action = getQuickFixFactory().createVariableAccessFromInnerClassFix(variable, scope); - highlightInfo.registerFix(action, null, null, null, null); - return highlightInfo; - } - HighlightInfo.Builder finalInsideLambdaInfo = checkWriteToFinalInsideLambda(variable, context); - if (finalInsideLambdaInfo != null) { - return finalInsideLambdaInfo; - } - return checkFinalUsageInsideGuardedPattern(variable, context); - } - - private static @Nullable HighlightInfo.Builder checkWriteToFinalInsideLambda(@NotNull PsiVariable variable, @NotNull PsiJavaCodeReferenceElement context) { - PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(context, PsiLambdaExpression.class); - if (lambdaExpression != null && !PsiTreeUtil.isAncestor(lambdaExpression, variable, true)) { - PsiElement parent = variable.getParent(); - if (parent instanceof PsiParameterList && parent.getParent() == lambdaExpression) { - return null; - } - PsiSwitchLabelStatementBase label = - PsiTreeUtil.getParentOfType(context, PsiSwitchLabelStatementBase.class, true, PsiLambdaExpression.class); - if (label != null && PsiTreeUtil.isAncestor(label.getGuardExpression(), context, false)) { - return null; - } - HighlightInfo.Builder builder = checkVariableMustBeEffectivelyFinal(variable, context, lambdaExpression, "lambda.variable.must.be.final"); - if (builder != null) return builder; - } - return null; - } - - private static HighlightInfo.Builder checkVariableMustBeEffectivelyFinal(@NotNull PsiVariable variable, - @NotNull PsiJavaCodeReferenceElement context, - @NotNull PsiElement scope, - @NotNull @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String messageKey) { - if (!ControlFlowUtil.isEffectivelyFinal(variable, scope, context)) { - String text = JavaErrorBundle.message(messageKey); - HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context).descriptionAndTooltip(text); - IntentionAction action1 = getQuickFixFactory().createVariableAccessFromInnerClassFix(variable, scope); - builder.registerFix(action1, null, null, null, null); - IntentionAction action2 = getQuickFixFactory().createMakeVariableEffectivelyFinalFix(variable); - if (action2 != null) { - builder.registerFix(action2, null, null, null, null); - } - ErrorFixExtensionPoint.registerFixes(builder, context, messageKey); - return builder; - } - return null; - } - - /** - * 14.30.1 Kinds of Patterns - *

Any variable that is used but not declared in the guarding expression of a guarded pattern must either be final or effectively final. - */ - private static @Nullable HighlightInfo.Builder checkFinalUsageInsideGuardedPattern(@NotNull PsiVariable variable, @NotNull PsiJavaCodeReferenceElement context) { - PsiSwitchLabelStatementBase refLabel = PsiTreeUtil.getParentOfType(context, PsiSwitchLabelStatementBase.class); - - if (refLabel == null) return null; - PsiExpression guardExpression = refLabel.getGuardExpression(); - if (!PsiTreeUtil.isAncestor(guardExpression, context, false)) return null; - //this assignment is covered by com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil.checkOutsideDeclaredCantBeAssignmentInGuard - boolean isAssignment = context instanceof PsiReferenceExpression ref && PsiUtil.isAccessedForWriting(ref); - if (!isAssignment && !PsiTreeUtil.isAncestor(guardExpression, variable, false)) { - HighlightInfo.Builder builder = checkVariableMustBeEffectivelyFinal(variable, context, refLabel, "guarded.pattern.variable.must.be.final"); - if (builder != null) return builder; - } - return null; - } - /** * A kind of final variable problem returned from {@link #getFinalVariableProblemsInBlock(Map, PsiElement)} * which designates a final variable which is initialized in a loop. diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java index b1e5ece9e6aa..a1746c45f2ee 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -958,10 +958,6 @@ public final class HighlightUtil { } } - if ((resolved instanceof PsiLocalVariable || resolved instanceof PsiParameter) && !(resolved instanceof ImplicitVariable)) { - return HighlightControlFlowUtil.checkVariableMustBeFinal((PsiVariable)resolved, ref, languageLevel); - } - return null; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index 5498bafefe01..d771c36b38bf 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -5,6 +5,7 @@ import com.intellij.codeInsight.daemon.JavaErrorBundle; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.daemon.impl.HighlightInfoType; import com.intellij.codeInsight.daemon.impl.HighlightVisitor; +import com.intellij.codeInsight.intention.CommonIntentionAction; import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider; import com.intellij.codeInspection.ex.GlobalInspectionContextBase; import com.intellij.java.codeserver.highlighting.JavaErrorCollector; @@ -222,7 +223,9 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh } else { info.range(anchor); } - errorFixProvider.processFixes(error, fix -> info.registerFix(fix.asIntention(), null, null, null, null)); + Consumer<@NotNull CommonIntentionAction> consumer = fix -> info.registerFix(fix.asIntention(), null, null, null, null); + errorFixProvider.processFixes(error, consumer); + ErrorFixExtensionPoint.registerFixes(consumer, error.psi(), error.kind().key()); error.psiForKind(EXPRESSION_EXPECTED, REFERENCE_UNRESOLVED, REFERENCE_AMBIGUOUS) .or(() -> error.psiForKind(TYPE_UNKNOWN_CLASS).map(PsiTypeElement::getInnermostComponentReferenceElement)) .or(() -> error.psiForKind(CALL_AMBIGUOUS_NO_MATCH, CALL_UNRESOLVED).map(PsiMethodCallExpression::getMethodExpression)) @@ -490,7 +493,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh } if (!hasErrorResults()) { try { - add(HighlightControlFlowUtil.checkVariableInitializedBeforeUsage(expression, variable, myUninitializedVarProblems, myFile)); + add(HighlightControlFlowUtil.checkVariableInitializedBeforeUsage(expression, variable, myUninitializedVarProblems)); } catch (IndexNotReadyException ignored) { } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java index 47ef9076bbc5..cdf105e3a878 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java @@ -47,7 +47,8 @@ import java.util.function.Consumer; import java.util.stream.Stream; import static com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds.*; -import static java.util.Objects.*; +import static java.util.Objects.requireNonNull; +import static java.util.Objects.requireNonNullElse; /** * Fixes attached to error messages provided by {@link JavaErrorCollector}. @@ -277,7 +278,6 @@ final class JavaErrorFixProvider { else if (element instanceof PsiClass cls) { sink.accept(myFactory.createCreateConstructorMatchingSuperFix(cls)); } - ErrorFixExtensionPoint.registerFixes(sink, element, "unhandled.exceptions"); }); fixes(EXCEPTION_UNHANDLED_CLOSE, (error, sink) -> HighlightFixUtil.registerUnhandledExceptionFixes(error.psi(), sink)); } @@ -375,6 +375,18 @@ final class JavaErrorFixProvider { fix(UNNAMED_VARIABLE_WITHOUT_INITIALIZER, error -> myFactory.createAddVariableInitializerFix(error.psi())); fixes(LVTI_NO_INITIALIZER, (error, sink) -> HighlightFixUtil.registerSpecifyVarTypeFix(error.psi(), sink)); fixes(LVTI_NULL, (error, sink) -> HighlightFixUtil.registerSpecifyVarTypeFix(error.psi(), sink)); + JavaFixProvider innerClassAccessFix = error -> { + PsiVariable variable = error.context(); + PsiElement scope = requireNonNull(ControlFlowUtil.getScopeEnforcingEffectiveFinality(variable, error.psi())); + return myFactory.createVariableAccessFromInnerClassFix(variable, scope); + }; + fix(VARIABLE_MUST_BE_FINAL, innerClassAccessFix); + fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL, innerClassAccessFix); + fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL_LAMBDA, innerClassAccessFix); + fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL_GUARD, innerClassAccessFix); + fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL, error -> myFactory.createMakeVariableEffectivelyFinalFix(error.context())); + fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL_LAMBDA, error -> myFactory.createMakeVariableEffectivelyFinalFix(error.context())); + fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL_GUARD, error -> myFactory.createMakeVariableEffectivelyFinalFix(error.context())); } private void createExpressionFixes() { diff --git a/java/java-analysis-impl/src/com/siyeh/ig/psiutils/FinalUtils.java b/java/java-analysis-impl/src/com/siyeh/ig/psiutils/FinalUtils.java index f4d11b6e4ce1..601829f036de 100644 --- a/java/java-analysis-impl/src/com/siyeh/ig/psiutils/FinalUtils.java +++ b/java/java-analysis-impl/src/com/siyeh/ig/psiutils/FinalUtils.java @@ -57,9 +57,10 @@ public final class FinalUtils { Map> finalVarProblems) { if (!(e instanceof PsiReferenceExpression ref)) return true; if (!ref.isReferenceTo(variable)) return true; - HighlightInfo.Builder highlightInfo = HighlightControlFlowUtil - .checkVariableInitializedBeforeUsage(ref, variable, uninitializedVarProblems, variable.getContainingFile(), true); - if (highlightInfo != null) return false; + if (!HighlightControlFlowUtil.isInitializedBeforeUsage( + ref, variable, uninitializedVarProblems, true)) { + return false; + } if (!PsiUtil.isAccessedForWriting(ref)) return true; if (!LocalsOrMyInstanceFieldsControlFlowPolicy.isLocalOrMyInstanceReference(ref)) return false; if (ControlFlowUtil.isVariableAssignedInLoop(ref, variable)) return false; diff --git a/java/java-impl/resources/META-INF/JavaPlugin.xml b/java/java-impl/resources/META-INF/JavaPlugin.xml index 6d3453cc439c..6bd34c9cddc1 100644 --- a/java/java-impl/resources/META-INF/JavaPlugin.xml +++ b/java/java-impl/resources/META-INF/JavaPlugin.xml @@ -939,7 +939,7 @@ - @@ -1580,9 +1580,9 @@ - - diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/BringVariableIntoScopeFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/BringVariableIntoScopeFix.java index 0034e75b2cd7..bd096677eb7d 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/BringVariableIntoScopeFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/BringVariableIntoScopeFix.java @@ -170,7 +170,7 @@ public final class BringVariableIntoScopeFix implements ModCommandAction { outOfScopeVariable.delete(); } - if (HighlightControlFlowUtil.checkVariableInitializedBeforeUsage(reference, addedVar, new HashMap<>(), file) != null) { + if (!HighlightControlFlowUtil.isInitializedBeforeUsage(reference, addedVar, new HashMap<>(), false)) { initialize(addedVar); } } diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/VariableAccessFromInnerClassFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/VariableAccessFromInnerClassFix.java index 2b5ef4e6ba7e..51b50fbcbc04 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/VariableAccessFromInnerClassFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/VariableAccessFromInnerClassFix.java @@ -317,10 +317,12 @@ public class VariableAccessFromInnerClassFix implements IntentionAction { Map> finalVarProblems = new HashMap<>(); for (PsiReferenceExpression expression : references) { if (ControlFlowUtil.isVariableAssignedInLoop(expression, variable)) return false; - HighlightInfo.Builder highlightInfo = HighlightControlFlowUtil.checkVariableInitializedBeforeUsage(expression, variable, uninitializedVarProblems, - variable.getContainingFile()); - if (highlightInfo != null) return false; - highlightInfo = HighlightControlFlowUtil.checkFinalVariableMightAlreadyHaveBeenAssignedTo(variable, expression, finalVarProblems); + if (!HighlightControlFlowUtil.isInitializedBeforeUsage( + expression, variable, uninitializedVarProblems, false)) { + return false; + } + HighlightInfo.Builder highlightInfo = + HighlightControlFlowUtil.checkFinalVariableMightAlreadyHaveBeenAssignedTo(variable, expression, finalVarProblems); if (highlightInfo != null) return false; if (variable instanceof PsiParameter && PsiUtil.isAccessedForWriting(expression)) return false; } diff --git a/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java b/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java index a8d6a27ba796..e84a3dc84bb1 100644 --- a/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java +++ b/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java @@ -25,7 +25,7 @@ public final class ControlFlowUtil { * @param variable variable * @param context the context that references to the variable * @return the scope around context that enforces variable to be effectively final. Currently, it could be - * an inner class, lambda expression, or switch guard. Returns null if there's no such scope, + * an inner class, lambda expression, or {@link PsiSwitchLabelStatementBase} for switch guard. Returns null if there's no such scope, * or the variable declaration is within the same scope, so it should not be effectively final. * Note that if null is returned, it doesn't mean that the variable could be modified, as another reference from * another place might exist. diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/EffectivelyFinal21.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/EffectivelyFinal21.java index d84faf9585d5..ffb94e9db251 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/EffectivelyFinal21.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/EffectivelyFinal21.java @@ -3,20 +3,20 @@ import java.util.function.*; class Test { void test1(Object o, int mode) { switch (o) { - case Integer i when i == mode -> System.out.println(); + case Integer i when i == mode -> System.out.println(); default -> {} } switch (o) { case Integer i when (switch (o) { - case Integer ii when ii != mode -> 2; + case Integer ii when ii != mode -> 2; default -> 1; - }) == mode -> System.out.println(); + }) == mode -> System.out.println(); default -> {} } switch (o) { - case Integer i when (i = mode) > 0 -> System.out.println(); + case Integer i when (i = mode) > 0 -> System.out.println(); default -> {} } mode = 0; @@ -24,7 +24,7 @@ class Test { void test2(Object o, final int mode) { switch (o) { - case Integer i when (switch (o) { + case Integer i when (switch (o) { case Integer ii when ii != mode -> 2; default -> 1; }) == mode -> o = null; @@ -60,7 +60,7 @@ class Test { switch (o) { case Integer mode when (mode = 42) > 9: switch (o) { - case Integer i when (i = mode) > 0 -> System.out.println(); + case Integer i when (i = mode) > 0 -> System.out.println(); default -> System.out.println(); } default : break; @@ -69,7 +69,7 @@ class Test { str = switch (o) { case Integer mode when (mode = 42) > 9 -> switch (o) { - case Integer i when (i = mode) > 0 -> ""; + case Integer i when (i = mode) > 0 -> ""; default -> ""; }; default -> ""; @@ -77,21 +77,21 @@ class Test { str = switch (o) { case Integer mode when (mode = 42) > 9: yield switch (o) { - case Integer i when (i = mode) > 0 -> ""; + case Integer i when (i = mode) > 0 -> ""; default -> ""; }; default: yield ""; }; // lambdas str = switch (o) { - case Integer i when (i = in) > 0: + case Integer i when (i = in) > 0: yield ((Function)(x) -> (in = 5) > 0 ? "" : null).apply(in); default: yield ""; }; Consumer c = (mode) -> { switch (o) { - case Integer i when (i = in) > 0 -> System.out.println(); + case Integer i when (i = in) > 0 -> System.out.println(); default -> System.out.println(); } in = 1; @@ -110,7 +110,7 @@ class Test { switch (o) { case Integer i -> { switch (o) { - case Integer ii when ii > mode: + case Integer ii when ii > mode: break; default: break; @@ -174,7 +174,7 @@ class Test { public static void testWhenReassigned() { Object object = "1234"; switch (object) { - case String s when s.length()==2 -> { + case String s when s.length()==2 -> { s = null; } default -> { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/SameVariableNameInPatternMatchingInSwitch.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/SameVariableNameInPatternMatchingInSwitch.java index fd11d490b8b1..a5def17174ca 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/SameVariableNameInPatternMatchingInSwitch.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/SameVariableNameInPatternMatchingInSwitch.java @@ -35,13 +35,13 @@ class Main { void nestedStatement(Object o, Object o2, int p) { int m = 0; switch (o) { - case Integer n when n < 1: + case Integer n when n < 1: n ++; case Integer n when n > 1: switch(o2) { - case Integer m when m > 0: + case Integer m when m > 0: m += n; - case Integer p when p > 0: + case Integer p when p > 0: p += n + m; break; case Integer p1: diff --git a/java/typeMigration/resources/META-INF/TypeMigration.xml b/java/typeMigration/resources/META-INF/TypeMigration.xml index 6ac7f38df346..d0663080f978 100644 --- a/java/typeMigration/resources/META-INF/TypeMigration.xml +++ b/java/typeMigration/resources/META-INF/TypeMigration.xml @@ -47,6 +47,7 @@ groupKey="group.names.language.level.specific.issues.and.migration.aids8" enabledByDefault="false" level="WARNING" implementationClass="com.intellij.refactoring.typeMigration.inspections.GuavaInspection" bundle="messages.TypeMigrationBundle" key="inspection.guava.name"/> - + diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/intentionDescription/resources/META-INF/plugin.xml b/plugins/devkit/devkit-java-tests/testData/inspections/intentionDescription/resources/META-INF/plugin.xml index d9888ae284e0..46dd0779041c 100644 --- a/plugins/devkit/devkit-java-tests/testData/inspections/intentionDescription/resources/META-INF/plugin.xml +++ b/plugins/devkit/devkit-java-tests/testData/inspections/intentionDescription/resources/META-INF/plugin.xml @@ -22,8 +22,7 @@ customDescriptionDirectory - + \ No newline at end of file diff --git a/plugins/lombok/src/main/resources/META-INF/plugin.xml b/plugins/lombok/src/main/resources/META-INF/plugin.xml index 6faa607f4220..346bf218a46f 100644 --- a/plugins/lombok/src/main/resources/META-INF/plugin.xml +++ b/plugins/lombok/src/main/resources/META-INF/plugin.xml @@ -83,7 +83,7 @@ - +