From dd63fcbb7d50c48c4dcb9320cc9042d9f3e83deb Mon Sep 17 00:00:00 2001 From: Samer Masterson Date: Mon, 17 Dec 2018 16:52:43 -0800 Subject: [PATCH] Short-circuit `if` expression for always true/always false vars and MYPY/TYPE_CHECKING - 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 --- mypy/reachability.py | 8 ++--- mypy/semanal.py | 11 ++++--- test-data/unit/check-unreachable-code.test | 36 ++++++++++++++++++++++ 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/mypy/reachability.py b/mypy/reachability.py index 7c17ec230250..4585c08c861a 100644 --- a/mypy/reachability.py +++ b/mypy/reachability.py @@ -84,8 +84,8 @@ def infer_condition_value(expr: Expression, options: Options) -> int: name = expr.name elif isinstance(expr, OpExpr) and expr.op in ('and', 'or'): left = infer_condition_value(expr.left, options) - if ((left == ALWAYS_TRUE and expr.op == 'and') or - (left == ALWAYS_FALSE and expr.op == 'or')): + if ((left in (ALWAYS_TRUE, MYPY_TRUE) and expr.op == 'and') or + (left in (ALWAYS_FALSE, MYPY_FALSE) and expr.op == 'or')): # Either `True and ` or `False or `: the result will # always be the right-hand-side. return infer_condition_value(expr.right, options) @@ -105,9 +105,9 @@ def infer_condition_value(expr: Expression, options: Options) -> int: elif name == 'MYPY' or name == 'TYPE_CHECKING': result = MYPY_TRUE elif name in options.always_true: - result = MYPY_TRUE + result = ALWAYS_TRUE elif name in options.always_false: - result = MYPY_FALSE + result = ALWAYS_FALSE if negated: result = inverted_truth_mapping[result] return result diff --git a/mypy/semanal.py b/mypy/semanal.py index 58eb1866de24..38277e18b600 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -90,7 +90,8 @@ from mypy.semanal_enum import EnumCallAnalyzer from mypy.semanal_newtype import NewTypeAnalyzer from mypy.reachability import ( - infer_reachability_of_if_statement, infer_condition_value, ALWAYS_FALSE, ALWAYS_TRUE + infer_reachability_of_if_statement, infer_condition_value, ALWAYS_FALSE, ALWAYS_TRUE, + MYPY_TRUE, MYPY_FALSE ) from mypy.typestate import TypeState @@ -3102,12 +3103,12 @@ def visit_op_expr(self, expr: OpExpr) -> None: if expr.op in ('and', 'or'): inferred = infer_condition_value(expr.left, self.options) - if ((inferred == ALWAYS_FALSE and expr.op == 'and') or - (inferred == ALWAYS_TRUE and expr.op == 'or')): + if ((inferred in (ALWAYS_FALSE, MYPY_FALSE) and expr.op == 'and') or + (inferred in (ALWAYS_TRUE, MYPY_TRUE) and expr.op == 'or')): expr.right_unreachable = True return - elif ((inferred == ALWAYS_TRUE and expr.op == 'and') or - (inferred == ALWAYS_FALSE and expr.op == 'or')): + elif ((inferred in (ALWAYS_TRUE, MYPY_TRUE) and expr.op == 'and') or + (inferred in (ALWAYS_FALSE, MYPY_FALSE) and expr.op == 'or')): expr.right_always = True expr.right.accept(self) diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 11683c04c560..badd8910d1ad 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -567,6 +567,42 @@ else: reveal_type(y) # E: Revealed type is 'builtins.str' [builtins fixtures/ops.pyi] +[case testShortCircuitNoEvaluation] +# flags: --platform linux --always-false COMPILE_TIME_FALSE +import sys + +if sys.platform == 'darwin': + mac_only = 'junk' + +# `mac_only` should not be evaluated +if sys.platform == 'darwin' and mac_only: + pass +if sys.platform == 'linux' or mac_only: + pass + +COMPILE_TIME_FALSE = 'junk' + +if COMPILE_TIME_FALSE: + compile_time_false_only = 'junk' + +# `compile_time_false_only` should not be evaluated +if COMPILE_TIME_FALSE and compile_time_false_only: + pass +if not COMPILE_TIME_FALSE or compile_time_false_only: + pass + +MYPY = False + +if not MYPY: + mypy_only = 'junk' + +# `mypy_only` should not be evaluated +if not MYPY and mypy_only: + pass +if MYPY or mypy_only: + pass +[builtins fixtures/ops.pyi] + [case testConditionalAssertWithoutElse] import typing