PY-48760 Implement CFG for PEP-634 match statements

I introduced a new type of CFG instructions, similar to ConditionalInstruction,
called RefutablePatternInstruction. The idea is that every pattern that can
possibly fail to match is surrounded with a pair of such instructions, helping
to describe how the control flow moves in each case. The PEP calls the opposite
type of patterns that always match "irrefutable", hence the name. We need these
synthetic instructions because otherwise some refutable patterns such as literal
ones (e.g. "42") don't leave any nodes in the graph. Incorporating the information
about irrefutable patterns right into the graph allows catching cases such
as "wildcard/name capture makes remaining patterns unreachable", both in OR
patterns and independent case clauses.

GitOrigin-RevId: beebe1890a6a824b188e6954a2c92f7ec52079e0
This commit is contained in:
Mikhail Golubev
2021-06-11 10:16:38 +00:00
committed by intellij-monorepo-bot
parent 79999b52e4
commit cb08d4de98
96 changed files with 1203 additions and 9 deletions
@@ -1,4 +1,7 @@
package com.jetbrains.python.psi;
import org.jetbrains.annotations.NotNull;
public interface PyAsPattern extends PyPattern {
@NotNull PyPattern getPattern();
}
@@ -1,5 +1,9 @@
// Copyright 2000-2021 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.jetbrains.python.psi;
import org.jetbrains.annotations.Nullable;
public interface PyCaseClause extends PyStatementPart {
@Nullable PyPattern getPattern();
@Nullable PyExpression getGuardCondition();
}
@@ -326,6 +326,10 @@ public class PyElementVisitor extends PsiElementVisitor {
visitPyPattern(node);
}
public void visitWildcardPattern(@NotNull PyWildcardPattern node) {
visitPyPattern(node);
}
public void visitPyClassPattern(@NotNull PyClassPattern node) {
visitPyPattern(node);
}
@@ -1,5 +1,8 @@
// Copyright 2000-2021 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.jetbrains.python.psi;
import org.jetbrains.annotations.NotNull;
public interface PyGroupPattern extends PyPattern {
@NotNull PyPattern getPattern();
}
@@ -1,5 +1,12 @@
// Copyright 2000-2021 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.jetbrains.python.psi;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public interface PyMatchStatement extends PyStatement {
@Nullable PyExpression getSubject();
@NotNull List<PyCaseClause> getCaseClauses();
}
@@ -1,5 +1,10 @@
// Copyright 2000-2021 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.jetbrains.python.psi;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public interface PyOrPattern extends PyPattern {
@NotNull List<PyPattern> getAlternatives();
}
@@ -353,6 +353,11 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
}
}
@Override
public void visitPyMatchStatement(@NotNull PyMatchStatement matchStatement) {
new PyMatchStatementControlFlowBuilder(myBuilder, this).build(matchStatement);
}
@Override
public void visitPyIfStatement(final @NotNull PyIfStatement node) {
myBuilder.startNode(node);
@@ -506,7 +511,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
}
}
private static boolean isConjunctionOrDisjunction(@Nullable PyExpression node) {
static boolean isConjunctionOrDisjunction(@Nullable PyExpression node) {
if (node instanceof PyBinaryExpression) {
final var operator = ((PyBinaryExpression)node).getOperator();
return operator == PyTokenTypes.AND_KEYWORD || operator == PyTokenTypes.OR_KEYWORD;
@@ -0,0 +1,203 @@
package com.jetbrains.python.codeInsight.controlflow;
import com.intellij.codeInsight.controlflow.ConditionalInstruction;
import com.intellij.codeInsight.controlflow.ControlFlowBuilder;
import com.intellij.codeInsight.controlflow.Instruction;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.function.BiFunction;
import static com.jetbrains.python.codeInsight.controlflow.PyControlFlowBuilder.isConjunctionOrDisjunction;
public final class PyMatchStatementControlFlowBuilder {
private final ControlFlowBuilder myBuilder;
private final PyElementVisitor myBaseVisitor;
public PyMatchStatementControlFlowBuilder(@NotNull ControlFlowBuilder builder, @NotNull PyElementVisitor baseVisitor) {
myBuilder = builder;
myBaseVisitor = baseVisitor;
}
public final void build(@NotNull PyMatchStatement matchStatement) {
myBuilder.startNode(matchStatement);
PyExpression subject = matchStatement.getSubject();
if (subject != null) {
subject.accept(myBaseVisitor);
}
for (PyCaseClause caseClause : matchStatement.getCaseClauses()) {
processCaseClause(caseClause);
}
}
private void processCaseClause(@NotNull PyCaseClause clause) {
PyPattern pattern = clause.getPattern();
if (pattern != null) {
processPattern(pattern);
retargetOutgoingPatternEdges(pattern, (oldScope, instr) -> {
return instr.isMatched() ? pattern : clause;
});
}
PyStatementList statementList = clause.getStatementList();
PyExpression guard = clause.getGuardCondition();
if (guard != null) {
guard.accept(myBaseVisitor);
// Retarget failure edges coming from inner OR and AND expressions
retargetOutgoingEdges(guard, (pendingScope, instr) -> {
if (instr instanceof ConditionalInstruction && !((ConditionalInstruction)instr).getResult()) {
return clause;
}
return pendingScope;
});
// Top-level OR and AND expressions should have had their own outgoing failure edges
if (!isConjunctionOrDisjunction(guard)) {
myBuilder.addPendingEdge(clause, myBuilder.prevInstruction);
}
myBuilder.startConditionalNode(statementList, guard, true);
}
statementList.accept(myBaseVisitor);
PyMatchStatement matchStatement = PsiTreeUtil.getParentOfType(clause, PyMatchStatement.class);
assert matchStatement != null;
retargetOutgoingEdges(statementList, (pendingScope, instruction) -> matchStatement);
myBuilder.addPendingEdge(matchStatement, myBuilder.prevInstruction);
myBuilder.prevInstruction = null;
}
private void processPattern(@NotNull PyPattern pattern) {
boolean isRefutable = !isIrrefutablePattern(pattern);
if (isRefutable) {
RefutablePatternInstruction instruction = new RefutablePatternInstruction(myBuilder, pattern, false);
myBuilder.addNodeAndCheckPending(instruction);
myBuilder.addPendingEdge(pattern, instruction);
}
if (pattern instanceof PyWildcardPattern) {
myBuilder.startNode(pattern);
}
else if (pattern instanceof PyOrPattern) {
List<PyPattern> alternatives = ((PyOrPattern)pattern).getAlternatives();
PyPattern lastAlternative = ContainerUtil.getLastItem(alternatives);
for (PyPattern alternative : alternatives) {
processPattern(alternative);
if (alternative != lastAlternative) {
myBuilder.addPendingEdge(alternative, myBuilder.prevInstruction);
myBuilder.prevInstruction = null;
}
retargetOutgoingEdges(alternative, (pendingScope, instruction) -> {
if (instruction instanceof RefutablePatternInstruction && !((RefutablePatternInstruction)instruction).isMatched()) {
// Pattern has failed, jump to the next alternative if any
return alternative;
}
// Pattern succeeded, jump out of OR-pattern. It can be either a refutable pattern or a capture/wildcard node.
else {
return pattern;
}
});
}
}
else {
pattern.acceptChildren(new PyElementVisitor() {
@Override
public void visitPyReferenceExpression(@NotNull PyReferenceExpression node) {
myBaseVisitor.visitPyReferenceExpression(node);
}
@Override
public void visitPyTargetExpression(@NotNull PyTargetExpression node) {
myBaseVisitor.visitPyTargetExpression(node);
}
@Override
public void visitPyPattern(@NotNull PyPattern node) {
processPattern(node);
retargetOutgoingPatternEdges(pattern, (oldScope, instr) -> {
// Mismatch in a non-OR pattern means mismatch of the containing pattern as well
return instr.isMatched() ? oldScope : pattern;
});
}
@Override
public void visitPyPatternArgumentList(@NotNull PyPatternArgumentList node) {
node.acceptChildren(this);
}
});
}
if (isRefutable) {
myBuilder.addNode(new RefutablePatternInstruction(myBuilder, pattern, true));
}
}
private static boolean isIrrefutablePattern(@NotNull PyPattern pattern) {
Ref<Boolean> result = Ref.create(false);
pattern.accept(new PyElementVisitor() {
@Override
public void visitPyAsPattern(@NotNull PyAsPattern node) {
result.set(isIrrefutablePattern(node.getPattern()));
}
@Override
public void visitPyCapturePattern(@NotNull PyCapturePattern node) {
result.set(true);
}
@Override
public void visitWildcardPattern(@NotNull PyWildcardPattern node) {
result.set(true);
}
@Override
public void visitPyDoubleStarPattern(@NotNull PyDoubleStarPattern node) {
result.set(true);
}
@Override
public void visitPySingleStarPattern(@NotNull PySingleStarPattern node) {
result.set(true);
}
@Override
public void visitPyGroupPattern(@NotNull PyGroupPattern node) {
result.set(isIrrefutablePattern(node.getPattern()));
}
@Override
public void visitPyOrPattern(@NotNull PyOrPattern node) {
result.set(ContainerUtil.exists(node.getAlternatives(), p -> isIrrefutablePattern(p)));
}
@Override
public void visitPyPattern(@NotNull PyPattern node) {
result.set(false);
}
});
return result.get();
}
private void retargetOutgoingEdges(@NotNull PsiElement scopeAncestor,
@NotNull BiFunction<PsiElement, Instruction, PsiElement> newScopeProvider) {
myBuilder.processPending((oldScope, instruction) -> {
if (oldScope != null && PsiTreeUtil.isAncestor(scopeAncestor, oldScope, false)) {
myBuilder.addPendingEdge(newScopeProvider.apply(oldScope, instruction), instruction);
}
else {
myBuilder.addPendingEdge(oldScope, instruction);
}
});
}
private void retargetOutgoingPatternEdges(@NotNull PsiElement scopeAncestor,
@NotNull BiFunction<PsiElement, RefutablePatternInstruction, PsiElement> newScopeProvider) {
retargetOutgoingEdges(scopeAncestor, (pendingScope, instruction) -> {
if (instruction instanceof RefutablePatternInstruction) {
return newScopeProvider.apply(pendingScope, (RefutablePatternInstruction)instruction);
}
return pendingScope;
});
}
}
@@ -0,0 +1,33 @@
package com.jetbrains.python.codeInsight.controlflow;
import com.intellij.codeInsight.controlflow.ControlFlowBuilder;
import com.intellij.codeInsight.controlflow.impl.InstructionImpl;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.psi.PyPattern;
import org.jetbrains.annotations.NotNull;
public class RefutablePatternInstruction extends InstructionImpl {
private final boolean myMatched;
public RefutablePatternInstruction(@NotNull ControlFlowBuilder builder,
@NotNull PyPattern element, boolean matched) {
super(builder, element);
myMatched = matched;
}
@Override
public @NotNull PsiElement getElement() {
PsiElement element = super.getElement();
assert element != null;
return element;
}
public boolean isMatched() {
return myMatched;
}
@Override
public @NotNull String getElementPresentation() {
return (myMatched ? "matched" : "refutable") + " pattern: " + getElement().getText();
}
}
@@ -3,6 +3,8 @@ package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.jetbrains.python.psi.PyAsPattern;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyPattern;
import org.jetbrains.annotations.NotNull;
public class PyAsPatternImpl extends PyElementImpl implements PyAsPattern {
public PyAsPatternImpl(ASTNode astNode) {
@@ -13,4 +15,9 @@ public class PyAsPatternImpl extends PyElementImpl implements PyAsPattern {
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyAsPattern(this);
}
@Override
public @NotNull PyPattern getPattern() {
return findNotNullChildByClass(PyPattern.class);
}
}
@@ -2,23 +2,32 @@ package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.psi.PyCaseClause;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyStatementList;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PyCaseClauseImpl extends PyElementImpl implements PyCaseClause {
public PyCaseClauseImpl(ASTNode astNode) {
super(astNode);
}
@Override
public @NotNull PyStatementList getStatementList() {
return childToPsiNotNull(PyElementTypes.STATEMENT_LIST);
}
@Override
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyCaseClause(this);
}
@Override
public @Nullable PyPattern getPattern() {
return findChildByClass(PyPattern.class);
}
@Override
public @Nullable PyExpression getGuardCondition() {
return findChildByClass(PyExpression.class);
}
@Override
public @NotNull PyStatementList getStatementList() {
return childToPsiNotNull(PyElementTypes.STATEMENT_LIST);
}
}
@@ -3,6 +3,8 @@ package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyGroupPattern;
import com.jetbrains.python.psi.PyPattern;
import org.jetbrains.annotations.NotNull;
public class PyGroupPatternImpl extends PyElementImpl implements PyGroupPattern {
public PyGroupPatternImpl(ASTNode astNode) {
@@ -13,4 +15,9 @@ public class PyGroupPatternImpl extends PyElementImpl implements PyGroupPattern
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyGroupPattern(this);
}
@Override
public @NotNull PyPattern getPattern() {
return findNotNullChildByClass(PyPattern.class);
}
}
@@ -1,8 +1,15 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.psi.PyCaseClause;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyMatchStatement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class PyMatchStatementImpl extends PyElementImpl implements PyMatchStatement {
public PyMatchStatementImpl(ASTNode astNode) {
@@ -13,4 +20,14 @@ public class PyMatchStatementImpl extends PyElementImpl implements PyMatchStatem
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyMatchStatement(this);
}
@Override
public @Nullable PyExpression getSubject() {
return findChildByClass(PyExpression.class);
}
@Override
public @NotNull List<PyCaseClause> getCaseClauses() {
return findChildrenByType(PyElementTypes.CASE_CLAUSE);
}
}
@@ -3,6 +3,11 @@ package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyOrPattern;
import com.jetbrains.python.psi.PyPattern;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
public class PyOrPatternImpl extends PyElementImpl implements PyOrPattern {
public PyOrPatternImpl(ASTNode astNode) {
@@ -13,4 +18,9 @@ public class PyOrPatternImpl extends PyElementImpl implements PyOrPattern {
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyOrPattern(this);
}
@Override
public @NotNull List<PyPattern> getAlternatives() {
return Arrays.asList(findChildrenByClass(PyPattern.class));
}
}
@@ -1,10 +1,16 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyWildcardPattern;
public class PyWildcardPatternImpl extends PyElementImpl implements PyWildcardPattern {
public PyWildcardPatternImpl(ASTNode astNode) {
super(astNode);
}
@Override
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitWildcardPattern(this);
}
}
@@ -0,0 +1,6 @@
while x:
match x:
case 42:
break
y
z
@@ -0,0 +1,14 @@
0(1) element: null
1(2) element: PyWhileStatement
2(3,11) READ ACCESS: x
3(4) element: PyStatementList. Condition: x:true
4(5) element: PyMatchStatement
5(6) READ ACCESS: x
6(7,9) refutable pattern: 42
7(8) matched pattern: 42
8(11) element: PyBreakStatement
9(10) element: PyExpressionStatement
10(1) READ ACCESS: y
11(12) element: PyExpressionStatement
12(13) READ ACCESS: z
13() element: null
@@ -0,0 +1,6 @@
while x:
match x:
case 42:
continue
y
z
@@ -0,0 +1,14 @@
0(1) element: null
1(2) element: PyWhileStatement
2(3,11) READ ACCESS: x
3(4) element: PyStatementList. Condition: x:true
4(5) element: PyMatchStatement
5(6) READ ACCESS: x
6(7,9) refutable pattern: 42
7(8) matched pattern: 42
8(1) element: PyContinueStatement
9(10) element: PyExpressionStatement
10(1) READ ACCESS: y
11(12) element: PyExpressionStatement
12(13) READ ACCESS: z
13() element: null
@@ -0,0 +1,5 @@
def func(x):
match x:
case 42:
return
y
@@ -0,0 +1,10 @@
0(1) element: null
1(2) WRITE ACCESS: x
2(3) element: PyMatchStatement
3(4) READ ACCESS: x
4(5,7) refutable pattern: 42
5(6) matched pattern: 42
6(9) element: PyReturnStatement
7(8) element: PyExpressionStatement
8(9) READ ACCESS: y
9() element: null
@@ -0,0 +1,9 @@
match 1:
case 1:
match 11:
case 11:
y11
y1
case 2:
y2
z
@@ -0,0 +1,18 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,11) refutable pattern: 1
3(4) matched pattern: 1
4(5) element: PyMatchStatement
5(6,9) refutable pattern: 11
6(7) matched pattern: 11
7(8) element: PyExpressionStatement
8(9) READ ACCESS: y11
9(10) element: PyExpressionStatement
10(15) READ ACCESS: y1
11(12,15) refutable pattern: 2
12(13) matched pattern: 2
13(14) element: PyExpressionStatement
14(15) READ ACCESS: y2
15(16) element: PyExpressionStatement
16(17) READ ACCESS: z
17() element: null
@@ -0,0 +1,8 @@
match 1:
case 1:
match 11:
case 11:
y11
case 2:
y2
z
@@ -0,0 +1,16 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,9) refutable pattern: 1
3(4) matched pattern: 1
4(5) element: PyMatchStatement
5(6,13) refutable pattern: 11
6(7) matched pattern: 11
7(8) element: PyExpressionStatement
8(13) READ ACCESS: y11
9(10,13) refutable pattern: 2
10(11) matched pattern: 2
11(12) element: PyExpressionStatement
12(13) READ ACCESS: y2
13(14) element: PyExpressionStatement
14(15) READ ACCESS: z
15() element: null
@@ -0,0 +1,4 @@
match 42:
case [42] | foo.bar as x:
y
z
@@ -0,0 +1,19 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,16) refutable pattern: [42] | foo.bar as x
3(4,16) refutable pattern: [42] | foo.bar
4(5,8) refutable pattern: [42]
5(6,8) refutable pattern: 42
6(7) matched pattern: 42
7(12) matched pattern: [42]
8(9,16) refutable pattern: foo.bar
9(10) READ ACCESS: foo
10(11) matched pattern: foo.bar
11(12) matched pattern: [42] | foo.bar
12(13) WRITE ACCESS: x
13(14) matched pattern: [42] | foo.bar as x
14(15) element: PyExpressionStatement
15(16) READ ACCESS: y
16(17) element: PyExpressionStatement
17(18) READ ACCESS: z
18() element: null
@@ -0,0 +1,4 @@
match 42:
case [x]:
y
z
@@ -0,0 +1,10 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,7) refutable pattern: [x]
3(4) WRITE ACCESS: x
4(5) matched pattern: [x]
5(6) element: PyExpressionStatement
6(7) READ ACCESS: y
7(8) element: PyExpressionStatement
8(9) READ ACCESS: z
9() element: null
@@ -0,0 +1,4 @@
match 42:
case x:
y
z
@@ -0,0 +1,8 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3) WRITE ACCESS: x
3(4) element: PyExpressionStatement
4(5) READ ACCESS: y
5(6) element: PyExpressionStatement
6(7) READ ACCESS: z
7() element: null
@@ -0,0 +1,4 @@
match 42:
case Class(1, attr=foo.bar):
x
y
@@ -0,0 +1,17 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,14) refutable pattern: Class(1, attr=foo.bar)
3(4) READ ACCESS: Class
4(5,14) refutable pattern: 1
5(6) matched pattern: 1
6(7,14) refutable pattern: attr=foo.bar
7(8,14) refutable pattern: foo.bar
8(9) READ ACCESS: foo
9(10) matched pattern: foo.bar
10(11) matched pattern: attr=foo.bar
11(12) matched pattern: Class(1, attr=foo.bar)
12(13) element: PyExpressionStatement
13(14) READ ACCESS: x
14(15) element: PyExpressionStatement
15(16) READ ACCESS: y
16() element: null
@@ -0,0 +1,4 @@
match 42:
case x if x > 0 and x < 10:
y
z
@@ -0,0 +1,16 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3) WRITE ACCESS: x
3(4) element: PyBinaryExpression
4(5,6) READ ACCESS: x
5(13) element: null. Condition: x > 0:false
6(7) element: null. Condition: x > 0:true
7(8,9) READ ACCESS: x
8(13) element: null. Condition: x < 10:false
9(10) element: null. Condition: x < 10:true
10(11) element: PyStatementList. Condition: x > 0 and x < 10:true
11(12) element: PyExpressionStatement
12(13) READ ACCESS: y
13(14) element: PyExpressionStatement
14(15) READ ACCESS: z
15() element: null
@@ -0,0 +1,4 @@
match 42:
case x if x % 4 == 0 and (x % 400 == 0 or x % 100 != 0):
y
z
@@ -0,0 +1,22 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3) WRITE ACCESS: x
3(4) element: PyBinaryExpression
4(5,6) READ ACCESS: x
5(19) element: null. Condition: x % 4 == 0:false
6(7) element: null. Condition: x % 4 == 0:true
7(8) element: PyBinaryExpression
8(9,10) READ ACCESS: x
9(16) element: null. Condition: x % 400 == 0:true
10(11) element: null. Condition: x % 400 == 0:false
11(12,13) READ ACCESS: x
12(19) element: null. Condition: x % 100 != 0:false
13(14,15) element: null. Condition: x % 100 != 0:true
14(19) element: null. Condition: (x % 400 == 0 or x % 100 != 0):false
15(16) element: null. Condition: (x % 400 == 0 or x % 100 != 0):true
16(17) element: PyStatementList. Condition: x % 4 == 0 and (x % 400 == 0 or x % 100 != 0):true
17(18) element: PyExpressionStatement
18(19) READ ACCESS: y
19(20) element: PyExpressionStatement
20(21) READ ACCESS: z
21() element: null
@@ -0,0 +1,4 @@
match 42:
case x if x > 0 or x < 0:
y
z
@@ -0,0 +1,16 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3) WRITE ACCESS: x
3(4) element: PyBinaryExpression
4(5,6) READ ACCESS: x
5(10) element: null. Condition: x > 0:true
6(7) element: null. Condition: x > 0:false
7(8,9) READ ACCESS: x
8(13) element: null. Condition: x < 0:false
9(10) element: null. Condition: x < 0:true
10(11) element: PyStatementList. Condition: x > 0 or x < 0:true
11(12) element: PyExpressionStatement
12(13) READ ACCESS: y
13(14) element: PyExpressionStatement
14(15) READ ACCESS: z
15() element: null
@@ -0,0 +1,4 @@
match 42:
case {"foo": 1, **x}:
y
z
@@ -0,0 +1,16 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,13) refutable pattern: {"foo": 1, **x}
3(4,13) refutable pattern: "foo": 1
4(5,13) refutable pattern: "foo"
5(6) matched pattern: "foo"
6(7,13) refutable pattern: 1
7(8) matched pattern: 1
8(9) matched pattern: "foo": 1
9(10) WRITE ACCESS: x
10(11) matched pattern: {"foo": 1, **x}
11(12) element: PyExpressionStatement
12(13) READ ACCESS: y
13(14) element: PyExpressionStatement
14(15) READ ACCESS: z
15() element: null
@@ -0,0 +1,4 @@
match 42:
case [x1, x2, x3] if (x1 or x2) > x3:
y
z
@@ -0,0 +1,21 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,18) refutable pattern: [x1, x2, x3]
3(4) WRITE ACCESS: x1
4(5) WRITE ACCESS: x2
5(6) WRITE ACCESS: x3
6(7) matched pattern: [x1, x2, x3]
7(8) element: PyBinaryExpression
8(9,10) READ ACCESS: x1
9(14) element: null. Condition: x1:true
10(11) element: null. Condition: x1:false
11(12,13) READ ACCESS: x2
12(14) element: null. Condition: x2:false
13(14) element: null. Condition: x2:true
14(15,18) READ ACCESS: x3
15(16) element: PyStatementList. Condition: (x1 or x2) > x3:true
16(17) element: PyExpressionStatement
17(18) READ ACCESS: y
18(19) element: PyExpressionStatement
19(20) READ ACCESS: z
20() element: null
@@ -0,0 +1,11 @@
0(1) element: null
1(2) element: PyMatchStatement
2(6) WRITE ACCESS: x
3(4,8) refutable pattern: [x]
4(5) WRITE ACCESS: x
5(6) matched pattern: [x]
6(7) element: PyExpressionStatement
7(8) READ ACCESS: y
8(9) element: PyExpressionStatement
9(10) READ ACCESS: z
10() element: null
@@ -0,0 +1,4 @@
match 42:
case [x] | x :
y
z
@@ -0,0 +1,11 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,5) refutable pattern: [x]
3(4) WRITE ACCESS: x
4(6) matched pattern: [x]
5(6) WRITE ACCESS: x
6(7) element: PyExpressionStatement
7(8) READ ACCESS: y
8(9) element: PyExpressionStatement
9(10) READ ACCESS: z
10() element: null
@@ -0,0 +1,4 @@
match 42:
case 42:
y
z
@@ -0,0 +1,9 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,6) refutable pattern: 42
3(4) matched pattern: 42
4(5) element: PyExpressionStatement
5(6) READ ACCESS: y
6(7) element: PyExpressionStatement
7(8) READ ACCESS: z
8() element: null
@@ -0,0 +1,4 @@
match 42:
case {'foo': 1, 'bar': foo.bar}:
x
y
@@ -0,0 +1,22 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,19) refutable pattern: {'foo': 1, 'bar': foo.bar}
3(4,19) refutable pattern: 'foo': 1
4(5,19) refutable pattern: 'foo'
5(6) matched pattern: 'foo'
6(7,19) refutable pattern: 1
7(8) matched pattern: 1
8(9) matched pattern: 'foo': 1
9(10,19) refutable pattern: 'bar': foo.bar
10(11,19) refutable pattern: 'bar'
11(12) matched pattern: 'bar'
12(13,19) refutable pattern: foo.bar
13(14) READ ACCESS: foo
14(15) matched pattern: foo.bar
15(16) matched pattern: 'bar': foo.bar
16(17) matched pattern: {'foo': 1, 'bar': foo.bar}
17(18) element: PyExpressionStatement
18(19) READ ACCESS: x
19(20) element: PyExpressionStatement
20(21) READ ACCESS: y
21() element: null
@@ -0,0 +1,4 @@
match 42:
case [1, *x]:
y
z
@@ -0,0 +1,12 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,9) refutable pattern: [1, *x]
3(4,9) refutable pattern: 1
4(5) matched pattern: 1
5(6) WRITE ACCESS: x
6(7) matched pattern: [1, *x]
7(8) element: PyExpressionStatement
8(9) READ ACCESS: y
9(10) element: PyExpressionStatement
10(11) READ ACCESS: z
11() element: null
@@ -0,0 +1,4 @@
match 42:
case 1 | (2 | 3):
x
y
@@ -0,0 +1,19 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,16) refutable pattern: 1 | (2 | 3)
3(4,5) refutable pattern: 1
4(14) matched pattern: 1
5(6,16) refutable pattern: (2 | 3)
6(7,16) refutable pattern: 2 | 3
7(8,9) refutable pattern: 2
8(14) matched pattern: 2
9(10,16) refutable pattern: 3
10(11) matched pattern: 3
11(12) matched pattern: 2 | 3
12(13) matched pattern: (2 | 3)
13(14) matched pattern: 1 | (2 | 3)
14(15) element: PyExpressionStatement
15(16) READ ACCESS: x
16(17) element: PyExpressionStatement
17(18) READ ACCESS: y
18() element: null
@@ -0,0 +1,4 @@
match 42:
case ((x)):
y
z
@@ -0,0 +1,8 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3) WRITE ACCESS: x
3(4) element: PyExpressionStatement
4(5) READ ACCESS: y
5(6) element: PyExpressionStatement
6(7) READ ACCESS: z
7() element: null
@@ -0,0 +1,4 @@
match 42:
case [x] | (foo.bar as x):
y
z
@@ -0,0 +1,20 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,17) refutable pattern: [x] | (foo.bar as x)
3(4,6) refutable pattern: [x]
4(5) WRITE ACCESS: x
5(15) matched pattern: [x]
6(7,17) refutable pattern: (foo.bar as x)
7(8,17) refutable pattern: foo.bar as x
8(9,17) refutable pattern: foo.bar
9(10) READ ACCESS: foo
10(11) matched pattern: foo.bar
11(12) WRITE ACCESS: x
12(13) matched pattern: foo.bar as x
13(14) matched pattern: (foo.bar as x)
14(15) matched pattern: [x] | (foo.bar as x)
15(16) element: PyExpressionStatement
16(17) READ ACCESS: y
17(18) element: PyExpressionStatement
18(19) READ ACCESS: z
19() element: null
@@ -0,0 +1,13 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,10) refutable pattern: [] | 42
3(4,5) refutable pattern: []
4(8) matched pattern: []
5(6,10) refutable pattern: 42
6(7) matched pattern: 42
7(8) matched pattern: [] | 42
8(9) element: PyExpressionStatement
9(10) READ ACCESS: y
10(11) element: PyExpressionStatement
11(12) READ ACCESS: z
12() element: null
@@ -0,0 +1,4 @@
match 42:
case _ | 42:
y
z
@@ -0,0 +1,10 @@
0(1) element: null
1(2) element: PyMatchStatement
2(5) element: PyWildcardPattern
3(4,7) refutable pattern: 42
4(5) matched pattern: 42
5(6) element: PyExpressionStatement
6(7) READ ACCESS: y
7(8) element: PyExpressionStatement
8(9) READ ACCESS: z
9() element: null
@@ -0,0 +1,4 @@
match 42:
case [x] if x > 0 and x % 2 == 0:
y
z
@@ -0,0 +1,18 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,15) refutable pattern: [x]
3(4) WRITE ACCESS: x
4(5) matched pattern: [x]
5(6) element: PyBinaryExpression
6(7,8) READ ACCESS: x
7(15) element: null. Condition: x > 0:false
8(9) element: null. Condition: x > 0:true
9(10,11) READ ACCESS: x
10(15) element: null. Condition: x % 2 == 0:false
11(12) element: null. Condition: x % 2 == 0:true
12(13) element: PyStatementList. Condition: x > 0 and x % 2 == 0:true
13(14) element: PyExpressionStatement
14(15) READ ACCESS: y
15(16) element: PyExpressionStatement
16(17) READ ACCESS: z
17() element: null
@@ -0,0 +1,4 @@
match 42:
case [1, foo.bar]:
x
y
@@ -0,0 +1,14 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,11) refutable pattern: [1, foo.bar]
3(4,11) refutable pattern: 1
4(5) matched pattern: 1
5(6,11) refutable pattern: foo.bar
6(7) READ ACCESS: foo
7(8) matched pattern: foo.bar
8(9) matched pattern: [1, foo.bar]
9(10) element: PyExpressionStatement
10(11) READ ACCESS: x
11(12) element: PyExpressionStatement
12(13) READ ACCESS: y
13() element: null
@@ -0,0 +1,4 @@
match 42:
case [1 | x]:
y
z
@@ -0,0 +1,12 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,9) refutable pattern: [1 | x]
3(4,5) refutable pattern: 1
4(7) matched pattern: 1
5(6) WRITE ACCESS: x
6(7) matched pattern: [1 | x]
7(8) element: PyExpressionStatement
8(9) READ ACCESS: y
9(10) element: PyExpressionStatement
10(11) READ ACCESS: z
11() element: null
@@ -0,0 +1,4 @@
match 42:
case x if x > 0:
y
z
@@ -0,0 +1,10 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3) WRITE ACCESS: x
3(4,7) READ ACCESS: x
4(5) element: PyStatementList. Condition: x > 0:true
5(6) element: PyExpressionStatement
6(7) READ ACCESS: y
7(8) element: PyExpressionStatement
8(9) READ ACCESS: z
9() element: null
@@ -0,0 +1,4 @@
match 42:
case _:
y
z
@@ -0,0 +1,8 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3) element: PyWildcardPattern
3(4) element: PyExpressionStatement
4(5) READ ACCESS: y
5(6) element: PyExpressionStatement
6(7) READ ACCESS: z
7() element: null
@@ -0,0 +1,12 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,9) refutable pattern: [1, *_]
3(4,9) refutable pattern: 1
4(5) matched pattern: 1
5(6) element: PyWildcardPattern
6(7) matched pattern: [1, *_]
7(8) element: PyExpressionStatement
8(9) READ ACCESS: y
9(10) element: PyExpressionStatement
10(11) READ ACCESS: z
11() element: null
@@ -0,0 +1,6 @@
match 42:
case x:
y
case 42:
y
z
@@ -0,0 +1,12 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3) WRITE ACCESS: x
3(4) element: PyExpressionStatement
4(9) READ ACCESS: y
5(6,9) refutable pattern: 42
6(7) matched pattern: 42
7(8) element: PyExpressionStatement
8(9) READ ACCESS: y
9(10) element: PyExpressionStatement
10(11) READ ACCESS: z
11() element: null
@@ -0,0 +1,6 @@
match 42:
case 42:
y
case x:
y
z
@@ -0,0 +1,12 @@
0(1) element: null
1(2) element: PyMatchStatement
2(3,6) refutable pattern: 42
3(4) matched pattern: 42
4(5) element: PyExpressionStatement
5(9) READ ACCESS: y
6(7) WRITE ACCESS: x
7(8) element: PyExpressionStatement
8(9) READ ACCESS: y
9(10) element: PyExpressionStatement
10(11) READ ACCESS: z
11() element: null
@@ -0,0 +1,3 @@
match x:
case (1 as y) | y:
pass
@@ -0,0 +1,3 @@
match x:
case [1 as y, <warning descr="Redeclared 'y' defined above without usage">y</warning>]:
pass
@@ -0,0 +1,7 @@
def func(x):
match x:
case 42 as y:
pass
case z:
pass
print(<warning descr="Local variable 'y' might be referenced before assignment">y</warning>, <warning descr="Local variable 'z' might be referenced before assignment">z</warning>)
@@ -0,0 +1,5 @@
def func(x):
match x:
case 42:
y = 'foo'
print(<warning descr="Local variable 'y' might be referenced before assignment">y</warning>)
@@ -0,0 +1,4 @@
def func(x):
match x:
case [1, y] | [2, z]:
print(<warning descr="Local variable 'y' might be referenced before assignment">y</warning>, <warning descr="Local variable 'z' might be referenced before assignment">z</warning>)
@@ -0,0 +1,6 @@
def func(xs):
for x in xs:
match x:
case 42:
break
print(x)
@@ -0,0 +1,6 @@
def func(xs):
for x in xs:
match x:
case 42:
continue
print(x)
@@ -0,0 +1,5 @@
def func(x):
match x:
case 42:
return
print(x)
@@ -0,0 +1,5 @@
match 42:
case x:
pass
case <warning descr="This code is unreachable">42</warning>:
pass
@@ -0,0 +1,3 @@
match 42:
case x | <warning descr="This code is unreachable">42</warning>:
pass
@@ -0,0 +1,4 @@
def func(x):
match x:
case (1 as y) | (2 as y):
print(y)
@@ -0,0 +1,4 @@
def func(x):
match x:
case [used, <weak_warning descr="Local variable 'unused' value is not used">unused</weak_warning>]:
print(used)
@@ -318,6 +318,166 @@ public class PyControlFlowBuilderTest extends LightMarkedTestCase {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseCapturePattern() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseWildcardPattern() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseLiteralPattern() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseBindingSequencePattern() {
doTest();
}
// PY-48760
public void testMatchStatementTwoClausesCapturePatternFirst() {
doTest();
}
// PY-48760
public void testMatchStatementTwoClausesCapturePatternLast() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseAliasedRefutableOrPattern() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseRefutableOrPattern() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseIrrefutableOrPatternCaptureVariantFirst() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseIrrefutableOrPatternCaptureVariantLast() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseRefutableOrPatternWithNonBindingVariants() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseRefutableOrPatternWithWildcard() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseSequencePatternWithSingleOrPattern() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseNestedOrPatterns() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseClassPattern() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseMappingPattern() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseSequencePattern() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseParenthesizedCapturePattern() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseNamedSingleStarPatternIsIrrefutable() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseWildcardSingleStarPatternIsIrrefutable() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseDoubleStarPatternIsIrrefutable() {
doTest();
}
// PY-48760
public void testMatchStatementClauseWithBreak() {
doTest();
}
// PY-48760
public void testMatchStatementClauseWithContinue() {
doTest();
}
// PY-48760
public void testMatchStatementClauseWithReturn() {
doTestFirstStatement();
}
// PY-48760
public void testMatchStatementNestedMatchStatementLastInClause() {
doTest();
}
// PY-48760
public void testMatchStatementNestedMatchStatement() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseTrivialGuard() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseDisjunctionGuard() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseConjunctionGuard() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseDisjunctionConjunctionGuard() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseRefutablePatternAndConjunctionGuard() {
doTest();
}
// PY-48760
public void testMatchStatementSingleClauseGuardWithNonTopLevelDisjunction() {
doTest();
}
private void doTestFirstStatement() {
final String testName = getTestName(false);
configureByFile(testName + ".py");
@@ -198,6 +198,16 @@ public class PyRedeclarationInspectionTest extends PyInspectionTestCase {
runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest);
}
// PY-48760
public void testNotReportedForNameRedeclarationInOrPattern() {
doTest();
}
// PY-48760
public void testPatternBindsSameNameTwice() {
doTest();
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {
@@ -362,6 +362,21 @@ public class PyUnboundLocalVariableInspectionTest extends PyInspectionTestCase {
" print(<warning descr=\"Name 'r' can be undefined\">r</warning>)");
}
// PY-48760
public void testCapturePatternNameUsedAfterMatchStatement() {
doTest();
}
// PY-48760
public void testOrPatternAlternativesDefineDifferentNames() {
doTest();
}
// PY-48760
public void testNameDefinedInCaseClauseBodyUsedAfterMatchStatement() {
doTest();
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {
@@ -170,6 +170,31 @@ public class PyUnreachableCodeInspectionTest extends PyInspectionTestCase {
);
}
// PY-48760
public void testContinueInCaseClause() {
doTest();
}
// PY-48760
public void testBreakInCaseClause() {
doTest();
}
// PY-48760
public void testReturnInCaseClause() {
doTest();
}
// PY-48760
public void testUnreachablePatternAfterIrrefutableCaseClause() {
doTest();
}
// PY-48760
public void testUnreachablePatternAfterIrrefutableOrPatternAlternative() {
doTest();
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {
@@ -203,6 +203,16 @@ public class PyUnusedLocalInspectionTest extends PyInspectionTestCase {
doTest();
}
// PY-48760
public void testAllBindingsOfSameNameInOrPatternConsideredUsed() {
doTest();
}
// PY-48760
public void testUnusedCapturePatterns() {
doTest();
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {