Skip to content

Commit 3bf8521

Browse files
authored
Consistently avoid type-checking unreachable code (#15386)
- On module-level, now we'll skip remaining statements once unreachable. This brings the behavior in line with function-level behavior. - For module and function code, if `--warn-unreachable` is enabled, we'll emit an error, just once, on the first unreachable statement that's not a no-op statement. Previously a no-op statement would not have the "Unreachable statement" error, but the subsequent statements did not have the error either, e.g. ```diff raise Exception assert False # no error since it's a "no-op statement" -foo = 42 +foo = 42 # E: Unreachable statement spam = "ham" # no error since we warn just once ```
1 parent dfea43f commit 3bf8521

11 files changed

+120
-80
lines changed

mypy/checker.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -464,14 +464,14 @@ def check_first_pass(self) -> None:
464464
with self.tscope.module_scope(self.tree.fullname):
465465
with self.enter_partial_types(), self.binder.top_frame_context():
466466
for d in self.tree.defs:
467-
if (
468-
self.binder.is_unreachable()
469-
and self.should_report_unreachable_issues()
470-
and not self.is_raising_or_empty(d)
471-
):
472-
self.msg.unreachable_statement(d)
473-
break
474-
self.accept(d)
467+
if self.binder.is_unreachable():
468+
if not self.should_report_unreachable_issues():
469+
break
470+
if not self.is_noop_for_reachability(d):
471+
self.msg.unreachable_statement(d)
472+
break
473+
else:
474+
self.accept(d)
475475

476476
assert not self.current_node_deferred
477477

@@ -2706,10 +2706,13 @@ def visit_block(self, b: Block) -> None:
27062706
return
27072707
for s in b.body:
27082708
if self.binder.is_unreachable():
2709-
if self.should_report_unreachable_issues() and not self.is_raising_or_empty(s):
2709+
if not self.should_report_unreachable_issues():
2710+
break
2711+
if not self.is_noop_for_reachability(s):
27102712
self.msg.unreachable_statement(s)
2711-
break
2712-
self.accept(s)
2713+
break
2714+
else:
2715+
self.accept(s)
27132716

27142717
def should_report_unreachable_issues(self) -> bool:
27152718
return (
@@ -2719,11 +2722,11 @@ def should_report_unreachable_issues(self) -> bool:
27192722
and not self.binder.is_unreachable_warning_suppressed()
27202723
)
27212724

2722-
def is_raising_or_empty(self, s: Statement) -> bool:
2725+
def is_noop_for_reachability(self, s: Statement) -> bool:
27232726
"""Returns 'true' if the given statement either throws an error of some kind
27242727
or is a no-op.
27252728
2726-
We use this function mostly while handling the '--warn-unreachable' flag. When
2729+
We use this function while handling the '--warn-unreachable' flag. When
27272730
that flag is present, we normally report an error on any unreachable statement.
27282731
But if that statement is just something like a 'pass' or a just-in-case 'assert False',
27292732
reporting an error would be annoying.

mypyc/test-data/run-misc.test

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,25 +1108,14 @@ assert not C
11081108
# make the initial import fail
11091109
assert False
11101110

1111-
class C:
1112-
def __init__(self):
1113-
self.x = 1
1114-
self.y = 2
1115-
def test() -> None:
1116-
a = C()
11171111
[file driver.py]
11181112
# load native, cause PyInit to be run, create the module but don't finish initializing the globals
1119-
try:
1120-
import native
1121-
except:
1122-
pass
1123-
try:
1124-
# try accessing those globals that were never properly initialized
1125-
import native
1126-
native.test()
1127-
# should fail with AssertionError due to `assert False` in other function
1128-
except AssertionError:
1129-
pass
1113+
for _ in range(2):
1114+
try:
1115+
import native
1116+
raise RuntimeError('exception expected')
1117+
except AssertionError:
1118+
pass
11301119

11311120
[case testRepeatedUnderscoreFunctions]
11321121
def _(arg): pass

test-data/unit/check-classes.test

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7725,10 +7725,14 @@ class D:
77257725
def __new__(cls) -> NoReturn: ...
77267726
def __init__(self) -> NoReturn: ...
77277727

7728-
reveal_type(A()) # N: Revealed type is "<nothing>"
7729-
reveal_type(B()) # N: Revealed type is "<nothing>"
7730-
reveal_type(C()) # N: Revealed type is "<nothing>"
7731-
reveal_type(D()) # N: Revealed type is "<nothing>"
7728+
if object():
7729+
reveal_type(A()) # N: Revealed type is "<nothing>"
7730+
if object():
7731+
reveal_type(B()) # N: Revealed type is "<nothing>"
7732+
if object():
7733+
reveal_type(C()) # N: Revealed type is "<nothing>"
7734+
if object():
7735+
reveal_type(D()) # N: Revealed type is "<nothing>"
77327736

77337737
[case testOverloadedNewAndInitNoReturn]
77347738
from typing import NoReturn, overload
@@ -7767,13 +7771,20 @@ class D:
77677771
def __init__(self, a: int) -> None: ...
77687772
def __init__(self, a: int = ...) -> None: ...
77697773

7770-
reveal_type(A()) # N: Revealed type is "<nothing>"
7774+
if object():
7775+
reveal_type(A()) # N: Revealed type is "<nothing>"
77717776
reveal_type(A(1)) # N: Revealed type is "__main__.A"
7772-
reveal_type(B()) # N: Revealed type is "<nothing>"
7777+
7778+
if object():
7779+
reveal_type(B()) # N: Revealed type is "<nothing>"
77737780
reveal_type(B(1)) # N: Revealed type is "__main__.B"
7774-
reveal_type(C()) # N: Revealed type is "<nothing>"
7781+
7782+
if object():
7783+
reveal_type(C()) # N: Revealed type is "<nothing>"
77757784
reveal_type(C(1)) # N: Revealed type is "__main__.C"
7776-
reveal_type(D()) # N: Revealed type is "<nothing>"
7785+
7786+
if object():
7787+
reveal_type(D()) # N: Revealed type is "<nothing>"
77777788
reveal_type(D(1)) # N: Revealed type is "__main__.D"
77787789

77797790
[case testClassScopeImportWithWrapperAndError]

test-data/unit/check-fastparse.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,8 @@ def g(): # E: Type signature has too many arguments
228228
assert 1, 2
229229
assert (1, 2) # E: Assertion is always true, perhaps remove parentheses?
230230
assert (1, 2), 3 # E: Assertion is always true, perhaps remove parentheses?
231-
assert ()
232231
assert (1,) # E: Assertion is always true, perhaps remove parentheses?
232+
assert ()
233233
[builtins fixtures/tuple.pyi]
234234

235235
[case testFastParseAssertMessage]

test-data/unit/check-incremental.test

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5413,7 +5413,8 @@ reveal_type(z)
54135413
[out]
54145414
tmp/c.py:2: note: Revealed type is "a.<subclass of "A" and "B">"
54155415
[out2]
5416-
tmp/c.py:2: note: Revealed type is "a.A"
5416+
tmp/b.py:2: error: Cannot determine type of "y"
5417+
tmp/c.py:2: note: Revealed type is "Any"
54175418

54185419
[case testIsInstanceAdHocIntersectionIncrementalUnreachaableToIntersection]
54195420
import c
@@ -5444,7 +5445,8 @@ from b import z
54445445
reveal_type(z)
54455446
[builtins fixtures/isinstance.pyi]
54465447
[out]
5447-
tmp/c.py:2: note: Revealed type is "a.A"
5448+
tmp/b.py:2: error: Cannot determine type of "y"
5449+
tmp/c.py:2: note: Revealed type is "Any"
54485450
[out2]
54495451
tmp/c.py:2: note: Revealed type is "a.<subclass of "A" and "B">"
54505452

test-data/unit/check-inference-context.test

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -625,8 +625,10 @@ reveal_type((lambda x, y: x + y)(1, 2)) # N: Revealed type is "builtins.int"
625625
reveal_type((lambda s, i: s)(i=0, s='x')) # N: Revealed type is "Literal['x']?"
626626
reveal_type((lambda s, i: i)(i=0, s='x')) # N: Revealed type is "Literal[0]?"
627627
reveal_type((lambda x, s, i: x)(1.0, i=0, s='x')) # N: Revealed type is "builtins.float"
628-
(lambda x, s, i: x)() # E: Too few arguments
629-
(lambda: 0)(1) # E: Too many arguments
628+
if object():
629+
(lambda x, s, i: x)() # E: Too few arguments
630+
if object():
631+
(lambda: 0)(1) # E: Too many arguments
630632
-- varargs are not handled, but it should not crash
631633
reveal_type((lambda *k, s, i: i)(type, i=0, s='x')) # N: Revealed type is "Any"
632634
reveal_type((lambda s, *k, i: i)(i=0, s='x')) # N: Revealed type is "Any"

test-data/unit/check-native-int.test

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,10 @@ reveal_type(meet(f32, f)) # N: Revealed type is "mypy_extensions.i32"
8787
reveal_type(meet(f, f32)) # N: Revealed type is "mypy_extensions.i32"
8888
reveal_type(meet(f64, f)) # N: Revealed type is "mypy_extensions.i64"
8989
reveal_type(meet(f, f64)) # N: Revealed type is "mypy_extensions.i64"
90-
reveal_type(meet(f32, f64)) # N: Revealed type is "<nothing>"
91-
reveal_type(meet(f64, f32)) # N: Revealed type is "<nothing>"
90+
if object():
91+
reveal_type(meet(f32, f64)) # N: Revealed type is "<nothing>"
92+
if object():
93+
reveal_type(meet(f64, f32)) # N: Revealed type is "<nothing>"
9294

9395
reveal_type(meet(f, fa)) # N: Revealed type is "builtins.int"
9496
reveal_type(meet(f32, fa)) # N: Revealed type is "mypy_extensions.i32"
@@ -148,8 +150,10 @@ def meet(c1: Callable[[T], None], c2: Callable[[T], None]) -> T:
148150
def ff(x: float) -> None: pass
149151
def fi32(x: i32) -> None: pass
150152

151-
reveal_type(meet(ff, fi32)) # N: Revealed type is "<nothing>"
152-
reveal_type(meet(fi32, ff)) # N: Revealed type is "<nothing>"
153+
if object():
154+
reveal_type(meet(ff, fi32)) # N: Revealed type is "<nothing>"
155+
if object():
156+
reveal_type(meet(fi32, ff)) # N: Revealed type is "<nothing>"
153157
[builtins fixtures/dict.pyi]
154158

155159
[case testNativeIntForLoopRange]

test-data/unit/check-statements.test

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -409,11 +409,16 @@ main:5: error: Exception must be derived from BaseException
409409
class A: pass
410410
class MyError(BaseException): pass
411411
def f(): pass
412-
raise BaseException
413-
raise MyError
414-
raise A # E: Exception must be derived from BaseException
415-
raise object # E: Exception must be derived from BaseException
416-
raise f # E: Exception must be derived from BaseException
412+
if object():
413+
raise BaseException
414+
if object():
415+
raise MyError
416+
if object():
417+
raise A # E: Exception must be derived from BaseException
418+
if object():
419+
raise object # E: Exception must be derived from BaseException
420+
if object():
421+
raise f # E: Exception must be derived from BaseException
417422
[builtins fixtures/exception.pyi]
418423

419424
[case testRaiseClassObjectCustomInit]
@@ -429,18 +434,30 @@ class MyKwError(Exception):
429434
class MyErrorWithDefault(Exception):
430435
def __init__(self, optional=1) -> None:
431436
...
432-
raise BaseException
433-
raise Exception
434-
raise BaseException(1)
435-
raise Exception(2)
436-
raise MyBaseError(4)
437-
raise MyError(5, 6)
438-
raise MyKwError(kwonly=7)
439-
raise MyErrorWithDefault(8)
440-
raise MyErrorWithDefault
441-
raise MyBaseError # E: Too few arguments for "MyBaseError"
442-
raise MyError # E: Too few arguments for "MyError"
443-
raise MyKwError # E: Missing named argument "kwonly" for "MyKwError"
437+
if object():
438+
raise BaseException
439+
if object():
440+
raise Exception
441+
if object():
442+
raise BaseException(1)
443+
if object():
444+
raise Exception(2)
445+
if object():
446+
raise MyBaseError(4)
447+
if object():
448+
raise MyError(5, 6)
449+
if object():
450+
raise MyKwError(kwonly=7)
451+
if object():
452+
raise MyErrorWithDefault(8)
453+
if object():
454+
raise MyErrorWithDefault
455+
if object():
456+
raise MyBaseError # E: Too few arguments for "MyBaseError"
457+
if object():
458+
raise MyError # E: Too few arguments for "MyError"
459+
if object():
460+
raise MyKwError # E: Missing named argument "kwonly" for "MyKwError"
444461
[builtins fixtures/exception.pyi]
445462

446463
[case testRaiseExceptionType]
@@ -473,10 +490,14 @@ f: MyError
473490
a: A
474491
x: BaseException
475492
del x
476-
raise e from a # E: Exception must be derived from BaseException
477-
raise e from e
478-
raise e from f
479-
raise e from x # E: Trying to read deleted variable "x"
493+
if object():
494+
raise e from a # E: Exception must be derived from BaseException
495+
if object():
496+
raise e from e
497+
if object():
498+
raise e from f
499+
if object():
500+
raise e from x # E: Trying to read deleted variable "x"
480501
class A: pass
481502
class MyError(BaseException): pass
482503
[builtins fixtures/exception.pyi]
@@ -486,11 +507,16 @@ import typing
486507
class A: pass
487508
class MyError(BaseException): pass
488509
def f(): pass
489-
raise BaseException from BaseException
490-
raise BaseException from MyError
491-
raise BaseException from A # E: Exception must be derived from BaseException
492-
raise BaseException from object # E: Exception must be derived from BaseException
493-
raise BaseException from f # E: Exception must be derived from BaseException
510+
if object():
511+
raise BaseException from BaseException
512+
if object():
513+
raise BaseException from MyError
514+
if object():
515+
raise BaseException from A # E: Exception must be derived from BaseException
516+
if object():
517+
raise BaseException from object # E: Exception must be derived from BaseException
518+
if object():
519+
raise BaseException from f # E: Exception must be derived from BaseException
494520
[builtins fixtures/exception.pyi]
495521

496522
[case testTryFinallyStatement]

test-data/unit/check-typevar-tuple.test

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ reveal_type(f(args)) # N: Revealed type is "Tuple[builtins.int, builtins.str]"
1717

1818
reveal_type(f(varargs)) # N: Revealed type is "builtins.tuple[builtins.int, ...]"
1919

20-
f(0) # E: Argument 1 to "f" has incompatible type "int"; expected <nothing>
20+
if object():
21+
f(0) # E: Argument 1 to "f" has incompatible type "int"; expected <nothing>
2122

2223
def g(a: Tuple[Unpack[Ts]], b: Tuple[Unpack[Ts]]) -> Tuple[Unpack[Ts]]:
2324
return a

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -873,15 +873,15 @@ def expect_str(x: str) -> str: pass
873873
x: int
874874
if False:
875875
assert False
876-
reveal_type(x)
876+
reveal_type(x) # E: Statement is unreachable
877877

878878
if False:
879879
raise Exception()
880-
reveal_type(x)
880+
reveal_type(x) # E: Statement is unreachable
881881

882882
if False:
883883
assert_never(x)
884-
reveal_type(x)
884+
reveal_type(x) # E: Statement is unreachable
885885

886886
if False:
887887
nonthrowing_assert_never(x) # E: Statement is unreachable
@@ -890,7 +890,7 @@ if False:
890890
if False:
891891
# Ignore obvious type errors
892892
assert_never(expect_str(x))
893-
reveal_type(x)
893+
reveal_type(x) # E: Statement is unreachable
894894
[builtins fixtures/exception.pyi]
895895

896896
[case testNeverVariants]

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy