Skip to content

Commit 790ab35

Browse files
authored
check_boolean_op: clean up semanal/type-based reachability (#10566)
1 parent 9d92fba commit 790ab35

File tree

3 files changed

+52
-28
lines changed

3 files changed

+52
-28
lines changed

mypy/checkexpr.py

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2776,38 +2776,34 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type:
27762776

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

2779-
if e.op == 'and':
2779+
left_map: mypy.checker.TypeMap
2780+
right_map: mypy.checker.TypeMap
2781+
if e.right_always:
2782+
left_map, right_map = None, {}
2783+
elif e.right_unreachable:
2784+
left_map, right_map = {}, None
2785+
elif e.op == 'and':
27802786
right_map, left_map = self.chk.find_isinstance_check(e.left)
2781-
restricted_left_type = false_only(left_type)
2782-
result_is_left = not left_type.can_be_true
27832787
elif e.op == 'or':
27842788
left_map, right_map = self.chk.find_isinstance_check(e.left)
2785-
restricted_left_type = true_only(left_type)
2786-
result_is_left = not left_type.can_be_false
27872789

27882790
# If left_map is None then we know mypy considers the left expression
27892791
# to be redundant.
2790-
#
2791-
# Note that we perform these checks *before* we take into account
2792-
# the analysis from the semanal phase below. We assume that nodes
2793-
# marked as unreachable during semantic analysis were done so intentionally.
2794-
# So, we shouldn't report an error.
2795-
if codes.REDUNDANT_EXPR in self.chk.options.enabled_error_codes:
2796-
if left_map is None:
2797-
self.msg.redundant_left_operand(e.op, e.left)
2798-
2799-
# Note that we perform these checks *before* we take into account
2800-
# the analysis from the semanal phase below. We assume that nodes
2801-
# marked as unreachable during semantic analysis were done so intentionally.
2802-
# So, we shouldn't report an error.
2803-
if self.chk.should_report_unreachable_issues():
2804-
if right_map is None:
2805-
self.msg.unreachable_right_operand(e.op, e.right)
2806-
2807-
if e.right_unreachable:
2808-
right_map = None
2809-
elif e.right_always:
2810-
left_map = None
2792+
if (
2793+
codes.REDUNDANT_EXPR in self.chk.options.enabled_error_codes
2794+
and left_map is None
2795+
# don't report an error if it's intentional
2796+
and not e.right_always
2797+
):
2798+
self.msg.redundant_left_operand(e.op, e.left)
2799+
2800+
if (
2801+
self.chk.should_report_unreachable_issues()
2802+
and right_map is None
2803+
# don't report an error if it's intentional
2804+
and not e.right_unreachable
2805+
):
2806+
self.msg.unreachable_right_operand(e.op, e.right)
28112807

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

2823+
if e.op == 'and':
2824+
restricted_left_type = false_only(left_type)
2825+
result_is_left = not left_type.can_be_true
2826+
elif e.op == 'or':
2827+
restricted_left_type = true_only(left_type)
2828+
result_is_left = not left_type.can_be_false
2829+
28272830
if isinstance(restricted_left_type, UninhabitedType):
28282831
# The left operand can never be the result
28292832
return right_type

mypy/nodes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1737,9 +1737,9 @@ class OpExpr(Expression):
17371737
right = None # type: Expression
17381738
# Inferred type for the operator method type (when relevant).
17391739
method_type = None # type: Optional[mypy.types.Type]
1740-
# Is the right side going to be evaluated every time?
1740+
# Per static analysis only: Is the right side going to be evaluated every time?
17411741
right_always = False
1742-
# Is the right side unreachable?
1742+
# Per static analysis only: Is the right side unreachable?
17431743
right_unreachable = False
17441744

17451745
def __init__(self, op: str, left: Expression, right: Expression) -> None:

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,27 @@ if MYPY or mypy_only:
615615
pass
616616
[builtins fixtures/ops.pyi]
617617

618+
[case testSemanticAnalysisFalseButTypeNarrowingTrue]
619+
# flags: --always-false COMPILE_TIME_FALSE
620+
from typing import Literal
621+
622+
indeterminate: bool
623+
COMPILE_TIME_FALSE: Literal[True] # type-narrowing: mapped in 'if' only
624+
a = COMPILE_TIME_FALSE or indeterminate
625+
reveal_type(a) # N: Revealed type is "builtins.bool"
626+
b = indeterminate or COMPILE_TIME_FALSE
627+
reveal_type(b) # N: Revealed type is "Union[builtins.bool, Literal[True]]"
628+
[typing fixtures/typing-medium.pyi]
629+
630+
[case testSemanticAnalysisTrueButTypeNarrowingFalse]
631+
# flags: --always-true COMPILE_TIME_TRUE
632+
indeterminate: bool
633+
COMPILE_TIME_TRUE: None # type narrowed to `else` only
634+
a = COMPILE_TIME_TRUE or indeterminate
635+
reveal_type(a) # N: Revealed type is "None"
636+
b = indeterminate or COMPILE_TIME_TRUE
637+
reveal_type(b) # N: Revealed type is "Union[builtins.bool, None]"
638+
618639
[case testConditionalAssertWithoutElse]
619640
import typing
620641

0 commit comments

Comments
 (0)