mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
committed by
intellij-monorepo-bot
parent
00eb6dbaf4
commit
fa138bf032
+11
-7
@@ -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)) {
|
||||
|
||||
+6
-3
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user