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 05da60992408..259aa50fef0a 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 @@ -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(); diff --git a/java/java-psi-api/src/com/intellij/psi/PsiPatternVariable.java b/java/java-psi-api/src/com/intellij/psi/PsiPatternVariable.java index fbfcaddd865f..b3fcc005d75d 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiPatternVariable.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiPatternVariable.java @@ -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(); } 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 480e59c52d07..34951e6ba941 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 @@ -351,6 +351,7 @@ public class ControlFlowUtil { return outputVariables; } + @SafeVarargs @NotNull public static Collection 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 exitStatements = new THashSet<>(); - InstructionClientVisitor visitor = new InstructionClientVisitor() { + InstructionClientVisitor visitor = new InstructionClientVisitor() { @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 exitStatements, @NotNull BranchingInstruction instruction, - final PsiStatement statement, @NotNull Class... classesFilter) { + final PsiStatement statement, @NotNull Class... 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 exitStatements, - PsiStatement statement, @NotNull Class... classesFilter) { + PsiStatement statement, @NotNull Class... 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... classesFilter) { + for (Class 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 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; diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiBinaryExpressionImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiBinaryExpressionImpl.java index dc26eee45a6b..3579641e2765 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiBinaryExpressionImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiBinaryExpressionImpl.java @@ -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() { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiCodeBlockImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiCodeBlockImpl.java index c94e8886cc9e..01930c35426c 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiCodeBlockImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiCodeBlockImpl.java @@ -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; diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiConditionalExpressionImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiConditionalExpressionImpl.java index 8ca652c5b07a..5cfceec28cbf 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiConditionalExpressionImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiConditionalExpressionImpl.java @@ -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(); diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiDoWhileStatementImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiDoWhileStatementImpl.java index ff40682a7f17..14fa08b9f822 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiDoWhileStatementImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiDoWhileStatementImpl.java @@ -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"; diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiForStatementImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiForStatementImpl.java index 5b563bfd540d..f8624aab8ef3 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiForStatementImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiForStatementImpl.java @@ -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; } } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiIfStatementImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiIfStatementImpl.java index 54a4d9352f61..ff1ebcf03913 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiIfStatementImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiIfStatementImpl.java @@ -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"; diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiInstanceOfExpressionImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiInstanceOfExpressionImpl.java index 7b54c45b24ec..0c175056a76b 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiInstanceOfExpressionImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiInstanceOfExpressionImpl.java @@ -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"; diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiParenthesizedExpressionImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiParenthesizedExpressionImpl.java index 4e05f1136bae..3dafa8e040fb 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiParenthesizedExpressionImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiParenthesizedExpressionImpl.java @@ -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(); diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiPatternVariableImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiPatternVariableImpl.java index 9918728c5e2a..e6f942ef24a7 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiPatternVariableImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiPatternVariableImpl.java @@ -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(); diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiPolyadicExpressionImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiPolyadicExpressionImpl.java index 99b319200c59..8f7a090c031f 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiPolyadicExpressionImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiPolyadicExpressionImpl.java @@ -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() { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiPrefixExpressionImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiPrefixExpressionImpl.java index 14f79cfeac49..fa24e5c61930 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiPrefixExpressionImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiPrefixExpressionImpl.java @@ -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(); diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiTypeTestPatternImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiTypeTestPatternImpl.java index f12350158e80..a187f1b54c1a 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiTypeTestPatternImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiTypeTestPatternImpl.java @@ -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"; diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiWhileStatementImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiWhileStatementImpl.java index fff7075dfddb..a630a7422e35 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiWhileStatementImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiWhileStatementImpl.java @@ -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"; diff --git a/java/java-psi-impl/src/com/intellij/psi/scope/PatternResolveState.java b/java/java-psi-impl/src/com/intellij/psi/scope/PatternResolveState.java new file mode 100644 index 000000000000..32720a6a833f --- /dev/null +++ b/java/java-psi-impl/src/com/intellij/psi/scope/PatternResolveState.java @@ -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 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; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatterns/InstanceOfBasics.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatterns/InstanceOfBasics.java new file mode 100644 index 000000000000..911f5c419bb0 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatterns/InstanceOfBasics.java @@ -0,0 +1,61 @@ +class X { + void expressions(Object obj) { + boolean b1 = obj instanceof String s && s.isEmpty(); + boolean b2 = !(obj instanceof String s) && s.isEmpty(); + boolean b3 = obj instanceof String s || s.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() : s.isEmpty(); + boolean b8 = !(obj instanceof String s) ? s.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(s2)) {} + } + + 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() : s2.isEmpty(); + } + + void ifThenSimple(Object o) { + if (o instanceof String s) { + System.out.println(s.trim()); + } else { + System.out.println(s.trim()); + } + if (!(o instanceof String s)) { + System.out.println(s.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 = n.next(); + } + } + + void forSimple(Object o) { + for (; o instanceof Node n; o = n.next()) { + System.out.println(n.name()); + } + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatterns/InstanceOfControlFlow.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatterns/InstanceOfControlFlow.java new file mode 100644 index 000000000000..6ac423d006cb --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatterns/InstanceOfControlFlow.java @@ -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(s); + } + + void ifElseInfiniteSelfRef(Object obj) { + if (obj instanceof Boolean b) { + while(b) {} + } + else { + while(b) {} + } + while(b) {} + } + + 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(x.intValue()); + } + + void testDoWhile(Object obj) { + do { + obj = getNextObj(); + if (obj instanceof String) break; + } + while (!(obj instanceof Integer x)); + System.out.println(x.intValue()); + } + + void testDoWhileWithBreak(Object obj) { + do { + obj = getNextObj(); + if (obj instanceof String) break; + } + while (!(obj instanceof Integer x)); + System.out.println(x.intValue()); + } + + void testFor() { + for (Object obj = getNextObj(); !(obj instanceof String s); obj = getNextObj()) { + System.out.println("going further"); + } + System.out.println("Found: "+s.trim()); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatterns/InstanceOfNameConflicts.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatterns/InstanceOfNameConflicts.java new file mode 100644 index 000000000000..5b758d1cd7af --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatterns/InstanceOfNameConflicts.java @@ -0,0 +1,32 @@ +class X { + void polyadic(Object o1, Object o2) { + boolean b1 = o1 instanceof String s && o2 instanceof String s; + boolean b2 = !(o1 instanceof String s) && !(o2 instanceof String s); + boolean b3 = !(o1 instanceof String s) || !(o2 instanceof String s); + boolean b4 = o1 instanceof String s || o2 instanceof String s; + + // Dubious cases: spec is not very clear about whether this should be accepted + boolean b5 = o1 instanceof String s && !(o2 instanceof String s); + boolean b6 = o1 instanceof String s && (!(o2 instanceof String s) || 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 s : o3 instanceof String s1; + boolean b2 = o1 instanceof String s ? o2 instanceof String s1 : o3 instanceof String s; + boolean b3 = o1 instanceof String s1 ? o2 instanceof String s : o3 instanceof String s; + boolean b4 = !(o1 instanceof String s) ? o2 instanceof String s : o3 instanceof String s1; + boolean b5 = !(o1 instanceof String s) ? o2 instanceof String s1 : o3 instanceof String s; + boolean b6 = !(o1 instanceof String s1) ? o2 instanceof String s : o3 instanceof String s; + } + + void ifElse(Object o1, Object o2) { + if (o1 instanceof String s) { + if (o2 instanceof String s) { + + } + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightPatternsHighlightingTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightPatternsHighlightingTest.java new file mode 100644 index 000000000000..5cf136032e00 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightPatternsHighlightingTest.java @@ -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(); + } +} \ No newline at end of file