Skip to content

Commit 6eb141f

Browse files
authored
Add a secret --check-unreachable (#21834)
Replaces #18707, references #21835. This is very unbaked, but I figure it's best to get the smallest bit in. I'll see if I can figure out stacked PRs to add extra stuff to this (namely decreasing the number of false errors in unreachable code!), but this is probably super easy to review and a good start!
1 parent ed90eaf commit 6eb141f

4 files changed

Lines changed: 54 additions & 10 deletions

File tree

mypy/checker.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -604,18 +604,31 @@ def check_first_pass(self, recurse_into_functions: bool = True) -> None:
604604
with self.tscope.module_scope(self.tree.fullname):
605605
with self.enter_partial_types(), self.binder.top_frame_context():
606606
marked_unreachable = False
607+
reported_unreachable = False
607608
for d in self.tree.defs:
608609
if self.binder.is_unreachable():
610+
finish = False
609611
if not marked_unreachable:
610612
self.mark_unreachable(self.tree.defs, after=d)
611613
marked_unreachable = True
612614
if not self.should_report_unreachable_issues():
613-
break
614-
if not self.is_noop_for_reachability(d):
615+
finish = True
616+
if (
617+
not finish
618+
and not reported_unreachable
619+
and not self.is_noop_for_reachability(d)
620+
):
615621
self.msg.unreachable_statement(d)
622+
finish = True
623+
reported_unreachable = True
624+
625+
if finish and not self.options.check_unreachable:
616626
break
617-
else:
618-
self.accept(d)
627+
628+
if not self.options.check_unreachable:
629+
continue
630+
631+
self.accept(d)
619632

620633
assert not self.current_node_deferred
621634

@@ -3303,20 +3316,33 @@ def visit_block(self, b: Block) -> None:
33033316
self.binder.unreachable()
33043317
return
33053318
marked_unreachable = False
3319+
reported_unreachable = False
33063320
for s in b.body:
33073321
if self.binder.is_unreachable():
3322+
finish = False
33083323
if self.scope.top_level_function() is None and not marked_unreachable:
33093324
self.mark_unreachable(b.body, after=s)
33103325
marked_unreachable = True
33113326
if not self.should_report_unreachable_issues():
3312-
break
3313-
if not self.is_noop_for_reachability(s):
3327+
finish = True
3328+
if (
3329+
not finish
3330+
and not reported_unreachable
3331+
and not self.is_noop_for_reachability(s)
3332+
):
33143333
self.msg.unreachable_statement(s)
3334+
finish = True
3335+
reported_unreachable = True
3336+
3337+
if finish and not self.options.check_unreachable:
33153338
break
3316-
else:
3317-
self.accept(s)
3318-
# Clear expression cache after each statement to avoid unlimited growth.
3319-
self.expr_checker.expr_cache.clear()
3339+
3340+
if not self.options.check_unreachable:
3341+
continue
3342+
3343+
self.accept(s)
3344+
# Clear expression cache after each statement to avoid unlimited growth.
3345+
self.expr_checker.expr_cache.clear()
33203346

33213347
def should_report_unreachable_issues(self) -> bool:
33223348
return (

mypy/main.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,13 @@ def add_invertible_flag(
965965
group=strictness_group,
966966
)
967967

968+
add_invertible_flag(
969+
"--check-unreachable",
970+
default=False,
971+
help=argparse.SUPPRESS, # "Type check unreachable code",
972+
group=strictness_group,
973+
)
974+
968975
strict_help = "Strict mode; enables the following flags: {}".format(
969976
", ".join(strict_flag_names)
970977
)

mypy/options.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class BuildType:
2929
"allow_untyped_globals",
3030
"always_false",
3131
"always_true",
32+
"check_unreachable",
3233
"check_untyped_defs",
3334
"debug_cache",
3435
"disable_error_code",
@@ -173,6 +174,9 @@ def __init__(self) -> None:
173174
# Disallow defining incompletely typed functions
174175
self.disallow_incomplete_defs = False
175176

177+
# Type check unreachable code
178+
self.check_unreachable = False
179+
176180
# Type check unannotated functions
177181
self.check_untyped_defs = False
178182

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1744,3 +1744,10 @@ assert sys.platform == "win32"
17441744

17451745
42 + "no way" # type: ignore[operator]
17461746
[builtins fixtures/isinstancelist.pyi]
1747+
1748+
1749+
[case testCheckUnreachableBasics]
1750+
# flags: --check-unreachable --warn-unreachable
1751+
if False:
1752+
reveal_type(5) # E: Statement is unreachable \
1753+
# N: Revealed type is "Literal[5]?"

0 commit comments

Comments
 (0)