Skip to content

Commit 833517d

Browse files
samertmmsullivan
authored andcommitted
Short-circuit if expression for always true/always false vars and MYPY/TYPE_CHECKING (#5965)
- Always short-circuit in the if's expression, instead of only short-circuiting for "ALWAYS_TRUE/FALSE" constants (e.g. sys.platform checks) and not "MYPY_TRUE/FALSE" constants (e.g. `MYPY`) - Make --always-true and --always-false map to ALWAYS_TRUE and ALWAYS_FALSE, respectively, instead of MYPY_TRUE and MYPY_FALSE. The only difference between the two is a change in import priority, but semantically uses of "--always-true" are probably closer to "this condition should be true at compile time and runtime" than "this condition should be true at compile time but false at runtime". - Fixes #5963
1 parent 01c2686 commit 833517d

File tree

3 files changed

+46
-9
lines changed

3 files changed

+46
-9
lines changed

mypy/reachability.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ def infer_condition_value(expr: Expression, options: Options) -> int:
8484
name = expr.name
8585
elif isinstance(expr, OpExpr) and expr.op in ('and', 'or'):
8686
left = infer_condition_value(expr.left, options)
87-
if ((left == ALWAYS_TRUE and expr.op == 'and') or
88-
(left == ALWAYS_FALSE and expr.op == 'or')):
87+
if ((left in (ALWAYS_TRUE, MYPY_TRUE) and expr.op == 'and') or
88+
(left in (ALWAYS_FALSE, MYPY_FALSE) and expr.op == 'or')):
8989
# Either `True and <other>` or `False or <other>`: the result will
9090
# always be the right-hand-side.
9191
return infer_condition_value(expr.right, options)
@@ -105,9 +105,9 @@ def infer_condition_value(expr: Expression, options: Options) -> int:
105105
elif name == 'MYPY' or name == 'TYPE_CHECKING':
106106
result = MYPY_TRUE
107107
elif name in options.always_true:
108-
result = MYPY_TRUE
108+
result = ALWAYS_TRUE
109109
elif name in options.always_false:
110-
result = MYPY_FALSE
110+
result = ALWAYS_FALSE
111111
if negated:
112112
result = inverted_truth_mapping[result]
113113
return result

mypy/semanal.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@
9090
from mypy.semanal_enum import EnumCallAnalyzer
9191
from mypy.semanal_newtype import NewTypeAnalyzer
9292
from mypy.reachability import (
93-
infer_reachability_of_if_statement, infer_condition_value, ALWAYS_FALSE, ALWAYS_TRUE
93+
infer_reachability_of_if_statement, infer_condition_value, ALWAYS_FALSE, ALWAYS_TRUE,
94+
MYPY_TRUE, MYPY_FALSE
9495
)
9596
from mypy.typestate import TypeState
9697

@@ -3102,12 +3103,12 @@ def visit_op_expr(self, expr: OpExpr) -> None:
31023103

31033104
if expr.op in ('and', 'or'):
31043105
inferred = infer_condition_value(expr.left, self.options)
3105-
if ((inferred == ALWAYS_FALSE and expr.op == 'and') or
3106-
(inferred == ALWAYS_TRUE and expr.op == 'or')):
3106+
if ((inferred in (ALWAYS_FALSE, MYPY_FALSE) and expr.op == 'and') or
3107+
(inferred in (ALWAYS_TRUE, MYPY_TRUE) and expr.op == 'or')):
31073108
expr.right_unreachable = True
31083109
return
3109-
elif ((inferred == ALWAYS_TRUE and expr.op == 'and') or
3110-
(inferred == ALWAYS_FALSE and expr.op == 'or')):
3110+
elif ((inferred in (ALWAYS_TRUE, MYPY_TRUE) and expr.op == 'and') or
3111+
(inferred in (ALWAYS_FALSE, MYPY_FALSE) and expr.op == 'or')):
31113112
expr.right_always = True
31123113

31133114
expr.right.accept(self)

test-data/unit/check-unreachable-code.test

+36
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,42 @@ else:
567567
reveal_type(y) # E: Revealed type is 'builtins.str'
568568
[builtins fixtures/ops.pyi]
569569

570+
[case testShortCircuitNoEvaluation]
571+
# flags: --platform linux --always-false COMPILE_TIME_FALSE
572+
import sys
573+
574+
if sys.platform == 'darwin':
575+
mac_only = 'junk'
576+
577+
# `mac_only` should not be evaluated
578+
if sys.platform == 'darwin' and mac_only:
579+
pass
580+
if sys.platform == 'linux' or mac_only:
581+
pass
582+
583+
COMPILE_TIME_FALSE = 'junk'
584+
585+
if COMPILE_TIME_FALSE:
586+
compile_time_false_only = 'junk'
587+
588+
# `compile_time_false_only` should not be evaluated
589+
if COMPILE_TIME_FALSE and compile_time_false_only:
590+
pass
591+
if not COMPILE_TIME_FALSE or compile_time_false_only:
592+
pass
593+
594+
MYPY = False
595+
596+
if not MYPY:
597+
mypy_only = 'junk'
598+
599+
# `mypy_only` should not be evaluated
600+
if not MYPY and mypy_only:
601+
pass
602+
if MYPY or mypy_only:
603+
pass
604+
[builtins fixtures/ops.pyi]
605+
570606
[case testConditionalAssertWithoutElse]
571607
import typing
572608

0 commit comments

Comments
 (0)