PY-50642 Take into account statically evaluated if-conditions when building CFG

Unreachable if-branch no longer rejoins the main control flow to prevent visiting it when traversing CFG backwards (e.g., in `PyDefUseUtil.getLatestDefs()`).

Also `typing.TYPE_CHECKING` checks are now taken into account when building CFG.

GitOrigin-RevId: b67c71872c53aee556351e6e025a1091cdaae226
This commit is contained in:
Petr
2025-11-28 19:07:18 +00:00
committed by intellij-monorepo-bot
parent 00eb6dbaf4
commit fa138bf032
10 changed files with 60 additions and 22 deletions
@@ -484,7 +484,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
myBuilder.startNode(node);
List<Instruction> exitInstructions = new ArrayList<>();
boolean unreachable = false;
boolean seenAlwaysTrueCondition = false;
for (PyIfPart ifPart : StreamEx.of(node.getIfPart()).append(node.getElifParts())) {
TransparentInstruction thenNode = addTransparentInstruction();
TransparentInstruction elseNode = addTransparentInstruction();
@@ -495,28 +495,32 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
myBuilder.prevInstruction = thenNode;
Boolean conditionResult = PyEvaluator.evaluateAsBooleanNoResolve(condition);
if (unreachable || Boolean.FALSE.equals(conditionResult)) {
// Condition is always False, or some previous condition is always True.
boolean unreachable = seenAlwaysTrueCondition || Boolean.FALSE.equals(conditionResult);
if (unreachable) {
addAssertTypeNever();
}
if (Boolean.TRUE.equals(conditionResult)) {
unreachable = true;
seenAlwaysTrueCondition = true;
}
visitPyStatementPart(ifPart);
exitInstructions.add(myBuilder.prevInstruction);
if (!unreachable) {
exitInstructions.add(myBuilder.prevInstruction);
}
myBuilder.prevInstruction = elseNode;
}
final PyElsePart elsePart = node.getElsePart();
if (elsePart != null) {
if (unreachable) {
if (seenAlwaysTrueCondition) {
addAssertTypeNever();
}
visitPyStatementPart(elsePart);
}
exitInstructions.add(myBuilder.prevInstruction);
if (!seenAlwaysTrueCondition) {
exitInstructions.add(myBuilder.prevInstruction);
}
myBuilder.prevInstruction = addTransparentInstruction(node);
for (Instruction exitInstruction : Lists.reverse(exitInstructions)) {
@@ -274,15 +274,18 @@ class PyInvertIfConditionIntention : PsiUpdateModCommandAction<PsiElement>(PsiEl
private val PyStatementList.isTerminated: Boolean
get() {
val controlFlow = ControlFlowCache.getControlFlow(parentsOfType<ScopeOwner>().first())
val currentElement = this
val currentInstruction = controlFlow.instructions.first { it.element == currentElement }
val currentInstruction = controlFlow.instructions.first { it.element == this }
var result = true
ControlFlowUtil.iterate(currentInstruction.num(), controlFlow.instructions, { instruction ->
when {
instruction.allSucc().isEmpty() -> {
result = false
ControlFlowUtil.Operation.BREAK
}
instruction == currentInstruction -> ControlFlowUtil.Operation.NEXT
instruction is ReadWriteInstruction -> ControlFlowUtil.Operation.NEXT
instruction.element == null -> ControlFlowUtil.Operation.NEXT
!instruction.element!!.parents(false).contains(currentElement) -> {
!instruction.element!!.parents(false).contains(this) -> {
result = false
ControlFlowUtil.Operation.BREAK
}
@@ -18,6 +18,7 @@ package com.jetbrains.python.psi.impl;
import com.intellij.psi.PsiElement;
import com.intellij.psi.ResolveResult;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyNames;
@@ -40,6 +41,11 @@ import java.util.*;
*/
public class PyEvaluator {
private static final List<QualifiedName> TYPING_TYPE_CHECKING_NAMES = List.of(
QualifiedName.fromDottedString("TYPE_CHECKING"),
QualifiedName.fromDottedString("typing.TYPE_CHECKING")
);
private final @NotNull Set<PyExpression> myVisited = new HashSet<>();
private @Nullable Map<String, Object> myNamespace = null;
@@ -253,6 +259,9 @@ public class PyEvaluator {
}
protected @Nullable Object evaluateReference(@NotNull PyReferenceExpression expression) {
if (isTypeCheckingExpression(expression)) {
return true;
}
if (!expression.isQualified()) {
if (myNamespace != null) {
return myNamespace.get(expression.getReferencedName());
@@ -456,4 +465,9 @@ public class PyEvaluator {
}
return myAllowExpressionsAsValues ? expression : null;
}
private static boolean isTypeCheckingExpression(@NotNull PyReferenceExpression expression) {
QualifiedName qualifiedName = expression.asQualifiedName();
return qualifiedName != null && TYPING_TYPE_CHECKING_NAMES.contains(qualifiedName);
}
}
@@ -13,7 +13,7 @@
12(13) ASSERTTYPE ACCESS: null
13(14) element: PyStatementList
14(15) element: PyAssignmentStatement
15(16) WRITE ACCESS: a
15() WRITE ACCESS: a
16(17) element: PyIfStatement
17(18,19) READ ACCESS: d
18(23) element: null. Condition: d:false
@@ -28,7 +28,7 @@
27(28) ASSERTTYPE ACCESS: null
28(29) element: PyStatementList
29(30) element: PyAssignmentStatement
30(33) WRITE ACCESS: b
30() WRITE ACCESS: b
31(32) element: PyStatementList
32(33) element: PyPassStatement
33() element: null
@@ -7,7 +7,7 @@
6(7) element: PyStatementList
7(14) element: PyPassStatement
8(9,10) READ ACCESS: True
9(14) element: null. Condition: True:false
9() element: null. Condition: True:false
10(11) element: null. Condition: True:true
11(12) ASSERTTYPE ACCESS: True
12(13) element: PyStatementList
@@ -28,5 +28,5 @@
27(28) ASSERTTYPE ACCESS: null
28(29) element: PyStatementList
29(30) element: PyAssignmentStatement
30(31) WRITE ACCESS: e
30() WRITE ACCESS: e
31() element: null
@@ -7,7 +7,7 @@
6(7) ASSERTTYPE ACCESS: null
7(8) element: PyStatementList
8(9) element: PyAssignmentStatement
9(10) WRITE ACCESS: a
9() WRITE ACCESS: a
10(11) element: PyIfStatement
11(12,13) READ ACCESS: False
12(19) element: null. Condition: False:false
@@ -16,7 +16,7 @@
15(16) ASSERTTYPE ACCESS: null
16(17) element: PyStatementList
17(18) element: PyAssignmentStatement
18(21) WRITE ACCESS: b
18() WRITE ACCESS: b
19(20) element: PyStatementList
20(21) element: PyPassStatement
21(22) element: PyIfStatement
@@ -27,7 +27,7 @@
26(27) ASSERTTYPE ACCESS: null
27(28) element: PyStatementList
28(29) element: PyAssignmentStatement
29(38) WRITE ACCESS: c
29() WRITE ACCESS: c
30(31,32) READ ACCESS: d
31(36) element: null. Condition: d:false
32(33) element: null. Condition: d:true
@@ -1,7 +1,7 @@
0(1) element: null
1(2) element: PyIfStatement
2(3,4) READ ACCESS: True
3(8) element: null. Condition: True:false
3() element: null. Condition: True:false
4(5) element: null. Condition: True:true
5(6) ASSERTTYPE ACCESS: True
6(7) element: PyStatementList
@@ -16,7 +16,7 @@
15(16) ASSERTTYPE ACCESS: null
16(17) element: PyStatementList
17(18) element: PyAssignmentStatement
18(19) WRITE ACCESS: b
18() WRITE ACCESS: b
19(20) element: PyIfStatement
20(21,22) READ ACCESS: True
21(26) element: null. Condition: True:false
@@ -31,9 +31,9 @@
30(31) ASSERTTYPE ACCESS: null
31(32) element: PyStatementList
32(33) element: PyAssignmentStatement
33(38) WRITE ACCESS: d
33() WRITE ACCESS: d
34(35) ASSERTTYPE ACCESS: null
35(36) element: PyStatementList
36(37) element: PyAssignmentStatement
37(38) WRITE ACCESS: e
37() WRITE ACCESS: e
38() element: null
@@ -14,5 +14,5 @@
13(17) element: PyBreakStatement
14(15) ASSERTTYPE ACCESS: null
15(16) element: PyStatementList
16(17) element: PyPrintStatement
16() element: PyPrintStatement
17() element: null
@@ -3335,6 +3335,18 @@ public class Py3TypeTest extends PyTestCase {
});
}
// PY-50642
public void testTypeChecking() {
doTest("int", """
from typing import TYPE_CHECKING
if not not TYPE_CHECKING:
v: int = -1
else:
v: str = 'ab'
expr = v
""");
}
// PY-73958
public void testNoStackOverflow() {
@@ -314,6 +314,11 @@ public class PyEvaluatorTest extends PyTestCase {
assertNull(PyEvaluator.evaluateAsBooleanNoResolve(parseText("a = []\nexpr = a")));
}
public void testTypingTypeChecking() {
assertTrue(PyEvaluator.evaluateAsBooleanNoResolve(parseText("expr = typing.TYPE_CHECKING")));
assertTrue(PyEvaluator.evaluateAsBooleanNoResolve(parseText("expr = TYPE_CHECKING")));
}
@NotNull
private <T> T byExpression(@NotNull String expression, @NotNull Class<T> cls) {
final Object value = new PyEvaluator().evaluate(parseExpression(expression));