diff --git a/mypy/reachability.py b/mypy/reachability.py index 5d170b5071db..dd54f927773a 100644 --- a/mypy/reachability.py +++ b/mypy/reachability.py @@ -115,31 +115,44 @@ def infer_condition_value(expr: Expression, options: Options) -> int: MYPY_TRUE if true under mypy and false at runtime, MYPY_FALSE if false under mypy and true at runtime, else TRUTH_VALUE_UNKNOWN. """ + if isinstance(expr, UnaryExpr) and expr.op == "not": + positive = infer_condition_value(expr.expr, options) + return inverted_truth_mapping[positive] + pyversion = options.python_version name = "" - negated = False - alias = expr - if isinstance(alias, UnaryExpr): - if alias.op == "not": - expr = alias.expr - negated = True + result = TRUTH_VALUE_UNKNOWN if isinstance(expr, NameExpr): name = expr.name elif isinstance(expr, MemberExpr): name = expr.name elif isinstance(expr, OpExpr) and expr.op in ("and", "or"): + if expr.op not in ("or", "and"): + return TRUTH_VALUE_UNKNOWN + left = infer_condition_value(expr.left, options) - 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) - else: - # The result will always be the left-hand-side (e.g. ALWAYS_* or - # TRUTH_VALUE_UNKNOWN). - return left + right = infer_condition_value(expr.right, options) + results = {left, right} + if expr.op == "or": + if ALWAYS_TRUE in results: + return ALWAYS_TRUE + elif MYPY_TRUE in results: + return MYPY_TRUE + elif left == right == MYPY_FALSE: + return MYPY_FALSE + elif results <= {ALWAYS_FALSE, MYPY_FALSE}: + return ALWAYS_FALSE + elif expr.op == "and": + if ALWAYS_FALSE in results: + return ALWAYS_FALSE + elif MYPY_FALSE in results: + return MYPY_FALSE + elif left == right == ALWAYS_TRUE: + return ALWAYS_TRUE + elif results <= {ALWAYS_TRUE, MYPY_TRUE}: + return MYPY_TRUE + return TRUTH_VALUE_UNKNOWN else: result = consider_sys_version_info(expr, pyversion) if result == TRUTH_VALUE_UNKNOWN: @@ -155,8 +168,6 @@ def infer_condition_value(expr: Expression, options: Options) -> int: result = ALWAYS_TRUE elif name in options.always_false: result = ALWAYS_FALSE - if negated: - result = inverted_truth_mapping[result] return result diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index a40aa21ff26a..5f3ceda3f27d 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -500,6 +500,82 @@ reveal_type(h) # N: Revealed type is "builtins.bool" [builtins fixtures/ops.pyi] [out] +[case testConditionalValuesBinaryOps] +# flags: --platform linux +import sys + +t_and_t = (sys.platform == 'linux' and sys.platform == 'linux') and 's' +t_or_t = (sys.platform == 'linux' or sys.platform == 'linux') and 's' +t_and_f = (sys.platform == 'linux' and sys.platform == 'windows') and 's' +t_or_f = (sys.platform == 'linux' or sys.platform == 'windows') and 's' +f_and_t = (sys.platform == 'windows' and sys.platform == 'linux') and 's' +f_or_t = (sys.platform == 'windows' or sys.platform == 'linux') and 's' +f_and_f = (sys.platform == 'windows' and sys.platform == 'windows') and 's' +f_or_f = (sys.platform == 'windows' or sys.platform == 'windows') and 's' +reveal_type(t_and_t) # N: Revealed type is "Literal['s']" +reveal_type(t_or_t) # N: Revealed type is "Literal['s']" +reveal_type(f_and_t) # N: Revealed type is "builtins.bool" +reveal_type(f_or_t) # N: Revealed type is "Literal['s']" +reveal_type(t_and_f) # N: Revealed type is "builtins.bool" +reveal_type(t_or_f) # N: Revealed type is "Literal['s']" +reveal_type(f_and_f) # N: Revealed type is "builtins.bool" +reveal_type(f_or_f) # N: Revealed type is "builtins.bool" +[builtins fixtures/ops.pyi] + +[case testConditionalValuesNegation] +# flags: --platform linux +import sys + +not_t = not sys.platform == 'linux' and 's' +not_f = not sys.platform == 'windows' and 's' +not_and_t = not (sys.platform == 'linux' and sys.platform == 'linux') and 's' +not_and_f = not (sys.platform == 'linux' and sys.platform == 'windows') and 's' +not_or_t = not (sys.platform == 'linux' or sys.platform == 'linux') and 's' +not_or_f = not (sys.platform == 'windows' or sys.platform == 'windows') and 's' +reveal_type(not_t) # N: Revealed type is "builtins.bool" +reveal_type(not_f) # N: Revealed type is "Literal['s']" +reveal_type(not_and_t) # N: Revealed type is "builtins.bool" +reveal_type(not_and_f) # N: Revealed type is "Literal['s']" +reveal_type(not_or_t) # N: Revealed type is "builtins.bool" +reveal_type(not_or_f) # N: Revealed type is "Literal['s']" +[builtins fixtures/ops.pyi] + +[case testConditionalValuesUnsupportedOps] +# flags: --platform linux +import sys + +unary_minus = -(sys.platform == 'linux') and 's' +binary_minus = ((sys.platform == 'linux') - (sys.platform == 'linux')) and 's' +reveal_type(unary_minus) # N: Revealed type is "Union[Literal[0], builtins.str]" +reveal_type(binary_minus) # N: Revealed type is "Union[Literal[0], builtins.str]" +[builtins fixtures/ops.pyi] + +[case testMypyFalseValuesInBinaryOps_no_empty] +# flags: --platform linux +import sys +from typing import TYPE_CHECKING + +MYPY = 0 + +if TYPE_CHECKING and sys.platform == 'linux': + def foo1() -> int: ... +if sys.platform == 'linux' and TYPE_CHECKING: + def foo2() -> int: ... +if MYPY and sys.platform == 'linux': + def foo3() -> int: ... +if sys.platform == 'linux' and MYPY: + def foo4() -> int: ... + +if TYPE_CHECKING or sys.platform == 'linux': + def bar1() -> int: ... # E: Missing return statement +if sys.platform == 'linux' or TYPE_CHECKING: + def bar2() -> int: ... # E: Missing return statement +if MYPY or sys.platform == 'linux': + def bar3() -> int: ... # E: Missing return statement +if sys.platform == 'linux' or MYPY: + def bar4() -> int: ... # E: Missing return statement +[builtins fixtures/ops.pyi] + [case testShortCircuitAndWithConditionalAssignment] # flags: --platform linux import sys