[java] IDEA-257411 Update pattern matching for instanceof support for Java 16

1. Do not report error if non-final variable is reassigned
2. Report error in Java 15 if 'final' modifier is used
3. Allow specifying 'final' modifier on introduce variable
4. Support non-final variables in PatternVariableCanBeUsed inspection
5. Copy modifiers in PatternVariableCanBeUsed quick-fix

GitOrigin-RevId: d7b82261a018c9a48bcdcf237ade0d8c08f5978d
This commit is contained in:
Tagir Valeev
2021-01-26 12:28:12 +07:00
committed by intellij-monorepo-bot
parent bc640063cd
commit 6fe2638869
23 changed files with 162 additions and 23 deletions

View File

@@ -611,23 +611,19 @@ public final class HighlightControlFlowUtil {
}
PsiReferenceExpression reference = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(operand), PsiReferenceExpression.class);
PsiVariable variable = reference == null ? null : ObjectUtils.tryCast(reference.resolve(), PsiVariable.class);
if (!(variable instanceof PsiPatternVariable)) {
if (variable == null || !variable.hasModifierProperty(PsiModifier.FINAL)) return null;
final boolean canWrite = canWriteToFinal(variable, expression, reference, containingFile) && checkWriteToFinalInsideLambda(variable, reference) == null;
if (canWrite) return null;
}
if (variable == null || !variable.hasModifierProperty(PsiModifier.FINAL)) return null;
final boolean canWrite = canWriteToFinal(variable, expression, reference, containingFile) && checkWriteToFinalInsideLambda(variable, reference) == null;
if (canWrite) return null;
final String name = variable.getName();
String description = JavaErrorBundle.message("assignment.to.final.variable", name);
final HighlightInfo highlightInfo =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(reference).descriptionAndTooltip(description).create();
if (!(variable instanceof PsiPatternVariable)) {
final PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, expression);
if (innerClass == null || variable instanceof PsiField) {
HighlightFixUtil.registerMakeNotFinalAction(variable, highlightInfo);
}
else {
QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, innerClass));
}
final PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, expression);
if (innerClass == null || variable instanceof PsiField) {
HighlightFixUtil.registerMakeNotFinalAction(variable, highlightInfo);
}
else {
QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, innerClass));
}
return highlightInfo;
}

View File

@@ -1065,7 +1065,8 @@ public final class HighlightUtil {
isAllowed = PsiModifier.STATIC.equals(modifier);
}
else if (modifierOwner instanceof PsiLocalVariable || modifierOwner instanceof PsiParameter) {
isAllowed = PsiModifier.FINAL.equals(modifier);
isAllowed = PsiModifier.FINAL.equals(modifier) &&
(!(modifierOwner instanceof PsiPatternVariable) || PsiUtil.isLanguageLevel16OrHigher(modifierOwner));
}
else if (modifierOwner instanceof PsiReceiverParameter || modifierOwner instanceof PsiRecordComponent) {
isAllowed = false;

View File

@@ -1381,7 +1381,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (resolved instanceof PsiVariable && resolved.getContainingFile() == expression.getContainingFile()) {
PsiVariable variable = (PsiVariable)resolved;
boolean isFinal = variable.hasModifierProperty(PsiModifier.FINAL);
if (isFinal && !variable.hasInitializer()) {
if (isFinal && !variable.hasInitializer() && !(variable instanceof PsiPatternVariable)) {
if (!myHolder.hasErrorResults()) {
myHolder.add(HighlightControlFlowUtil.checkFinalVariableMightAlreadyHaveBeenAssignedTo(variable, expression, myFinalVarProblems));
}

View File

@@ -36,7 +36,8 @@ public class PatternVariableCanBeUsedInspection extends AbstractBaseJavaLocalIns
if (scope == null) return;
PsiDeclarationStatement declaration = ObjectUtils.tryCast(variable.getParent(), PsiDeclarationStatement.class);
if (declaration == null) return;
if (!variable.hasModifierProperty(PsiModifier.FINAL) &&
if (!PsiUtil.isLanguageLevel16OrHigher(holder.getFile()) &&
!variable.hasModifierProperty(PsiModifier.FINAL) &&
!HighlightControlFlowUtil.isEffectivelyFinal(variable, scope, null)) return;
PsiInstanceOfExpression instanceOf = InstanceOfUtils.findPatternCandidate(cast);
if (instanceOf != null) {
@@ -54,7 +55,7 @@ public class PatternVariableCanBeUsedInspection extends AbstractBaseJavaLocalIns
private final SmartPsiElementPointer<PsiInstanceOfExpression> myInstanceOfPointer;
private final String myName;
public PatternVariableCanBeUsedFix(@NotNull String name, @NotNull PsiInstanceOfExpression instanceOf) {
private PatternVariableCanBeUsedFix(@NotNull String name, @NotNull PsiInstanceOfExpression instanceOf) {
myName = name;
myInstanceOfPointer = SmartPointerManager.createPointer(instanceOf);
}
@@ -85,7 +86,11 @@ public class PatternVariableCanBeUsedInspection extends AbstractBaseJavaLocalIns
PsiInstanceOfExpression instanceOf = myInstanceOfPointer.getElement();
if (instanceOf == null) return;
CommentTracker ct = new CommentTracker();
ct.replace(instanceOf, ct.text(instanceOf.getOperand()) + " instanceof " + typeElement.getText() + " " + variable.getName());
PsiModifierList modifierList = variable.getModifierList();
String modifiers = modifierList == null || modifierList.getTextLength() == 0 || !PsiUtil.isLanguageLevel16OrHigher(variable) ?
"" : modifierList.getText() + " ";
ct.replace(instanceOf, ct.text(instanceOf.getOperand()) +
" instanceof " + modifiers + typeElement.getText() + " " + variable.getName());
ct.deleteAndRestoreComments(variable);
}

View File

@@ -40,6 +40,7 @@ import com.intellij.psi.scope.processor.VariablesProcessor;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.JavaRefactoringSettings;
import com.intellij.refactoring.RefactoringActionHandler;
@@ -249,7 +250,7 @@ public class JavaVariableInplaceIntroducer extends AbstractJavaInplaceIntroducer
@Override
@Nullable
protected JComponent getComponent() {
if (getVariable() instanceof PsiPatternVariable) return null;
if (getVariable() instanceof PsiPatternVariable && !PsiUtil.isLanguageLevel16OrHigher(getVariable())) return null;
if (myCantChangeFinalModifier && !(myCanBeVarType && getVariable() instanceof PsiLocalVariable)) return null;
if (!myCantChangeFinalModifier) {
myCanBeFinalCb = new NonFocusableCheckBox(JavaRefactoringBundle.message("declare.final"));

View File

@@ -112,8 +112,10 @@ final class VariableExtractor {
highlight(var);
if (!(var instanceof PsiPatternVariable)) {
if (!(var instanceof PsiPatternVariable) || PsiUtil.isLanguageLevel16OrHigher(myContainer.getContainingFile())) {
PsiUtil.setModifierProperty(var, PsiModifier.FINAL, mySettings.isDeclareFinal());
}
if (!(var instanceof PsiPatternVariable)) {
if (mySettings.isDeclareVarType()) {
PsiTypeElement typeElement = var.getTypeElement();
LOG.assertTrue(typeElement != null);

View File

@@ -15,6 +15,7 @@ import com.intellij.psi.impl.source.tree.CompositeElement;
import com.intellij.psi.impl.source.tree.Factory;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.BitUtil;
import com.intellij.util.IncorrectOperationException;
@@ -186,6 +187,9 @@ public class PsiModifierListImpl extends JavaStubPsiElement<PsiModifierListStub>
else if (parent instanceof PsiResourceVariable) {
Collections.addAll(implicitModifiers, FINAL);
}
else if (parent instanceof PsiPatternVariable && !PsiUtil.isLanguageLevel16OrHigher(parent)) {
Collections.addAll(implicitModifiers, FINAL);
}
return implicitModifiers;
}

View File

@@ -1,6 +1,9 @@
class X {
void expressions(Object obj) {
if (obj instanceof String s) {
s = "foo";
}
if (obj instanceof final String s) {
<error descr="Cannot assign a value to final variable 's'">s</error> = "foo";
}
}

View File

@@ -0,0 +1,10 @@
class X {
void expressions(Object obj) {
if (obj instanceof String s) {
<error descr="Cannot assign a value to final variable 's'">s</error> = "foo";
}
if (obj instanceof <error descr="Modifier 'final' not allowed here">final</error> String s) {
<error descr="Cannot assign a value to final variable 's'">s</error> = "foo";
}
}
}

View File

@@ -0,0 +1,9 @@
// "Replace 's' with pattern variable" "true"
class X {
void test(Object obj) {
if (obj instanceof @Foo String s) {
}
}
@interface Foo {}
}

View File

@@ -0,0 +1,7 @@
// "Replace 's' with pattern variable" "true"
class X {
void test(Object obj) {
if (obj instanceof final String s) {
}
}
}

View File

@@ -0,0 +1,9 @@
// "Replace 's' with pattern variable" "true"
class X {
void test(Object obj) {
if (obj instanceof String s) {
System.out.println(s);
s = s.trim();
}
}
}

View File

@@ -0,0 +1,10 @@
// "Replace 's' with pattern variable" "true"
class X {
void test(Object obj) {
if (obj instanceof String) {
@Foo String <caret>s = (String)obj;
}
}
@interface Foo {}
}

View File

@@ -0,0 +1,8 @@
// "Replace 's' with pattern variable" "true"
class X {
void test(Object obj) {
if (obj instanceof String) {
final String <caret>s = (String)obj;
}
}
}

View File

@@ -1,4 +1,4 @@
// "Replace 's' with pattern variable" "false"
// "Replace 's' with pattern variable" "true"
class X {
void test(Object obj) {
if (obj instanceof String) {

View File

@@ -0,0 +1,9 @@
// "Replace 's' with pattern variable" "true"
class X {
void test(Object obj) {
if (obj instanceof String s) {
}
}
@interface Foo {}
}

View File

@@ -0,0 +1,7 @@
// "Replace 's' with pattern variable" "true"
class X {
void test(Object obj) {
if (obj instanceof String s) {
}
}
}

View File

@@ -0,0 +1,10 @@
// "Replace 's' with pattern variable" "true"
class X {
void test(Object obj) {
if (obj instanceof String) {
@Foo String <caret>s = (String)obj;
}
}
@interface Foo {}
}

View File

@@ -0,0 +1,8 @@
// "Replace 's' with pattern variable" "true"
class X {
void test(Object obj) {
if (obj instanceof String) {
final String <caret>s = (String)obj;
}
}
}

View File

@@ -0,0 +1,10 @@
// "Replace 's' with pattern variable" "false"
class X {
void test(Object obj) {
if (obj instanceof String) {
String <caret>s = (String)obj;
System.out.println(s);
s = s.trim();
}
}
}

View File

@@ -36,9 +36,12 @@ public class LightPatternsHighlightingTest extends LightJavaCodeInsightFixtureTe
public void testInstanceOfInSwitch() {
doTest();
}
public void testReassignPatternVariable() {
public void testReassignPatternVariableJava15() {
doTest();
}
public void testReassignPatternVariable() {
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_16, this::doTest);
}
public void testUnusedPatternVariable() {
myFixture.enableInspections(new UnusedDeclarationInspection());
doTest();

View File

@@ -0,0 +1,27 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.java.codeInspection;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.PatternVariableCanBeUsedInspection;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
public class PatternVariableCanBeUsedInspection15Test extends LightQuickFixParameterizedTestCase {
@Override
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new PatternVariableCanBeUsedInspection()};
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return LightJavaCodeInsightFixtureTestCase.JAVA_15;
}
@Override
protected String getBasePath() {
return "/inspection/patternVariableCanBeUsed15";
}
}

View File

@@ -17,7 +17,7 @@ public class PatternVariableCanBeUsedInspectionTest extends LightQuickFixParamet
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return LightJavaCodeInsightFixtureTestCase.JAVA_15;
return LightJavaCodeInsightFixtureTestCase.JAVA_16;
}
@Override