[java-intentions] VariableAccessFromInnerClassFix: adapt to 'when' expressions

IJ-CR-95276
IDEA-301356

GitOrigin-RevId: 7281c53c12f40840de36cc8d7e0c18e20ae8c463
This commit is contained in:
Andrey.Cherkasov
2022-10-13 13:54:43 +04:00
committed by intellij-monorepo-bot
parent d572da2539
commit 8b99ea7e9c
16 changed files with 139 additions and 28 deletions

View File

@@ -644,12 +644,12 @@ public final class HighlightControlFlowUtil {
String description = JavaErrorBundle.message("assignment.to.final.variable", name);
HighlightInfo highlightInfo =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(reference).descriptionAndTooltip(description).create();
PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, expression);
if (innerClass == null || variable instanceof PsiField) {
PsiElement scope = getElementVariableReferencedFrom(variable, expression);
if (scope == null || variable instanceof PsiField) {
HighlightFixUtil.registerMakeNotFinalAction(variable, highlightInfo);
}
else {
QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, innerClass));
QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, scope));
}
return highlightInfo;
}
@@ -660,13 +660,13 @@ public final class HighlightControlFlowUtil {
@NotNull PsiFile containingFile) {
if (variable.hasInitializer()) return false;
if (variable instanceof PsiParameter) return false;
PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, expression);
PsiElement scope = getElementVariableReferencedFrom(variable, expression);
if (variable instanceof PsiField) {
// if inside some field initializer
if (HighlightUtil.findEnclosingFieldInitializer(expression) != null) return true;
// assignment from within inner class is illegal always
PsiField field = (PsiField)variable;
if (innerClass != null && !containingFile.getManager().areElementsEquivalent(innerClass, field.getContainingClass())) return false;
if (scope != null && !containingFile.getManager().areElementsEquivalent(scope, field.getContainingClass())) return false;
PsiMember enclosingCtrOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expression);
return enclosingCtrOrInitializer != null &&
!(enclosingCtrOrInitializer instanceof PsiMethod &&
@@ -674,7 +674,7 @@ public final class HighlightControlFlowUtil {
isSameField(enclosingCtrOrInitializer, field, reference, containingFile);
}
if (variable instanceof PsiLocalVariable) {
boolean isAccessedFromOtherClass = innerClass != null;
boolean isAccessedFromOtherClass = scope != null;
return !isAccessedFromOtherClass;
}
return true;
@@ -693,8 +693,8 @@ public final class HighlightControlFlowUtil {
@NotNull PsiJavaCodeReferenceElement context,
@NotNull LanguageLevel languageLevel) {
if (variable.hasModifierProperty(PsiModifier.FINAL)) return null;
PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, context);
if (innerClass instanceof PsiClass) {
PsiElement scope = getElementVariableReferencedFrom(variable, context);
if (scope instanceof PsiClass) {
if (variable instanceof PsiParameter) {
PsiElement parent = variable.getParent();
if (parent instanceof PsiParameterList && parent.getParent() instanceof PsiLambdaExpression &&
@@ -703,7 +703,7 @@ public final class HighlightControlFlowUtil {
}
}
boolean isToBeEffectivelyFinal = languageLevel.isAtLeast(LanguageLevel.JDK_1_8);
if (isToBeEffectivelyFinal && isEffectivelyFinal(variable, innerClass, context)) {
if (isToBeEffectivelyFinal && isEffectivelyFinal(variable, scope, context)) {
return null;
}
String description = JavaErrorBundle
@@ -711,7 +711,7 @@ public final class HighlightControlFlowUtil {
HighlightInfo highlightInfo =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context).descriptionAndTooltip(description).create();
QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, innerClass));
QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, scope));
return highlightInfo;
}
HighlightInfo finalInsideLambdaInfo = checkWriteToFinalInsideLambda(variable, context);
@@ -754,11 +754,10 @@ public final class HighlightControlFlowUtil {
if (refGuardedPattern == null) return null;
PsiCaseLabelElement varGuardedPattern = PsiTreeUtil.getParentOfType(variable, PsiGuardedPattern.class, PsiPatternGuard.class);
if (refGuardedPattern != varGuardedPattern && !isEffectivelyFinal(variable, refGuardedPattern, context)) {
HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context)
.descriptionAndTooltip(JavaErrorBundle.message("guarded.pattern.variable.must.be.final")).create();
// todo quick-fix may be registered here, but
// todo com.intellij.codeInsight.intention.QuickFixFactory.createVariableAccessFromInnerClassFix should be fix beforehand
return ErrorFixExtensionPoint.registerFixes(highlightInfo, context, "guarded.pattern.variable.must.be.final");
String message = JavaErrorBundle.message("guarded.pattern.variable.must.be.final");
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context).descriptionAndTooltip(message).create();
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, refGuardedPattern));
return ErrorFixExtensionPoint.registerFixes(info, context, "guarded.pattern.variable.must.be.final");
}
return null;
}
@@ -809,17 +808,22 @@ public final class HighlightControlFlowUtil {
return effectivelyFinal;
}
public static PsiElement getInnerClassVariableReferencedFrom(@NotNull PsiVariable variable, @NotNull PsiElement context) {
/**
* @param variable variable
* @param context the context that reference to the variable
* @return inner class, lambda expression, or guarded pattern that refers to the variable
*/
public static @Nullable PsiElement getElementVariableReferencedFrom(@NotNull PsiVariable variable, @NotNull PsiElement context) {
PsiElement[] scope;
if (variable instanceof PsiResourceVariable) {
scope = ((PsiResourceVariable)variable).getDeclarationScope();
if (variable instanceof PsiResourceVariable resourceVariable) {
scope = resourceVariable.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 if (variable instanceof PsiParameter parameter) {
scope = new PsiElement[]{parameter.getDeclarationScope()};
}
else {
scope = new PsiElement[]{variable.getParent()};
@@ -838,6 +842,9 @@ public final class HighlightControlFlowUtil {
if (parent instanceof PsiLambdaExpression) {
return parent;
}
if (parent instanceof PsiGuardedPattern || parent instanceof PsiPatternGuard) {
return parent;
}
prevParent = parent;
parent = parent.getParent();
}

View File

@@ -63,7 +63,7 @@ public final class ReassignVariableUtil {
PsiElement outerCodeBlock = PsiUtil.getVariableCodeBlock(variable, null);
if (outerCodeBlock == null) continue;
if (ReferencesSearch.search(variable, new LocalSearchScope(outerCodeBlock))
.allMatch(reference -> HighlightControlFlowUtil.getInnerClassVariableReferencedFrom(variable, reference.getElement()) == null)) {
.allMatch(reference -> HighlightControlFlowUtil.getElementVariableReferencedFrom(variable, reference.getElement()) == null)) {
vars.add(variable);
}
}

View File

@@ -38,8 +38,8 @@ public class VariableAccessFromInnerClassFix implements IntentionAction {
@FixType
private final int myFixType;
private static final int MAKE_FINAL = 0;
private static final int MAKE_ARRAY = 1;
private static final int COPY_TO_FINAL = 2;
private static final int COPY_TO_FINAL = 1;
private static final int MAKE_ARRAY = 2;
private static final int UNKNOWN = -1;
@MagicConstant(intValues = {MAKE_FINAL, COPY_TO_FINAL, MAKE_ARRAY, UNKNOWN})
@interface FixType { }
@@ -235,7 +235,7 @@ public class VariableAccessFromInnerClassFix implements IntentionAction {
}
PsiElement element = statement;
while (element != declarationScope && !(element instanceof PsiFile)) {
if (element instanceof PsiClass || element instanceof PsiLambdaExpression) {
if (element instanceof PsiClass || element instanceof PsiLambdaExpression || element instanceof PsiSwitchLabelStatementBase) {
statement = statement.getParent();
continue nextInnerClass;
}
@@ -299,7 +299,7 @@ public class VariableAccessFromInnerClassFix implements IntentionAction {
int type = MAKE_FINAL;
for (PsiReferenceExpression expression : outerReferences) {
// if it happens that variable referenced from another inner class, make sure it can be make final from there
PsiElement innerScope = HighlightControlFlowUtil.getInnerClassVariableReferencedFrom(variable, expression);
PsiElement innerScope = HighlightControlFlowUtil.getElementVariableReferencedFrom(variable, expression);
if (innerScope != null) {
@FixType int thisType = MAKE_FINAL;

View File

@@ -295,7 +295,7 @@ public class VariableLookupItem extends LookupItem<PsiVariable> implements Typed
return;
}
if (HighlightControlFlowUtil.getInnerClassVariableReferencedFrom(variable, place) != null &&
if (HighlightControlFlowUtil.getElementVariableReferencedFrom(variable, place) != null &&
!HighlightControlFlowUtil.isReassigned(variable, new HashMap<>())) {
PsiUtil.setModifierProperty(variable, PsiModifier.FINAL, true);
}

View File

@@ -0,0 +1,10 @@
// "Make 'i' not final" "false"
class Main {
void foo(Object obj) {
final int i = 41;
switch (obj) {
case String s when s.length() == ++i<caret> -> {}
default -> {}
}
}
}

View File

@@ -0,0 +1,9 @@
// "Transform 'i' into final one element array" "true-preview"
class Main {
void test() {
final int[] i = {42};
Runnable runnable1 = () -> System.out.println(i[0]);
Runnable runnable2 = () -> System.out.println(i[0]++);
i[0] = 0;
}
}

View File

@@ -0,0 +1,15 @@
// "Copy 'i' to effectively final temp variable" "true-preview"
class Main {
void foo(Object obj) {
int i = 42;
int finalI = i;
switch (obj) {
case String s when switch ((Object) s.length()) {
case Integer integer when integer == finalI -> 0;
default -> 42;
} == 42 -> {}
default -> {}
}
i = 0;
}
}

View File

@@ -0,0 +1,13 @@
// "Transform 'i' into final one element array" "true-preview"
class Main {
void foo(Object obj) {
final int[] i = {42};
switch (obj) {
case String s when switch ((Object) s.length()) {
case Integer integer when integer == ++i[0] -> 0;
default -> 42;
} == 42 -> {}
default -> {}
}
}
}

View File

@@ -0,0 +1,11 @@
// "Copy 'i' to effectively final temp variable" "true-preview"
class Main {
void foo(int i) {
int finalI = i;
switch (i) {
case 42 -> () -> System.out.println(finalI)
default -> {}
}
i = 0;
}
}

View File

@@ -0,0 +1,9 @@
// "Transform 'i' into final one element array" "true-preview"
class Main {
void test() {
int i = 42;
Runnable runnable1 = () -> System.out.println(i);
Runnable runnable2 = () -> System.out.println(i<caret>++);
i = 0;
}
}

View File

@@ -0,0 +1,14 @@
// "Copy 'i' to effectively final temp variable" "true-preview"
class Main {
void foo(Object obj) {
int i = 42;
switch (obj) {
case String s when switch ((Object) s.length()) {
case Integer integer when integer == i<caret> -> 0;
default -> 42;
} == 42 -> {}
default -> {}
}
i = 0;
}
}

View File

@@ -0,0 +1,13 @@
// "Transform 'i' into final one element array" "true-preview"
class Main {
void foo(Object obj) {
int i = 42;
switch (obj) {
case String s when switch ((Object) s.length()) {
case Integer integer when integer == ++i<caret> -> 0;
default -> 42;
} == 42 -> {}
default -> {}
}
}
}

View File

@@ -0,0 +1,10 @@
// "Copy 'i' to effectively final temp variable" "true-preview"
class Main {
void foo(int i) {
switch (i) {
case 42 -> () -> System.out.println(i<caret>)
default -> {}
}
i = 0;
}
}

View File

@@ -58,8 +58,8 @@ public final class FinalUtils {
if (ControlFlowUtil.isVariableAssignedInLoop(ref, variable)) return false;
if (variable instanceof PsiField) {
if (PsiUtil.findEnclosingConstructorOrInitializer(ref) == null) return false;
PsiElement innerClass = HighlightControlFlowUtil.getInnerClassVariableReferencedFrom(variable, ref);
if (innerClass != null && innerClass != ((PsiField)variable).getContainingClass()) return false;
PsiElement innerScope = HighlightControlFlowUtil.getElementVariableReferencedFrom(variable, ref);
if (innerScope != null && innerScope != ((PsiField)variable).getContainingClass()) return false;
}
return HighlightControlFlowUtil.checkFinalVariableMightAlreadyHaveBeenAssignedTo(variable, ref, finalVarProblems) == null;
};