Skip to content

Commit 00f66c9

Browse files
committed
Distinguish redundant-expr warnings from unreachable warnings
1 parent b1f5121 commit 00f66c9

File tree

8 files changed

+46
-16
lines changed

8 files changed

+46
-16
lines changed

mypy/checkexpr.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2740,6 +2740,17 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type:
27402740
restricted_left_type = true_only(left_type)
27412741
result_is_left = not left_type.can_be_false
27422742

2743+
# If left_map is None then we know mypy considers the left expression
2744+
# to be reundant.
2745+
#
2746+
# Note that we perform these checks *before* we take into account
2747+
# the analysis from the semanal phase below. We assume that nodes
2748+
# marked as unreachable during semantic analysis were done so intentionally.
2749+
# So, we shouldn't report an error.
2750+
if self.chk.options.warn_redundant_expr:
2751+
if left_map is None:
2752+
self.msg.redundant_left_operand(e.op, e.left)
2753+
27432754
# If right_map is None then we know mypy considers the right branch
27442755
# to be unreachable and therefore any errors found in the right branch
27452756
# should be suppressed.
@@ -2749,10 +2760,8 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type:
27492760
# marked as unreachable during semantic analysis were done so intentionally.
27502761
# So, we shouldn't report an error.
27512762
if self.chk.options.warn_unreachable:
2752-
if left_map is None:
2753-
self.msg.redundant_left_operand(e.op, e.left)
27542763
if right_map is None:
2755-
self.msg.redundant_right_operand(e.op, e.right)
2764+
self.msg.unreachable_right_operand(e.op, e.right)
27562765

27572766
if e.right_unreachable:
27582767
right_map = None
@@ -3669,7 +3678,7 @@ def check_for_comp(self, e: Union[GeneratorExpr, DictionaryComprehension]) -> No
36693678
for var, type in true_map.items():
36703679
self.chk.binder.put(var, type)
36713680

3672-
if self.chk.options.warn_unreachable:
3681+
if self.chk.options.warn_redundant_expr:
36733682
if true_map is None:
36743683
self.msg.redundant_condition_in_comprehension(False, condition)
36753684
elif false_map is None:
@@ -3682,7 +3691,7 @@ def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = F
36823691
# Gain type information from isinstance if it is there
36833692
# but only for the current expression
36843693
if_map, else_map = self.chk.find_isinstance_check(e.cond)
3685-
if self.chk.options.warn_unreachable:
3694+
if self.chk.options.warn_redundant_expr:
36863695
if if_map is None:
36873696
self.msg.redundant_condition_in_if(False, e.cond)
36883697
elif else_map is None:

mypy/errorcodes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ def __str__(self) -> str:
105105
'General') # type: Final
106106
UNREACHABLE = ErrorCode(
107107
'unreachable', "Warn about unreachable statements or expressions", 'General') # type: Final
108+
REDUNDANT_EXPR = ErrorCode(
109+
'redundant-expr', "Warn about redundant expressions", 'General') # type: Final
108110

109111
# Syntax errors are often blocking.
110112
SYNTAX = ErrorCode(

mypy/main.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,10 @@ def add_invertible_flag(flag: str,
575575
group=lint_group)
576576
add_invertible_flag('--warn-unreachable', default=False, strict_flag=False,
577577
help="Warn about statements or expressions inferred to be"
578-
" unreachable or redundant",
578+
" unreachable",
579+
group=lint_group)
580+
add_invertible_flag('--warn-redundant-expr', default=False, strict_flag=False,
581+
help="Warn about expressions inferred to be redundant",
579582
group=lint_group)
580583

581584
# Note: this group is intentionally added here even though we don't add

mypy/messages.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,7 +1280,7 @@ def redundant_left_operand(self, op_name: str, context: Context) -> None:
12801280
"""
12811281
self.redundant_expr("Left operand of '{}'".format(op_name), op_name == 'and', context)
12821282

1283-
def redundant_right_operand(self, op_name: str, context: Context) -> None:
1283+
def unreachable_right_operand(self, op_name: str, context: Context) -> None:
12841284
"""Indicates that the right operand of a boolean expression is redundant:
12851285
it does not change the truth value of the entire condition as a whole.
12861286
'op_name' should either be the string "and" or the string "or".
@@ -1299,7 +1299,7 @@ def redundant_condition_in_assert(self, truthiness: bool, context: Context) -> N
12991299

13001300
def redundant_expr(self, description: str, truthiness: bool, context: Context) -> None:
13011301
self.fail("{} is always {}".format(description, str(truthiness).lower()),
1302-
context, code=codes.UNREACHABLE)
1302+
context, code=codes.REDUNDANT_EXPR)
13031303

13041304
def impossible_intersection(self,
13051305
formatted_base_class_list: str,

mypy/options.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class BuildType:
4848
"strict_optional_whitelist",
4949
"warn_no_return",
5050
"warn_return_any",
51+
"warn_redundant_expr",
5152
"warn_unreachable",
5253
"warn_unused_ignores",
5354
} # type: Final
@@ -171,6 +172,10 @@ def __init__(self) -> None:
171172
# type analysis.
172173
self.warn_unreachable = False
173174

175+
# Report an error for any expressions inferred to be redundant as a result of
176+
# type analysis.
177+
self.warn_redundant_expr = False
178+
174179
# Variable names considered True
175180
self.always_true = [] # type: List[str]
176181

mypy/test/testcheck.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
'check-typevar-values.test',
4343
'check-unsupported.test',
4444
'check-unreachable-code.test',
45+
'check-redundant-expr.test',
4546
'check-unions.test',
4647
'check-isinstance.test',
4748
'check-lists.test',
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-- Type checker test cases for conditional checks that result in some
2+
-- expressions classified as redundant.
3+
4+
[case testRedundantExpressions]
5+
# flags: --warn-redundant-expr
6+
def foo() -> bool: ...
7+
8+
lst = [1, 2, 3, 4]
9+
10+
b = False or foo() # E: Left operand of 'or' is always false
11+
c = True and foo() # E: Left operand of 'and' is always true
12+
g = 3 if True else 4 # E: If condition is always true
13+
h = 3 if False else 4 # E: If condition is always false
14+
i = [x for x in lst if True] # E: If condition in comprehension is always true
15+
j = [x for x in lst if False] # E: If condition in comprehension is always false
16+
k = [x for x in lst if isinstance(x, int) or foo()] # E: If condition in comprehension is always true
17+
[builtins fixtures/isinstancelist.pyi]

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -898,18 +898,11 @@ def foo() -> bool: ...
898898
lst = [1, 2, 3, 4]
899899

900900
a = True or foo() # E: Right operand of 'or' is never evaluated
901-
b = False or foo() # E: Left operand of 'or' is always false
902-
c = True and foo() # E: Left operand of 'and' is always true
903901
d = False and foo() # E: Right operand of 'and' is never evaluated
904902
e = True or (True or (True or foo())) # E: Right operand of 'or' is never evaluated
905903
f = (True or foo()) or (True or foo()) # E: Right operand of 'or' is never evaluated
906-
g = 3 if True else 4 # E: If condition is always true
907-
h = 3 if False else 4 # E: If condition is always false
908-
i = [x for x in lst if True] # E: If condition in comprehension is always true
909-
j = [x for x in lst if False] # E: If condition in comprehension is always false
910904

911-
k = [x for x in lst if isinstance(x, int) or foo()] # E: If condition in comprehension is always true \
912-
# E: Right operand of 'or' is never evaluated
905+
k = [x for x in lst if isinstance(x, int) or foo()] # E: Right operand of 'or' is never evaluated
913906
[builtins fixtures/isinstancelist.pyi]
914907

915908
[case testUnreachableFlagMiscTestCaseMissingMethod]

0 commit comments

Comments
 (0)