IDEA-229846 Resolve for pattern matching (except switch handling)

GitOrigin-RevId: b8addebac00f681641d5cef1089fa6f7d2d668a7
This commit is contained in:
Tagir Valeev
2019-12-27 13:48:40 +07:00
committed by intellij-monorepo-bot
parent 14183dd8a9
commit 0df312338d
21 changed files with 685 additions and 23 deletions

View File

@@ -38,6 +38,7 @@ import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl;
import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.scope.PatternResolveState;
import com.intellij.psi.scope.processor.VariablesNotProcessor;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.search.GlobalSearchScope;
@@ -602,7 +603,7 @@ public class HighlightUtil extends HighlightUtilBase {
if (variable instanceof ExternallyDefinedPsiElement) return null;
PsiVariable oldVariable = null;
PsiElement declarationScope = null;
if (variable instanceof PsiLocalVariable ||
if (variable instanceof PsiLocalVariable || variable instanceof PsiPatternVariable ||
variable instanceof PsiParameter &&
((declarationScope = ((PsiParameter)variable).getDeclarationScope()) instanceof PsiCatchSection ||
declarationScope instanceof PsiForeachStatement ||
@@ -611,7 +612,8 @@ public class HighlightUtil extends HighlightUtilBase {
VariablesNotProcessor proc = new VariablesNotProcessor(variable, false) {
@Override
protected boolean check(final PsiVariable var, final ResolveState state) {
return (var instanceof PsiLocalVariable || var instanceof PsiParameter) && super.check(var, state);
return (var instanceof PsiLocalVariable || var instanceof PsiParameter || var instanceof PsiPatternVariable) &&
super.check(var, state);
}
};
PsiIdentifier identifier = variable.getNameIdentifier();
@@ -627,6 +629,9 @@ public class HighlightUtil extends HighlightUtilBase {
else if (declarationScope instanceof PsiLambdaExpression) {
oldVariable = checkSameNames(variable);
}
else if (variable instanceof PsiPatternVariable) {
oldVariable = checkSamePatternVariableInBranches((PsiPatternVariable)variable);
}
}
else if (variable instanceof PsiField) {
PsiField field = (PsiField)variable;
@@ -666,6 +671,54 @@ public class HighlightUtil extends HighlightUtilBase {
return null;
}
private static PsiPatternVariable checkSamePatternVariableInBranches(PsiPatternVariable variable) {
PsiPattern pattern = variable.getPattern();
if (pattern == null) return null;
PatternResolveState hint = PatternResolveState.WHEN_TRUE;
VariablesNotProcessor proc = new VariablesNotProcessor(variable, false) {
@Override
protected boolean check(final PsiVariable var, final ResolveState state) {
return var instanceof PsiPatternVariable && super.check(var, state);
}
};
PsiElement lastParent = pattern;
for (PsiElement parent = lastParent.getParent(); parent != null; lastParent = parent, parent = parent.getParent()) {
if (parent instanceof PsiInstanceOfExpression || parent instanceof PsiParenthesizedExpression) continue;
if (parent instanceof PsiPrefixExpression && ((PsiPrefixExpression)parent).getOperationTokenType().equals(JavaTokenType.EXCL)) {
hint = hint.invert();
continue;
}
if (parent instanceof PsiPolyadicExpression) {
IElementType tokenType = ((PsiPolyadicExpression)parent).getOperationTokenType();
if (tokenType.equals(JavaTokenType.ANDAND) || tokenType.equals(JavaTokenType.OROR)) {
PatternResolveState targetHint = PatternResolveState.fromBoolean(tokenType.equals(JavaTokenType.OROR));
if (hint == targetHint) {
for (PsiExpression operand : ((PsiPolyadicExpression)parent).getOperands()) {
if (operand == lastParent) break;
operand.processDeclarations(proc, hint.putInto(ResolveState.initial()), null, pattern);
}
}
continue;
}
}
if (parent instanceof PsiConditionalExpression) {
PsiConditionalExpression conditional = (PsiConditionalExpression)parent;
PsiExpression thenExpression = conditional.getThenExpression();
if (lastParent == thenExpression) {
conditional.getCondition().processDeclarations(proc, PatternResolveState.WHEN_FALSE.putInto(ResolveState.initial()), null, pattern);
}
else if (lastParent == conditional.getElseExpression()) {
conditional.getCondition().processDeclarations(proc, PatternResolveState.WHEN_TRUE.putInto(ResolveState.initial()), null, pattern);
if (thenExpression != null) {
thenExpression.processDeclarations(proc, hint.putInto(ResolveState.initial()), null, pattern);
}
}
}
break;
}
return proc.size() > 0 ? (PsiPatternVariable)proc.getResult(0) : null;
}
private static PsiVariable checkSameNames(@NotNull PsiVariable variable) {
PsiElement scope = variable.getParent();
PsiElement[] children = scope.getChildren();

View File

@@ -1,5 +1,17 @@
// Copyright 2000-2019 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.psi;
public interface PsiPatternVariable extends PsiLocalVariable {
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A variable declared within the pattern
*/
public interface PsiPatternVariable extends PsiVariable {
@NotNull
@Override
PsiTypeElement getTypeElement();
@Nullable
PsiPattern getPattern();
}

View File

@@ -351,6 +351,7 @@ public class ControlFlowUtil {
return outputVariables;
}
@SafeVarargs
@NotNull
public static Collection<PsiStatement> findExitPointsAndStatements(@NotNull ControlFlow flow, final int start, final int end,
@NotNull IntArrayList exitPoints,
@@ -360,7 +361,7 @@ public class ControlFlowUtil {
return Collections.emptyList();
}
final Collection<PsiStatement> exitStatements = new THashSet<>();
InstructionClientVisitor visitor = new InstructionClientVisitor() {
InstructionClientVisitor<Void> visitor = new InstructionClientVisitor<Void>() {
@Override
public void visitThrowToInstruction(ThrowToInstruction instruction, int offset, int nextOffset) {
//[ven]This is a hack since Extract Method doesn't want to see throw's exit points
@@ -398,7 +399,7 @@ public class ControlFlowUtil {
}
@Override
public Object getResult() {
public Void getResult() {
return null;
}
};
@@ -406,11 +407,12 @@ public class ControlFlowUtil {
return exitStatements;
}
@SafeVarargs
private static void processGoto(@NotNull ControlFlow flow, int start, int end,
@NotNull IntArrayList exitPoints,
@NotNull Collection<? super PsiStatement> exitStatements,
@NotNull BranchingInstruction instruction,
final PsiStatement statement, @NotNull Class... classesFilter) {
final PsiStatement statement, @NotNull Class<? extends PsiStatement>... classesFilter) {
if (statement == null) return;
int gotoOffset = instruction.offset;
if (start > gotoOffset || gotoOffset >= end || isElementOfClass(statement, classesFilter)) {
@@ -434,15 +436,17 @@ public class ControlFlowUtil {
}
}
@SafeVarargs
private static void processGotoStatement(@NotNull Collection<? super PsiStatement> exitStatements,
PsiStatement statement, @NotNull Class... classesFilter) {
PsiStatement statement, @NotNull Class<? extends PsiStatement>... classesFilter) {
if (statement != null && isElementOfClass(statement, classesFilter)) {
exitStatements.add(statement);
}
}
private static boolean isElementOfClass(@NotNull PsiElement element, @NotNull Class... classesFilter) {
for (Class aClassesFilter : classesFilter) {
@SafeVarargs
private static boolean isElementOfClass(@NotNull PsiElement element, @NotNull Class<? extends PsiStatement>... classesFilter) {
for (Class<? extends PsiStatement> aClassesFilter : classesFilter) {
if (ReflectionUtil.isAssignable(aClassesFilter, element.getClass())) {
return true;
}
@@ -1005,7 +1009,7 @@ public class ControlFlowUtil {
}
/**
* returns true iff exists controlflow path completing normally, i.e. not resulting in return,break,continue or exception thrown.
* returns true iff exists control flow path completing normally, i.e. not resulting in return,break,continue or exception thrown.
* In other words, if we add instruction after controlflow specified, it should be reachable
*/
public static boolean canCompleteNormally(@NotNull ControlFlow flow, final int startOffset, final int endOffset) {
@@ -1554,17 +1558,17 @@ public class ControlFlowUtil {
return endOffset;
}
private static void depthFirstSearch(@NotNull ControlFlow flow, @NotNull InstructionClientVisitor visitor) {
private static void depthFirstSearch(@NotNull ControlFlow flow, @NotNull InstructionClientVisitor<?> visitor) {
depthFirstSearch(flow, visitor, 0, flow.getSize());
}
private static void depthFirstSearch(@NotNull ControlFlow flow, @NotNull InstructionClientVisitor visitor, int startOffset, int endOffset) {
private static void depthFirstSearch(@NotNull ControlFlow flow, @NotNull InstructionClientVisitor<?> visitor, int startOffset, int endOffset) {
visitor.processedInstructions = new boolean[endOffset];
internalDepthFirstSearch(flow.getInstructions(), visitor, startOffset, endOffset);
}
private static void internalDepthFirstSearch(@NotNull List<? extends Instruction> instructions,
@NotNull InstructionClientVisitor clientVisitor,
@NotNull InstructionClientVisitor<?> clientVisitor,
int startOffset,
int endOffset) {
@@ -1870,7 +1874,7 @@ public class ControlFlowUtil {
public void visitReadVariableInstruction(ReadVariableInstruction instruction, int offset, int nextOffset) {
CopyOnWriteList readVars = readVariables[Math.min(nextOffset, myFlow.getSize())];
final PsiVariable variable = instruction.variable;
if (!localVariablesOnly || !isMethodParameter(variable)) {
if (!localVariablesOnly || !isImplicitlyInitialized(variable)) {
final PsiReferenceExpression expression = getEnclosingReferenceExpression(myFlow.getElement(offset), variable);
if (expression != null) {
readVars = CopyOnWriteList.add(readVars, new VariableInfo(variable, expression));
@@ -1885,12 +1889,16 @@ public class ControlFlowUtil {
if (readVars == null) return;
final PsiVariable variable = instruction.variable;
if (!localVariablesOnly || !isMethodParameter(variable)) {
if (!localVariablesOnly || !isImplicitlyInitialized(variable)) {
readVars = readVars.remove(new VariableInfo(variable, null));
}
merge(offset, readVars, readVariables);
}
private static boolean isImplicitlyInitialized(@NotNull PsiVariable variable) {
return isMethodParameter(variable) || variable instanceof PsiPatternVariable;
}
private static boolean isMethodParameter(@NotNull PsiVariable variable) {
if (variable instanceof PsiParameter) {
final PsiParameter parameter = (PsiParameter)variable;

View File

@@ -22,6 +22,7 @@ import com.intellij.psi.impl.source.resolve.JavaResolveCache;
import com.intellij.psi.impl.source.tree.ChildRole;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
@@ -141,6 +142,14 @@ public class PsiBinaryExpressionImpl extends ExpressionPsiElement implements Psi
return "PsiBinaryExpression:" + getText();
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@NotNull PsiElement place) {
return PsiPolyadicExpressionImpl.processDeclarations(this, processor, state, lastParent, place);
}
@NotNull
@Override
public PsiExpression[] getOperands() {

View File

@@ -12,6 +12,7 @@ import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.tree.*;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.NameHint;
import com.intellij.psi.scope.PatternResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.tree.ChildRoleBase;
@@ -113,8 +114,8 @@ public class PsiCodeBlockImpl extends LazyParseablePsiElement implements PsiCode
PsiScopesUtil.walkChildrenScopes(this, new PsiScopeProcessor() {
@Override
public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
if (element instanceof PsiLocalVariable) {
final PsiLocalVariable variable = (PsiLocalVariable)element;
if (element instanceof PsiLocalVariable || element instanceof PsiPatternVariable) {
final PsiVariable variable = (PsiVariable)element;
final String name = variable.getName();
if (!localsSet.add(name)) {
conflict.set(Boolean.TRUE);
@@ -133,7 +134,7 @@ public class PsiCodeBlockImpl extends LazyParseablePsiElement implements PsiCode
}
return !conflict.get();
}
}, ResolveState.initial(), this, this);
}, PatternResolveState.WHEN_BOTH.putInto(ResolveState.initial()), this, this);
myClassesSet = set1 = classesSet.isEmpty() ? Collections.emptySet() : classesSet;
myVariablesSet = set2 = localsSet.isEmpty() ? Collections.emptySet() : localsSet;

View File

@@ -24,6 +24,9 @@ import com.intellij.psi.impl.source.tree.ChildRole;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.PatternResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
@@ -189,6 +192,23 @@ public class PsiConditionalExpressionImpl extends ExpressionPsiElement implement
}
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@NotNull PsiElement place) {
ElementClassHint elementClassHint = processor.getHint(ElementClassHint.KEY);
if (elementClassHint != null && !elementClassHint.shouldProcess(ElementClassHint.DeclarationKind.VARIABLE)) return true;
PsiExpression condition = getCondition();
if (lastParent == getThenExpression()) {
return condition.processDeclarations(processor, PatternResolveState.WHEN_TRUE.putInto(state), null, place);
}
if (lastParent == getElseExpression()) {
return condition.processDeclarations(processor, PatternResolveState.WHEN_FALSE.putInto(state), null, place);
}
return true;
}
@Override
public String toString() {
return "PsiConditionalExpression:" + getText();

View File

@@ -22,6 +22,8 @@ import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.tree.ChildRole;
import com.intellij.psi.impl.source.tree.TreeUtil;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
@@ -130,6 +132,17 @@ public class PsiDoWhileStatementImpl extends PsiLoopStatementImpl implements Psi
}
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@NotNull PsiElement place) {
if (lastParent != null) return true;
ElementClassHint elementClassHint = processor.getHint(ElementClassHint.KEY);
if (elementClassHint != null && !elementClassHint.shouldProcess(ElementClassHint.DeclarationKind.VARIABLE)) return true;
return PsiWhileStatementImpl.processDeclarationsInLoopCondition(processor, state, place, this);
}
@Override
public String toString(){
return "PsiDoWhileStatement";

View File

@@ -22,8 +22,9 @@ import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.tree.ChildRole;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.PatternResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
@@ -179,10 +180,24 @@ public class PsiForStatementImpl extends PsiLoopStatementImpl implements PsiForS
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor, @NotNull ResolveState state, PsiElement lastParent, @NotNull PsiElement place) {
processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, this);
if (lastParent == null || lastParent.getParent() != this)
ElementClassHint elementClassHint = processor.getHint(ElementClassHint.KEY);
if (elementClassHint != null && !elementClassHint.shouldProcess(ElementClassHint.DeclarationKind.VARIABLE)) return true;
if (lastParent == null) {
// Only patterns may introduce variables visible after loop
return PsiWhileStatementImpl.processDeclarationsInLoopCondition(processor, state, place, this);
}
else if (lastParent.getParent() != this) {
// Parent element should not see our vars
return true;
return PsiScopesUtil.walkChildrenScopes(this, processor, state, lastParent, place);
}
PsiStatement initialization = getInitialization();
if (initialization != null && lastParent != initialization) {
if (!initialization.processDeclarations(processor, state, null, place)) return false;
}
PsiExpression condition = getCondition();
if (condition != null && (lastParent == getBody() || lastParent == getUpdate())) {
return condition.processDeclarations(processor, PatternResolveState.WHEN_TRUE.putInto(state), null, place);
}
return true;
}
}

View File

@@ -18,12 +18,16 @@ package com.intellij.psi.impl.source.tree.java;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.*;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.tree.ChildRole;
import com.intellij.psi.impl.source.tree.CompositePsiElement;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.PatternResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.IncorrectOperationException;
@@ -193,6 +197,62 @@ public class PsiIfStatementImpl extends CompositePsiElement implements PsiIfStat
}
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@NotNull PsiElement place) {
ElementClassHint elementClassHint = processor.getHint(ElementClassHint.KEY);
if (elementClassHint != null && !elementClassHint.shouldProcess(ElementClassHint.DeclarationKind.VARIABLE)) return true;
PsiExpression condition = getCondition();
if (condition != null) {
PsiStatement thenBranch = getThenBranch();
PsiStatement elseBranch = getElseBranch();
if (lastParent == null) {
PsiScopeProcessor conditionProcessor;
if (state.get(PatternResolveState.KEY) == PatternResolveState.WHEN_BOTH) {
conditionProcessor = processor;
}
else {
conditionProcessor = (element, s) -> {
LOG.assertTrue(element instanceof PsiPatternVariable);
ControlFlow flow;
try {
flow = ControlFlowFactory.getInstance(getProject()).getControlFlow(
this, new LocalsControlFlowPolicy(this), false, false);
}
catch (AnalysisCanceledException e) {
return true;
}
boolean thenCompletesNormally = canCompleteNormally(thenBranch, flow);
boolean elseCompletesNormally = canCompleteNormally(elseBranch, flow);
if (thenCompletesNormally == elseCompletesNormally ||
PatternResolveState.fromBoolean(thenCompletesNormally) !=
PatternResolveState.stateAtParent((PsiPatternVariable)element, condition)) {
return true;
}
return processor.execute(element, s);
};
}
return condition.processDeclarations(conditionProcessor, PatternResolveState.WHEN_BOTH.putInto(state), null, place);
}
if (lastParent == thenBranch) {
return condition.processDeclarations(processor, PatternResolveState.WHEN_TRUE.putInto(state), null, place);
}
if (lastParent == elseBranch) {
return condition.processDeclarations(processor, PatternResolveState.WHEN_FALSE.putInto(state), null, place);
}
}
return true;
}
private static boolean canCompleteNormally(PsiStatement branch, ControlFlow flow) {
if (branch == null) return true;
int startOffset = flow.getStartOffset(branch);
int endOffset = flow.getEndOffset(branch);
return startOffset != -1 && endOffset != -1 && ControlFlowUtil.canCompleteNormally(flow, startOffset, endOffset);
}
@Override
public String toString() {
return "PsiIfStatement";

View File

@@ -6,6 +6,9 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.tree.ChildRole;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.PatternResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
@@ -81,6 +84,20 @@ public class PsiInstanceOfExpressionImpl extends ExpressionPsiElement implements
}
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@NotNull PsiElement place) {
if (lastParent != null) return true;
ElementClassHint elementClassHint = processor.getHint(ElementClassHint.KEY);
if (elementClassHint != null && !elementClassHint.shouldProcess(ElementClassHint.DeclarationKind.VARIABLE)) return true;
if (state.get(PatternResolveState.KEY) == PatternResolveState.WHEN_FALSE) return true;
PsiPattern pattern = getPattern();
if (pattern == null) return true;
return pattern.processDeclarations(processor, state, null, place);
}
@Override
public String toString() {
return "PsiInstanceofExpression";

View File

@@ -20,8 +20,10 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.tree.ChildRole;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -92,6 +94,19 @@ public class PsiParenthesizedExpressionImpl extends ExpressionPsiElement impleme
}
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@NotNull PsiElement place) {
if (lastParent != null) return true;
ElementClassHint elementClassHint = processor.getHint(ElementClassHint.KEY);
if (elementClassHint != null && !elementClassHint.shouldProcess(ElementClassHint.DeclarationKind.VARIABLE)) return true;
PsiExpression expression = getExpression();
if (expression == null) return true;
return expression.processDeclarations(processor, state, null, place);
}
@Override
public String toString() {
return "PsiParenthesizedExpression:" + getText();

View File

@@ -6,8 +6,11 @@ import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.tree.CompositePsiElement;
import com.intellij.psi.impl.source.tree.JavaSharedImplUtil;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -40,6 +43,12 @@ public class PsiPatternVariableImpl extends CompositePsiElement implements PsiPa
}
}
@Nullable
@Override
public PsiPattern getPattern() {
return ObjectUtils.tryCast(getParent(), PsiPattern.class);
}
@Override
public void setInitializer(@Nullable PsiExpression initializer) throws IncorrectOperationException {
throw new IncorrectOperationException();
@@ -85,6 +94,11 @@ public class PsiPatternVariableImpl extends CompositePsiElement implements PsiPa
return identifier.getText();
}
@Override
public int getTextOffset() {
return getNameIdentifier().getTextOffset();
}
@Nullable
@Override
public PsiModifierList getModifierList() {
@@ -96,6 +110,28 @@ public class PsiPatternVariableImpl extends CompositePsiElement implements PsiPa
return false;
}
@NotNull
@Override
public SearchScope getUseScope() {
PsiPattern pattern = getPattern();
if (pattern != null) {
PsiElement parent = pattern.getParent();
while (parent instanceof PsiInstanceOfExpression || parent instanceof PsiParenthesizedExpression ||
parent instanceof PsiConditionalExpression ||
parent instanceof PsiPrefixExpression && ((PsiPrefixExpression)parent).getOperationTokenType().equals(EXCL) ||
parent instanceof PsiPolyadicExpression &&
(((PsiPolyadicExpression)parent).getOperationTokenType().equals(ANDAND) ||
((PsiPolyadicExpression)parent).getOperationTokenType().equals(OROR))) {
parent = parent.getParent();
}
if (parent instanceof PsiIfStatement || parent instanceof PsiConditionalLoopStatement) {
return new LocalSearchScope(parent.getParent());
}
return new LocalSearchScope(parent);
}
return super.getUseScope();
}
@Override
public String toString() {
return "PsiPatternVariable:" + getName();

View File

@@ -22,6 +22,10 @@ import com.intellij.psi.impl.source.resolve.JavaResolveCache;
import com.intellij.psi.impl.source.tree.ChildRole;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.PatternResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
@@ -124,6 +128,30 @@ public class PsiPolyadicExpressionImpl extends ExpressionPsiElement implements P
return operands;
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@NotNull PsiElement place) {
return processDeclarations(this, processor, state, lastParent, place);
}
static boolean processDeclarations(@NotNull PsiPolyadicExpression expression,
@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@NotNull PsiElement place) {
IElementType tokenType = expression.getOperationTokenType();
boolean and = tokenType.equals(JavaTokenType.ANDAND);
boolean or = tokenType.equals(JavaTokenType.OROR);
if (!and && !or) return true;
ElementClassHint elementClassHint = processor.getHint(ElementClassHint.KEY);
if (elementClassHint != null && !elementClassHint.shouldProcess(ElementClassHint.DeclarationKind.VARIABLE)) return true;
PatternResolveState wantedHint = PatternResolveState.fromBoolean(and);
if (state.get(PatternResolveState.KEY) == wantedHint.invert()) return true;
return PsiScopesUtil.walkChildrenScopes(expression, processor, wantedHint.putInto(state), lastParent, place);
}
private volatile PsiExpression[] cachedOperands;
@Override
public void clearCaches() {

View File

@@ -21,6 +21,10 @@ import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.ChildRole;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.PatternResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
@@ -105,6 +109,19 @@ public class PsiPrefixExpressionImpl extends ExpressionPsiElement implements Psi
}
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@NotNull PsiElement place) {
if (lastParent != null || !getOperationTokenType().equals(JavaTokenType.EXCL)) return true;
ElementClassHint elementClassHint = processor.getHint(ElementClassHint.KEY);
if (elementClassHint != null && !elementClassHint.shouldProcess(ElementClassHint.DeclarationKind.VARIABLE)) return true;
PatternResolveState hint = state.get(PatternResolveState.KEY);
if (hint == null) return true;
return PsiScopesUtil.walkChildrenScopes(this, processor, hint.invert().putInto(state), null, place);
}
@Override
public String toString() {
return "PsiPrefixExpression:" + getText();

View File

@@ -4,6 +4,7 @@ package com.intellij.psi.impl.source.tree.java;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.tree.CompositePsiElement;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -40,6 +41,18 @@ public class PsiTypeTestPatternImpl extends CompositePsiElement implements PsiTy
}
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor, @NotNull ResolveState state, PsiElement lastParent,
@NotNull PsiElement place) {
processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, this);
PsiPatternVariable variable = getPatternVariable();
if (variable != null && variable != lastParent) {
return processor.execute(variable, state);
}
return true;
}
@Override
public String toString() {
return "PsiTypeTestPattern";

View File

@@ -21,8 +21,12 @@ import com.intellij.psi.*;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.tree.ChildRole;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.PatternResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
public class PsiWhileStatementImpl extends PsiLoopStatementImpl implements PsiWhileStatement, Constants {
@@ -112,6 +116,42 @@ public class PsiWhileStatementImpl extends PsiLoopStatementImpl implements PsiWh
}
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@NotNull PsiElement place) {
ElementClassHint elementClassHint = processor.getHint(ElementClassHint.KEY);
if (elementClassHint != null && !elementClassHint.shouldProcess(ElementClassHint.DeclarationKind.VARIABLE)) return true;
if (lastParent == null) {
return processDeclarationsInLoopCondition(processor, state, place, this);
}
PsiExpression condition = getCondition();
if (condition != null && lastParent == getBody()) {
return condition.processDeclarations(processor, PatternResolveState.WHEN_TRUE.putInto(state), null, place);
}
return true;
}
static boolean processDeclarationsInLoopCondition(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
@NotNull PsiElement place,
@NotNull PsiConditionalLoopStatement loop) {
PsiExpression condition = loop.getCondition();
if (condition == null) return true;
PsiScopeProcessor conditionProcessor = (element, s) -> {
assert element instanceof PsiPatternVariable;
PatternResolveState resolveState = PatternResolveState.stateAtParent((PsiPatternVariable)element, condition);
if (resolveState == PatternResolveState.WHEN_TRUE ||
!PsiTreeUtil
.processElements(loop, e -> !(e instanceof PsiBreakStatement) || ((PsiBreakStatement)e).findExitedStatement() != loop)) {
return true;
}
return processor.execute(element, s);
};
return condition.processDeclarations(conditionProcessor, PatternResolveState.WHEN_BOTH.putInto(state), null, place);
}
@Override
public String toString(){
return "PsiWhileStatement";

View File

@@ -0,0 +1,57 @@
// Copyright 2000-2019 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.psi.scope;
import com.intellij.openapi.util.Key;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
public enum PatternResolveState {
WHEN_TRUE, WHEN_FALSE, WHEN_BOTH;
public static final Key<PatternResolveState> KEY = Key.create("JavaPatternDeclarationHint");
public static PatternResolveState fromBoolean(boolean value) {
return value ? WHEN_TRUE : WHEN_FALSE;
}
public PatternResolveState invert() {
switch (this) {
case WHEN_TRUE:
return WHEN_FALSE;
case WHEN_FALSE:
return WHEN_TRUE;
case WHEN_BOTH:
return WHEN_BOTH;
default:
throw new IllegalStateException("Unexpected value: " + this);
}
}
public ResolveState putInto(ResolveState rs) {
return rs.put(KEY, this);
}
@NotNull
public static PatternResolveState stateAtParent(PsiPatternVariable element, PsiExpression parent) {
PsiPattern pattern = element.getPattern();
if (pattern == null) {
throw new IllegalArgumentException("Variable has no pattern associated");
}
PatternResolveState state = WHEN_TRUE;
for (PsiElement prev = pattern, current = prev.getParent(); prev != parent; prev = current, current = current.getParent()) {
if (current instanceof PsiInstanceOfExpression || current instanceof PsiParenthesizedExpression ||
current instanceof PsiPolyadicExpression &&
(((PsiPolyadicExpression)current).getOperationTokenType() == JavaTokenType.ANDAND ||
((PsiPolyadicExpression)current).getOperationTokenType() == JavaTokenType.OROR)) {
continue;
}
if (current instanceof PsiPrefixExpression &&
((PsiPrefixExpression)current).getOperationTokenType() == JavaTokenType.EXCL) {
state = state.invert();
continue;
}
throw new IllegalArgumentException("Variable is not available at parent");
}
return state;
}
}

View File

@@ -0,0 +1,61 @@
class X {
void expressions(Object obj) {
boolean b1 = obj instanceof String s && s.isEmpty();
boolean b2 = !(obj instanceof String s) && <error descr="Cannot resolve symbol 's'">s</error>.isEmpty();
boolean b3 = obj instanceof String s || <error descr="Cannot resolve symbol 's'">s</error>.isEmpty();
boolean b4 = !(obj instanceof String s) || s.isEmpty();
boolean b5 = obj instanceof String s ? s.isEmpty() : obj == null;
boolean b6 = !(obj instanceof String s) ? obj == null : s.isEmpty();
boolean b7 = obj instanceof String s ? s.isEmpty() : <error descr="Cannot resolve symbol 's'">s</error>.isEmpty();
boolean b8 = !(obj instanceof String s) ? <error descr="Cannot resolve symbol 's'">s</error>.isEmpty() : s.isEmpty();
}
void twoPatterns(Object o1, Object o2) {
if (o1 instanceof String s1 && o2 instanceof String s2 && s1.startsWith(s2)) {}
if ((o1 instanceof String s1 && o2 instanceof String s2) && s1.startsWith(s2)) {}
if (o1 instanceof String s1 && (o2 instanceof String s2 && s1.startsWith(s2))) {}
if (o1 instanceof String s1 && !(o2 instanceof String s2) && s1.startsWith(<error descr="Cannot resolve symbol 's2'">s2</error>)) {}
}
void polyadicInCondition(Object o1, Object o2) {
boolean b1 = o1 instanceof String s1 && o2 instanceof String s2 ? s1.isEmpty() && s2.isEmpty() : false;
boolean b2 = o1 instanceof String s1 && !(o2 instanceof String s2) ? s1.isEmpty() : <error descr="Cannot resolve symbol 's2'">s2</error>.isEmpty();
}
void ifThenSimple(Object o) {
if (o instanceof String s) {
System.out.println(s.trim());
} else {
System.out.println(<error descr="Cannot resolve symbol 's'">s</error>.trim());
}
if (!(o instanceof String s)) {
System.out.println(<error descr="Cannot resolve symbol 's'">s</error>.trim());
} else {
System.out.println(s.trim());
}
}
interface Node {
Object next();
String name();
}
void whileSimple(Object o) {
while (o instanceof Node n) {
o = n.next();
}
}
void whileNot(Object o) {
while (!(o instanceof Node n)) {
o = <error descr="Cannot resolve symbol 'n'">n</error>.next();
}
}
void forSimple(Object o) {
for (; o instanceof Node n; o = n.next()) {
System.out.println(n.name());
}
}
}

View File

@@ -0,0 +1,120 @@
class X {
void simpleIf(Object obj) {
if (!(obj instanceof String s)) return;
System.out.println(s.trim());
}
void ifElse(Object obj) {
if (!(obj instanceof String s)) return;
else {
System.out.println(s.trim());
}
System.out.println(s.trim());
}
static boolean FLAG2 = true;
static final boolean FLAG = true;
void ifElseInfiniteLoop(Object obj) {
if (obj instanceof String s) {}
else {
while (FLAG) {
System.out.println("oops");
}
}
System.out.println(s);
}
void ifElseInfiniteLoopLocal(Object obj) {
final boolean FLAG = true;
if (obj instanceof String s) {}
else {
while (FLAG) {
System.out.println("oops");
}
}
System.out.println(s);
}
void ifElseFiniteLoop(Object obj) {
if (obj instanceof String s) {}
else {
while (FLAG2) {
System.out.println("oops");
}
}
System.out.println(<error descr="Cannot resolve symbol 's'">s</error>);
}
void ifElseInfiniteSelfRef(Object obj) {
if (obj instanceof Boolean b) {
while(b) {}
}
else {
while(<error descr="Cannot resolve symbol 'b'">b</error>) {}
}
while(<error descr="Cannot resolve symbol 'b'">b</error>) {}
}
class Shadow {
String b;
void ifElseInfiniteSelfRef(Object obj) {
if (obj instanceof Boolean b) {
while(b) {}
}
else {
while(b == "") {}
}
while(b == "") {}
}
void inverted(Object obj) {
if (!(obj instanceof Integer b)) {}
System.out.println(b.trim());
}
}
native Object getNextObj();
void testWhile(Object obj) {
while (!(obj instanceof Integer x)) {
obj = getNextObj();
}
System.out.println(x.intValue());
}
void testWhileWithBreak(Object obj) {
while (!(obj instanceof Integer x)) {
obj = getNextObj();
if (obj instanceof String) break;
}
System.out.println(<error descr="Cannot resolve symbol 'x'">x</error>.intValue());
}
void testDoWhile(Object obj) {
do {
obj = getNextObj();
if (obj instanceof String) break;
}
while (!(obj instanceof Integer x));
System.out.println(<error descr="Cannot resolve symbol 'x'">x</error>.intValue());
}
void testDoWhileWithBreak(Object obj) {
do {
obj = getNextObj();
if (obj instanceof String) break;
}
while (!(obj instanceof Integer x));
System.out.println(<error descr="Cannot resolve symbol 'x'">x</error>.intValue());
}
void testFor() {
for (Object obj = getNextObj(); !(obj instanceof String s); obj = getNextObj()) {
System.out.println("going further");
}
System.out.println("Found: "+s.trim());
}
}

View File

@@ -0,0 +1,32 @@
class X {
void polyadic(Object o1, Object o2) {
boolean b1 = o1 instanceof String s && o2 instanceof String <error descr="Variable 's' is already defined in the scope">s</error>;
boolean b2 = !(o1 instanceof String s) && !(o2 instanceof String <error descr="Variable 's' is already defined in the scope">s</error>);
boolean b3 = !(o1 instanceof String s) || !(o2 instanceof String <error descr="Variable 's' is already defined in the scope">s</error>);
boolean b4 = o1 instanceof String s || o2 instanceof String <error descr="Variable 's' is already defined in the scope">s</error>;
// Dubious cases: spec is not very clear about whether this should be accepted
boolean b5 = o1 instanceof String s && !(o2 instanceof String <error descr="Variable 's' is already defined in the scope">s</error>);
boolean b6 = o1 instanceof String s && (!(o2 instanceof String <error descr="Variable 's' is already defined in the scope">s</error>) || s.isEmpty());
boolean b7 = !(o2 instanceof String s) && o1 instanceof String s;
boolean b8 = (!(o2 instanceof String s) || s.isEmpty()) && o1 instanceof String s && s.isEmpty();
}
void ternary(Object o1, Object o2, Object o3) {
// Currently all these samples are accepted by javac
boolean b1 = o1 instanceof String s ? o2 instanceof String <error descr="Variable 's' is already defined in the scope">s</error> : o3 instanceof String s1;
boolean b2 = o1 instanceof String s ? o2 instanceof String s1 : o3 instanceof String <error descr="Variable 's' is already defined in the scope">s</error>;
boolean b3 = o1 instanceof String s1 ? o2 instanceof String s : o3 instanceof String <error descr="Variable 's' is already defined in the scope">s</error>;
boolean b4 = !(o1 instanceof String s) ? o2 instanceof String <error descr="Variable 's' is already defined in the scope">s</error> : o3 instanceof String s1;
boolean b5 = !(o1 instanceof String s) ? o2 instanceof String s1 : o3 instanceof String <error descr="Variable 's' is already defined in the scope">s</error>;
boolean b6 = !(o1 instanceof String s1) ? o2 instanceof String s : o3 instanceof String <error descr="Variable 's' is already defined in the scope">s</error>;
}
void ifElse(Object o1, Object o2) {
if (o1 instanceof String s) {
if (o2 instanceof String <error descr="Variable 's' is already defined in the scope">s</error>) {
}
}
}
}

View File

@@ -0,0 +1,35 @@
// Copyright 2000-2019 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.codeInsight.daemon;
import com.intellij.JavaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
public class LightPatternsHighlightingTest extends LightJavaCodeInsightFixtureTestCase {
@Override
protected String getBasePath() {
return JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/daemonCodeAnalyzer/advHighlightingPatterns";
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_14;
}
public void testInstanceOfBasics() {
doTest();
}
public void testInstanceOfNameConflicts() {
doTest();
}
public void testInstanceOfControlFlow() {
doTest();
}
private void doTest() {
myFixture.configureByFile(getTestName(false) + ".java");
myFixture.checkHighlighting();
}
}