Skip to content

Commit 15f5740

Browse files
committed
Add the test case testPersistentUnreachableLinesNestedInInpersistentUnreachableLines provided by @A5rocks and extend the current logic to fix it.
1 parent 01b6140 commit 15f5740

File tree

3 files changed

+52
-18
lines changed

3 files changed

+52
-18
lines changed

mypy/checker.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -600,22 +600,25 @@ def accept_loop(
600600
# on without bound otherwise)
601601
widened_old = len(self.widened_vars)
602602

603+
# one set of `unreachable` and `redundant-expr`errors per iteration step:
604+
uselessness_errors = []
605+
# one set of unreachable line numbers per iteration step:
606+
unreachable_lines = []
607+
# one set of revealed types per line where `reveal_type` is used (each
608+
# created set can grow during the iteration):
609+
revealed_types = defaultdict(set)
603610
iter = 1
604611
while True:
605612
with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1):
606613
if on_enter_body is not None:
607614
on_enter_body()
608615

609-
# We collect errors that indicate unreachability or redundant expressions
610-
# in the first iteration and remove them in subsequent iterations if the
611-
# related statement becomes reachable or non-redundant due to changes in
612-
# partial types:
613616
with LoopErrorWatcher(self.msg.errors) as watcher:
614617
self.accept(body)
615-
if iter == 1:
616-
uselessness_errors = watcher.useless_statements
617-
else:
618-
uselessness_errors.intersection_update(watcher.useless_statements)
618+
uselessness_errors.append(watcher.uselessness_errors)
619+
unreachable_lines.append(watcher.unreachable_lines)
620+
for key, values in watcher.revealed_types.items():
621+
revealed_types[key].update(values)
619622

620623
partials_new = sum(len(pts.map) for pts in self.partial_types)
621624
widened_new = len(self.widened_vars)
@@ -637,15 +640,23 @@ def accept_loop(
637640
if iter == 20:
638641
raise RuntimeError("Too many iterations when checking a loop")
639642

640-
# Report those unreachable and redundant expression errors identified in all
641-
# iteration steps:
642-
for error_info in uselessness_errors:
643+
# Report only those `unreachable` and `redundant-expr` errors that could not
644+
# be ruled out in any iteration step:
645+
persistent_uselessness_errors = set()
646+
for candidate in set(itertools.chain(*uselessness_errors)):
647+
if all(
648+
(candidate in errors) or (candidate[2] in lines)
649+
for errors, lines in zip(uselessness_errors, unreachable_lines)
650+
):
651+
persistent_uselessness_errors.add(candidate)
652+
for error_info in persistent_uselessness_errors:
643653
context = Context(line=error_info[2], column=error_info[3])
644654
context.end_line = error_info[4]
645655
context.end_column = error_info[5]
646656
self.msg.fail(error_info[1], context, code=error_info[0])
657+
647658
# Report all types revealed in at least one iteration step:
648-
for note_info, types in watcher.revealed_types.items():
659+
for note_info, types in revealed_types.items():
649660
sorted_ = sorted(types, key=lambda typ: typ.lower())
650661
revealed = sorted_[0] if len(types) == 1 else f"Union[{', '.join(sorted_)}]"
651662
context = Context(line=note_info[1], column=note_info[2])

mypy/errors.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,15 +221,19 @@ def filtered_errors(self) -> list[ErrorInfo]:
221221

222222

223223
class LoopErrorWatcher(ErrorWatcher):
224-
"""Error watcher that filters and separately collects unreachable errors, redundant
225-
expression errors, and revealed types when analysing loops iteratively to help avoid
226-
making too-hasty reports."""
224+
"""Error watcher that filters and separately collects `unreachable` errors,
225+
`redundant-expr` errors, and revealed types when analysing loops iteratively
226+
to help avoid making too-hasty reports."""
227227

228228
# Meaning of the tuple items: ErrorCode, message, line, column, end_line, end_column:
229-
useless_statements: set[tuple[ErrorCode, str, int, int, int, int]]
229+
uselessness_errors: set[tuple[ErrorCode, str, int, int, int, int]]
230+
230231
# Meaning of the tuple items: function_or_member, line, column, end_line, end_column:
231232
revealed_types: dict[tuple[str | None, int, int, int, int], set[str]]
232233

234+
# Not only the lines where the error report occurs but really all unreachable lines:
235+
unreachable_lines: set[int]
236+
233237
def __init__(
234238
self,
235239
errors: Errors,
@@ -244,15 +248,20 @@ def __init__(
244248
save_filtered_errors=save_filtered_errors,
245249
filter_deprecated=filter_deprecated,
246250
)
247-
self.useless_statements = set()
251+
self.uselessness_errors = set()
252+
self.unreachable_lines = set()
248253
self.revealed_types = defaultdict(set)
249254

250255
def on_error(self, file: str, info: ErrorInfo) -> bool:
256+
251257
if info.code in (codes.UNREACHABLE, codes.REDUNDANT_EXPR):
252-
self.useless_statements.add(
258+
self.uselessness_errors.add(
253259
(info.code, info.message, info.line, info.column, info.end_line, info.end_column)
254260
)
261+
if info.code == codes.UNREACHABLE:
262+
self.unreachable_lines.update(range(info.line, info.end_line + 1))
255263
return True
264+
256265
if info.code == codes.MISC and info.message.startswith("Revealed type is "):
257266
key = info.function_or_member, info.line, info.column, info.end_line, info.end_column
258267
types = info.message.split('"')[1]
@@ -261,6 +270,7 @@ def on_error(self, file: str, info: ErrorInfo) -> bool:
261270
else:
262271
self.revealed_types[key].add(types)
263272
return True
273+
264274
return super().on_error(file, info)
265275

266276

test-data/unit/check-narrowing.test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2369,6 +2369,19 @@ class A:
23692369

23702370
[builtins fixtures/primitives.pyi]
23712371

2372+
[case testPersistentUnreachableLinesNestedInInpersistentUnreachableLines]
2373+
# flags: --warn-unreachable --python-version 3.11
2374+
2375+
x = None
2376+
y = None
2377+
while True:
2378+
if x is not None:
2379+
if y is not None:
2380+
reveal_type(y) # E: Statement is unreachable
2381+
x = 1
2382+
2383+
[builtins fixtures/bool.pyi]
2384+
23722385
[case testAvoidFalseUnreachableInLoop1]
23732386
# flags: --warn-unreachable --python-version 3.11
23742387

0 commit comments

Comments
 (0)