mypy Statement is unreachable (tested)
mypy reports Statement is unreachable [unreachable] only when --warn-unreachable is on, for code its type narrowing says can never run: after an isinstance that always matches the declared type, after sys.exit(), or in a branch where a bool has no values left. Usually the annotation is too narrow.
1. Reproduce it
b1.py:
import sys
def parse(value: int) -> str:
if isinstance(value, int):
return "int"
return "other" # value is declared int, so never reached
def check(flag: bool) -> None:
if flag is True:
print("yes")
elif flag is False:
print("no")
else:
print("impossible") # bool narrowed away
def stop() -> None:
sys.exit(1)
print("after exit") # sys.exit returns NoReturn
if sys.platform == "win32":
import winreg # skipped on Linux, never reported
b2.py, the fixed version:
def parse(value: int | str) -> str: # widen the type if other values really arrive
if isinstance(value, int):
return "int"
return "other"
def check(flag: bool) -> None:
if flag:
print("yes")
else:
print("no")
def legacy(value: int) -> str:
if isinstance(value, int):
return "int"
return "other" # type: ignore[unreachable]
Output (mypy 2.4.0, Python 3.13.5, --no-incremental):
$ mypy b1.py
Success: no issues found in 1 source file
$ mypy --warn-unreachable b1.py
b1.py:6: error: Statement is unreachable [unreachable]
b1.py:14: error: Statement is unreachable [unreachable]
b1.py:18: error: Statement is unreachable [unreachable]
Found 3 errors in 1 file (checked 1 source file)
$ mypy --warn-unreachable b2.py
Success: no issues found in 1 source file
2. Why each line is unreachable
- The check is off by default: plain
mypy b1.pyreports nothing. Only--warn-unreachable(orwarn_unreachable = truein config) turns it on.--strictdid not enable it either (mypy --strict b1.py: Success). - Line 6:
valueis annotatedint, so afterisinstance(value, int)returns, mypy believes nothing is left. If other types really reach the function at runtime, the annotation is wrong: widen it (int | str). - Line 14: after
is Trueandis False, aboolhas no values left. Simplify toif flag: ... else: .... - Line 18: code after
sys.exit()(typedNoReturn) never runs. - The
sys.platform == "win32"block was not reported: mypy skips platform and version checks silently, by design.
3. Keeping a defensive branch
If you want a defensive fallback anyway, mark it with # type: ignore[unreachable] (legacy() in b2.py passed). For exhaustive checks on enums or unions, end with typing.assert_never(x) instead of an unreachable return.
Frequently asked questions
Does mypy --strict enable warn-unreachable?
No. In this test mypy --strict reported nothing; you need --warn-unreachable or warn_unreachable = true.
Why is my isinstance branch unreachable in mypy?
The parameter is annotated with a type that the isinstance check fully covers. Widen the annotation if other types really arrive.
How do I ignore a mypy unreachable error?
Add # type: ignore[unreachable] to the line.
Written with AI assistance. Measured on 2026-10-23 with mypy 2.4.0 and Python 3.13.5 on Linux. Files were checked verbatim and the output pasted unedited. PlainTables is our extension; mypy is free.