Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 36 additions & 10 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,18 +604,31 @@ def check_first_pass(self, recurse_into_functions: bool = True) -> None:
with self.tscope.module_scope(self.tree.fullname):
with self.enter_partial_types(), self.binder.top_frame_context():
marked_unreachable = False
reported_unreachable = False
for d in self.tree.defs:
if self.binder.is_unreachable():
finish = False
if not marked_unreachable:
self.mark_unreachable(self.tree.defs, after=d)
marked_unreachable = True
if not self.should_report_unreachable_issues():
break
if not self.is_noop_for_reachability(d):
finish = True
if (
not finish
and not reported_unreachable
and not self.is_noop_for_reachability(d)
):
self.msg.unreachable_statement(d)
finish = True
reported_unreachable = True

if finish and not self.options.check_unreachable:
break
else:
self.accept(d)

if not self.options.check_unreachable:
continue

self.accept(d)

assert not self.current_node_deferred

Expand Down Expand Up @@ -3303,20 +3316,33 @@ def visit_block(self, b: Block) -> None:
self.binder.unreachable()
return
marked_unreachable = False
reported_unreachable = False
for s in b.body:
if self.binder.is_unreachable():
finish = False
if self.scope.top_level_function() is None and not marked_unreachable:
self.mark_unreachable(b.body, after=s)
marked_unreachable = True
if not self.should_report_unreachable_issues():
break
if not self.is_noop_for_reachability(s):
finish = True
if (
not finish
and not reported_unreachable
and not self.is_noop_for_reachability(s)
):
self.msg.unreachable_statement(s)
finish = True
reported_unreachable = True

if finish and not self.options.check_unreachable:
break
else:
self.accept(s)
# Clear expression cache after each statement to avoid unlimited growth.
self.expr_checker.expr_cache.clear()

if not self.options.check_unreachable:
continue

self.accept(s)
# Clear expression cache after each statement to avoid unlimited growth.
self.expr_checker.expr_cache.clear()

def should_report_unreachable_issues(self) -> bool:
return (
Expand Down
7 changes: 7 additions & 0 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,13 @@ def add_invertible_flag(
group=strictness_group,
)

add_invertible_flag(
"--check-unreachable",
default=False,
help=argparse.SUPPRESS, # "Type check unreachable code",
group=strictness_group,
)

strict_help = "Strict mode; enables the following flags: {}".format(
", ".join(strict_flag_names)
)
Expand Down
4 changes: 4 additions & 0 deletions mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class BuildType:
"allow_untyped_globals",
"always_false",
"always_true",
"check_unreachable",
"check_untyped_defs",
"debug_cache",
"disable_error_code",
Expand Down Expand Up @@ -173,6 +174,9 @@ def __init__(self) -> None:
# Disallow defining incompletely typed functions
self.disallow_incomplete_defs = False

# Type check unreachable code
self.check_unreachable = False

# Type check unannotated functions
self.check_untyped_defs = False

Expand Down
7 changes: 7 additions & 0 deletions test-data/unit/check-unreachable-code.test
Original file line number Diff line number Diff line change
Expand Up @@ -1744,3 +1744,10 @@ assert sys.platform == "win32"

42 + "no way" # type: ignore[operator]
[builtins fixtures/isinstancelist.pyi]


[case testCheckUnreachableBasics]
# flags: --check-unreachable --warn-unreachable
if False:
reveal_type(5) # E: Statement is unreachable \
# N: Revealed type is "Literal[5]?"
Loading