Skip to content

Commit

Permalink
check_boolean_op: clean up semanal/type-based reachability (#10566)
Browse files Browse the repository at this point in the history
  • Loading branch information
ikonst authored Jun 16, 2021
1 parent 9d92fba commit 790ab35
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 28 deletions.
55 changes: 29 additions & 26 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2776,38 +2776,34 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type:

assert e.op in ('and', 'or') # Checked by visit_op_expr

if e.op == 'and':
left_map: mypy.checker.TypeMap
right_map: mypy.checker.TypeMap
if e.right_always:
left_map, right_map = None, {}
elif e.right_unreachable:
left_map, right_map = {}, None
elif e.op == 'and':
right_map, left_map = self.chk.find_isinstance_check(e.left)
restricted_left_type = false_only(left_type)
result_is_left = not left_type.can_be_true
elif e.op == 'or':
left_map, right_map = self.chk.find_isinstance_check(e.left)
restricted_left_type = true_only(left_type)
result_is_left = not left_type.can_be_false

# If left_map is None then we know mypy considers the left expression
# to be redundant.
#
# Note that we perform these checks *before* we take into account
# the analysis from the semanal phase below. We assume that nodes
# marked as unreachable during semantic analysis were done so intentionally.
# So, we shouldn't report an error.
if codes.REDUNDANT_EXPR in self.chk.options.enabled_error_codes:
if left_map is None:
self.msg.redundant_left_operand(e.op, e.left)

# Note that we perform these checks *before* we take into account
# the analysis from the semanal phase below. We assume that nodes
# marked as unreachable during semantic analysis were done so intentionally.
# So, we shouldn't report an error.
if self.chk.should_report_unreachable_issues():
if right_map is None:
self.msg.unreachable_right_operand(e.op, e.right)

if e.right_unreachable:
right_map = None
elif e.right_always:
left_map = None
if (
codes.REDUNDANT_EXPR in self.chk.options.enabled_error_codes
and left_map is None
# don't report an error if it's intentional
and not e.right_always
):
self.msg.redundant_left_operand(e.op, e.left)

if (
self.chk.should_report_unreachable_issues()
and right_map is None
# don't report an error if it's intentional
and not e.right_unreachable
):
self.msg.unreachable_right_operand(e.op, e.right)

# If right_map is None then we know mypy considers the right branch
# to be unreachable and therefore any errors found in the right branch
Expand All @@ -2824,6 +2820,13 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type:
assert right_map is not None # find_isinstance_check guarantees this
return right_type

if e.op == 'and':
restricted_left_type = false_only(left_type)
result_is_left = not left_type.can_be_true
elif e.op == 'or':
restricted_left_type = true_only(left_type)
result_is_left = not left_type.can_be_false

if isinstance(restricted_left_type, UninhabitedType):
# The left operand can never be the result
return right_type
Expand Down
4 changes: 2 additions & 2 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1737,9 +1737,9 @@ class OpExpr(Expression):
right = None # type: Expression
# Inferred type for the operator method type (when relevant).
method_type = None # type: Optional[mypy.types.Type]
# Is the right side going to be evaluated every time?
# Per static analysis only: Is the right side going to be evaluated every time?
right_always = False
# Is the right side unreachable?
# Per static analysis only: Is the right side unreachable?
right_unreachable = False

def __init__(self, op: str, left: Expression, right: Expression) -> None:
Expand Down
21 changes: 21 additions & 0 deletions test-data/unit/check-unreachable-code.test
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,27 @@ if MYPY or mypy_only:
pass
[builtins fixtures/ops.pyi]

[case testSemanticAnalysisFalseButTypeNarrowingTrue]
# flags: --always-false COMPILE_TIME_FALSE
from typing import Literal

indeterminate: bool
COMPILE_TIME_FALSE: Literal[True] # type-narrowing: mapped in 'if' only
a = COMPILE_TIME_FALSE or indeterminate
reveal_type(a) # N: Revealed type is "builtins.bool"
b = indeterminate or COMPILE_TIME_FALSE
reveal_type(b) # N: Revealed type is "Union[builtins.bool, Literal[True]]"
[typing fixtures/typing-medium.pyi]

[case testSemanticAnalysisTrueButTypeNarrowingFalse]
# flags: --always-true COMPILE_TIME_TRUE
indeterminate: bool
COMPILE_TIME_TRUE: None # type narrowed to `else` only
a = COMPILE_TIME_TRUE or indeterminate
reveal_type(a) # N: Revealed type is "None"
b = indeterminate or COMPILE_TIME_TRUE
reveal_type(b) # N: Revealed type is "Union[builtins.bool, None]"

[case testConditionalAssertWithoutElse]
import typing

Expand Down

0 comments on commit 790ab35

Please sign in to comment.