[java][resolve] IDEA-271676 Pattern matching for switch: fix resolve

Fix resolve in PsiSwitchLabelStatementImpl.

GitOrigin-RevId: 88d00bf2c7d08f2e3e4950218bcada69d55b0772
This commit is contained in:
Nikita Eshkeev
2021-07-02 20:09:05 +00:00
committed by intellij-monorepo-bot
parent 396b542c59
commit 2ca73ddb92
5 changed files with 106 additions and 14 deletions

View File

@@ -12,6 +12,9 @@ import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.atomic.AtomicBoolean;
public class PsiSwitchLabelStatementImpl extends PsiSwitchLabelStatementBaseImpl implements PsiSwitchLabelStatement {
private static final Logger LOG = Logger.getInstance(PsiSwitchLabelStatementImpl.class);
@@ -61,14 +64,11 @@ public class PsiSwitchLabelStatementImpl extends PsiSwitchLabelStatementBaseImpl
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@Nullable PsiElement lastParent,
@NotNull PsiElement place) {
if (!super.processDeclarations(processor, state, lastParent, place)) return false;
// Do not resolve references that come from the list of elements in this case rule
if (lastParent instanceof PsiCaseLabelElementList) return true;
if (isNotImmediateSwitchLabel()) return true;
if (!shouldAnalyzePatternVariablesInCaseLabel(place)) return true;
final PsiCaseLabelElementList patternsInCaseLabel = getCaseLabelElementList();
if (patternsInCaseLabel == null) return true;
@@ -77,14 +77,33 @@ public class PsiSwitchLabelStatementImpl extends PsiSwitchLabelStatementBaseImpl
}
/**
* When the resolving is happening inside a {@link PsiCodeBlock} it traverses through all the case labels,
* which is not what is expected for pattern variables, because their scope is bound only to the nearest case handler.
* The method checks if this {@link PsiSwitchLabelStatement} is the nearest one to the case handler.
*
* @return true if the this {@link PsiSwitchLabelStatement} is followed by another {@link PsiSwitchLabelStatement}, false otherwise
* Checks if the pattern variables in the case label list can be analyzed. The processing is allowed in the two following cases:
* <ol>
* <li>The place that is being resolved is a code block. It just wants to build the map of the local and pattern variables in {@code PsiCodeBlockImpl#buildMaps}.</li>
* <li>The current {@link PsiSwitchLabelStatement} is the switch label where it's legal to resolve the passed element.
* <p>
* Read more about scopes of variables in pattern matching for switch statements in
* <a href="https://openjdk.java.net/jeps/406#3--Scope-of-pattern-variable-declarations">the JEP</a>.
* </p>
* </li>
* </ol>
* @param place element that is being resolved
* @return true when the pattern variables in the case label list can be analyzed.
*/
private boolean isNotImmediateSwitchLabel() {
final PsiElement rightNeighbour = PsiTreeUtil.skipWhitespacesForward(this);
return rightNeighbour instanceof PsiSwitchLabelStatement;
private boolean shouldAnalyzePatternVariablesInCaseLabel(@NotNull PsiElement place) {
if (place instanceof PsiCodeBlock) return true;
final AtomicBoolean thisSwitchLabelIsImmediate = new AtomicBoolean();
PsiTreeUtil.treeWalkUp(place, getParent(), (currentScope, __) -> {
final PsiElement sibling = PsiTreeUtil.skipWhitespacesBackward(currentScope.getPrevSibling());
if (sibling == this) {
thisSwitchLabelIsImmediate.set(true);
return false;
}
return true;
});
return thisSwitchLabelIsImmediate.get();
}
}

View File

@@ -0,0 +1,53 @@
class Main {
void statement(Object o) {
switch (o) {
case Integer n && n > 1:
break;
case Integer n && n < 1:
break;
default:
break;
}
}
int expression(Object o) {
return switch (o) {
case Integer n && n > 1 -> n;
case Integer n && n < 1 -> n;
default -> 0;
};
}
int nestedExpression(Object o, Object o2, int p) {
int m = 0;
return switch (o) {
case Integer n && n > 1 -> switch(o2) {
case Integer <error descr="Variable 'm' is already defined in the scope">m</error> && m > 0 -> m + n;
case Integer <error descr="Variable 'p' is already defined in the scope">p</error> -> p + n;
default -> 0;
};
case Integer n && n < 1 -> n;
default -> 0;
};
}
void nestedStatement(Object o, Object o2, int p) {
int m = 0;
switch (o) {
case Integer n && n < 1:
n ++;
case Integer n && n > 1:
switch(o2) {
case Integer <error descr="Variable 'm' is already defined in the scope">m</error> && m > 0:
m += n;
case Integer <error descr="Variable 'p' is already defined in the scope">p</error> && p > 0:
p += n + m;
break;
case Integer p1:
p += n + m + p1;
};
break;
};
}
}

View File

@@ -0,0 +1,12 @@
class Main {
final int i = 2;
void f(Object obj) {
switch (obj) {
case Integer i: break;
case i<caret>:
System.out.println(i);
}
}
}
}

View File

@@ -44,8 +44,12 @@ public class LightPatternsForSwitchHighlightingTest extends LightJavaCodeInsight
doTest();
}
public void testSameVariableNameInPatternMatchingInSwitch() {
doTest();
}
private void doTest() {
myFixture.configureByFile(getTestName(false) + ".java");
myFixture.checkHighlighting();
}
}
}

View File

@@ -130,6 +130,10 @@ public class GotoDeclarationTest extends LightJavaCodeInsightTestCase {
doTestPatternMatchingGuard();
}
public void testReferenceFieldInPatternMatchingInSwitchStatement() {
doTestPatternMatchingGuard();
}
private void doTestPatternMatchingGuard() {
String name = getTestName(false);
configureByFile("/codeInsight/gotoDeclaration/" + name + ".java");