Skip to content

Make infer_condition_value recognize the whole truth table #18944

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 29 additions & 18 deletions mypy/reachability.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <other>` or `False or <other>`: 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:
Expand All @@ -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


Expand Down
76 changes: 76 additions & 0 deletions test-data/unit/check-unreachable-code.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down