Skip to content

Commit 303030c

Browse files
authored
Merge pull request #7613 from bluetech/typing-warn-unreachable
typing: set warn_unreachable
2 parents 0d65e4b + 9ab14c6 commit 303030c

24 files changed

+60
-46
lines changed

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,6 @@ show_error_codes = True
103103
strict_equality = True
104104
warn_redundant_casts = True
105105
warn_return_any = True
106+
warn_unreachable = True
106107
warn_unused_configs = True
107108
no_implicit_reexport = True

src/_pytest/_code/code.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]:
676676

677677
def get_source(
678678
self,
679-
source: "Source",
679+
source: Optional["Source"],
680680
line_index: int = -1,
681681
excinfo: Optional[ExceptionInfo[BaseException]] = None,
682682
short: bool = False,

src/_pytest/assertion/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def register_assert_rewrite(*names: str) -> None:
5757
"""
5858
for name in names:
5959
if not isinstance(name, str):
60-
msg = "expected module names as *args, got {0} instead"
60+
msg = "expected module names as *args, got {0} instead" # type: ignore[unreachable]
6161
raise TypeError(msg.format(repr(names)))
6262
for hook in sys.meta_path:
6363
if isinstance(hook, rewrite.AssertionRewritingHook):

src/_pytest/assertion/rewrite.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from typing import Callable
1717
from typing import Dict
1818
from typing import IO
19+
from typing import Iterable
1920
from typing import List
2021
from typing import Optional
2122
from typing import Sequence
@@ -455,12 +456,9 @@ def _should_repr_global_name(obj: object) -> bool:
455456
return True
456457

457458

458-
def _format_boolop(explanations, is_or: bool):
459+
def _format_boolop(explanations: Iterable[str], is_or: bool) -> str:
459460
explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")"
460-
if isinstance(explanation, str):
461-
return explanation.replace("%", "%%")
462-
else:
463-
return explanation.replace(b"%", b"%%")
461+
return explanation.replace("%", "%%")
464462

465463

466464
def _call_reprcompare(

src/_pytest/capture.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def _py36_windowsconsoleio_workaround(stream: TextIO) -> None:
116116
return
117117

118118
# Bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666).
119-
if not hasattr(stream, "buffer"):
119+
if not hasattr(stream, "buffer"): # type: ignore[unreachable]
120120
return
121121

122122
buffered = hasattr(stream.buffer, "raw")

src/_pytest/compat.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,9 @@ def getfuncargnames(
176176
p.name
177177
for p in parameters.values()
178178
if (
179-
p.kind is Parameter.POSITIONAL_OR_KEYWORD
180-
or p.kind is Parameter.KEYWORD_ONLY
179+
# TODO: Remove type ignore after https://github.com/python/typeshed/pull/4383
180+
p.kind is Parameter.POSITIONAL_OR_KEYWORD # type: ignore[unreachable]
181+
or p.kind is Parameter.KEYWORD_ONLY # type: ignore[unreachable]
181182
)
182183
and p.default is Parameter.empty
183184
)

src/_pytest/config/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1390,7 +1390,7 @@ def _assertion_supported() -> bool:
13901390
except AssertionError:
13911391
return True
13921392
else:
1393-
return False
1393+
return False # type: ignore[unreachable]
13941394

13951395

13961396
def create_terminal_writer(

src/_pytest/config/findpaths.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def locate_config(
107107

108108

109109
def get_common_ancestor(paths: Iterable[py.path.local]) -> py.path.local:
110-
common_ancestor = None
110+
common_ancestor = None # type: Optional[py.path.local]
111111
for path in paths:
112112
if not path.exists():
113113
continue

src/_pytest/debugging.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import Callable
88
from typing import Generator
99
from typing import List
10+
from typing import Optional
1011
from typing import Tuple
1112
from typing import Union
1213

@@ -23,6 +24,8 @@
2324
from _pytest.reports import BaseReport
2425

2526
if TYPE_CHECKING:
27+
from typing import Type
28+
2629
from _pytest.capture import CaptureManager
2730
from _pytest.runner import CallInfo
2831

@@ -92,20 +95,22 @@ def fin() -> None:
9295
class pytestPDB:
9396
"""Pseudo PDB that defers to the real pdb."""
9497

95-
_pluginmanager = None # type: PytestPluginManager
98+
_pluginmanager = None # type: Optional[PytestPluginManager]
9699
_config = None # type: Config
97-
_saved = [] # type: List[Tuple[Callable[..., None], PytestPluginManager, Config]]
100+
_saved = (
101+
[]
102+
) # type: List[Tuple[Callable[..., None], Optional[PytestPluginManager], Config]]
98103
_recursive_debug = 0
99-
_wrapped_pdb_cls = None
104+
_wrapped_pdb_cls = None # type: Optional[Tuple[Type[Any], Type[Any]]]
100105

101106
@classmethod
102-
def _is_capturing(cls, capman: "CaptureManager") -> Union[str, bool]:
107+
def _is_capturing(cls, capman: Optional["CaptureManager"]) -> Union[str, bool]:
103108
if capman:
104109
return capman.is_capturing()
105110
return False
106111

107112
@classmethod
108-
def _import_pdb_cls(cls, capman: "CaptureManager"):
113+
def _import_pdb_cls(cls, capman: Optional["CaptureManager"]):
109114
if not cls._config:
110115
import pdb
111116

@@ -144,7 +149,7 @@ def _import_pdb_cls(cls, capman: "CaptureManager"):
144149
return wrapped_cls
145150

146151
@classmethod
147-
def _get_pdb_wrapper_class(cls, pdb_cls, capman: "CaptureManager"):
152+
def _get_pdb_wrapper_class(cls, pdb_cls, capman: Optional["CaptureManager"]):
148153
import _pytest.config
149154

150155
# Type ignored because mypy doesn't support "dynamic"
@@ -176,9 +181,11 @@ def do_continue(self, arg):
176181
"PDB continue (IO-capturing resumed for %s)"
177182
% capturing,
178183
)
184+
assert capman is not None
179185
capman.resume()
180186
else:
181187
tw.sep(">", "PDB continue")
188+
assert cls._pluginmanager is not None
182189
cls._pluginmanager.hook.pytest_leave_pdb(config=cls._config, pdb=self)
183190
self._continued = True
184191
return ret
@@ -232,10 +239,10 @@ def _init_pdb(cls, method, *args, **kwargs):
232239
"""Initialize PDB debugging, dropping any IO capturing."""
233240
import _pytest.config
234241

235-
if cls._pluginmanager is not None:
236-
capman = cls._pluginmanager.getplugin("capturemanager")
242+
if cls._pluginmanager is None:
243+
capman = None # type: Optional[CaptureManager]
237244
else:
238-
capman = None
245+
capman = cls._pluginmanager.getplugin("capturemanager")
239246
if capman:
240247
capman.suspend(in_=True)
241248

src/_pytest/doctest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -635,8 +635,8 @@ def _remove_unwanted_precision(self, want: str, got: str) -> str:
635635
return got
636636
offset = 0
637637
for w, g in zip(wants, gots):
638-
fraction = w.group("fraction")
639-
exponent = w.group("exponent1")
638+
fraction = w.group("fraction") # type: Optional[str]
639+
exponent = w.group("exponent1") # type: Optional[str]
640640
if exponent is None:
641641
exponent = w.group("exponent2")
642642
if fraction is None:

src/_pytest/fixtures.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -993,7 +993,8 @@ def __init__(
993993
else:
994994
scope_ = scope
995995
self.scopenum = scope2index(
996-
scope_ or "function",
996+
# TODO: Check if the `or` here is really necessary.
997+
scope_ or "function", # type: ignore[unreachable]
997998
descr="Fixture '{}'".format(func.__name__),
998999
where=baseid,
9991000
)
@@ -1319,7 +1320,7 @@ def fixture( # noqa: F811
13191320
# **kwargs and check `in`, but that obfuscates the function signature.
13201321
if isinstance(fixture_function, str):
13211322
# It's actually the first positional argument, scope.
1322-
args = (fixture_function, *args)
1323+
args = (fixture_function, *args) # type: ignore[unreachable]
13231324
fixture_function = None
13241325
duplicated_args = []
13251326
if len(args) > 0:

src/_pytest/junitxml.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ def _check_record_param_type(param: str, v: str) -> None:
330330
type."""
331331
__tracebackhide__ = True
332332
if not isinstance(v, str):
333-
msg = "{param} parameter needs to be a string, but {g} given"
333+
msg = "{param} parameter needs to be a string, but {g} given" # type: ignore[unreachable]
334334
raise TypeError(msg.format(param=param, g=type(v).__name__))
335335

336336

src/_pytest/monkeypatch.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def annotated_getattr(obj: object, name: str, ann: str) -> object:
9191

9292

9393
def derive_importpath(import_path: str, raising: bool) -> Tuple[str, object]:
94-
if not isinstance(import_path, str) or "." not in import_path:
94+
if not isinstance(import_path, str) or "." not in import_path: # type: ignore[unreachable]
9595
raise TypeError(
9696
"must be absolute import path string, not {!r}".format(import_path)
9797
)
@@ -272,7 +272,7 @@ def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None:
272272
character.
273273
"""
274274
if not isinstance(value, str):
275-
warnings.warn(
275+
warnings.warn( # type: ignore[unreachable]
276276
pytest.PytestWarning(
277277
"Value of environment variable {name} type should be str, but got "
278278
"{value!r} (type: {type}); converted to str implicitly".format(

src/_pytest/outcomes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class OutcomeException(BaseException):
2828

2929
def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None:
3030
if msg is not None and not isinstance(msg, str):
31-
error_msg = (
31+
error_msg = ( # type: ignore[unreachable]
3232
"{} expected string as 'msg' parameter, got '{}' instead.\n"
3333
"Perhaps you meant to use a mark?"
3434
)

src/_pytest/pathlib.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,6 @@ def make_numbered_dir_with_cleanup(
367367

368368

369369
def resolve_from_str(input: str, root: py.path.local) -> Path:
370-
assert not isinstance(input, Path), "would break on py2"
371370
rootpath = Path(root)
372371
input = expanduser(input)
373372
input = expandvars(input)

src/_pytest/python.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1097,7 +1097,10 @@ def _validate_ids(
10971097
elif isinstance(id_value, (float, int, bool)):
10981098
new_ids.append(str(id_value))
10991099
else:
1100-
msg = "In {}: ids must be list of string/float/int/bool, found: {} (type: {!r}) at index {}"
1100+
msg = ( # type: ignore[unreachable]
1101+
"In {}: ids must be list of string/float/int/bool, "
1102+
"found: {} (type: {!r}) at index {}"
1103+
)
11011104
fail(
11021105
msg.format(func_name, saferepr(id_value), type(id_value), idx),
11031106
pytrace=False,

src/_pytest/python_api.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,8 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
495495
elif (
496496
isinstance(expected, Iterable)
497497
and isinstance(expected, Sized)
498-
and not isinstance(expected, STRING_TYPES)
498+
# Type ignored because the error is wrong -- not unreachable.
499+
and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable]
499500
):
500501
cls = ApproxSequencelike
501502
else:
@@ -662,8 +663,8 @@ def raises( # noqa: F811
662663
else:
663664
excepted_exceptions = expected_exception
664665
for exc in excepted_exceptions:
665-
if not isinstance(exc, type) or not issubclass(exc, BaseException):
666-
msg = "expected exception must be a BaseException type, not {}"
666+
if not isinstance(exc, type) or not issubclass(exc, BaseException): # type: ignore[unreachable]
667+
msg = "expected exception must be a BaseException type, not {}" # type: ignore[unreachable]
667668
not_a = exc.__name__ if isinstance(exc, type) else type(exc).__name__
668669
raise TypeError(msg.format(not_a))
669670

src/_pytest/reports.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,8 @@ def pytest_report_to_serializable(
386386
data = report._to_json()
387387
data["$report_type"] = report.__class__.__name__
388388
return data
389-
return None
389+
# TODO: Check if this is actually reachable.
390+
return None # type: ignore[unreachable]
390391

391392

392393
def pytest_report_from_serializable(

src/_pytest/terminal.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -522,12 +522,12 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
522522
rep = report
523523
res = self.config.hook.pytest_report_teststatus(
524524
report=rep, config=self.config
525-
) # type: Tuple[str, str, str]
525+
) # type: Tuple[str, str, Union[str, Tuple[str, Mapping[str, bool]]]]
526526
category, letter, word = res
527-
if isinstance(word, tuple):
528-
word, markup = word
529-
else:
527+
if not isinstance(word, tuple):
530528
markup = None
529+
else:
530+
word, markup = word
531531
self._add_stats(category, [rep])
532532
if not letter and not word:
533533
# Probably passed setup/teardown.

src/_pytest/tmpdir.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class TempPathFactory:
2626
"""
2727

2828
_given_basetemp = attr.ib(
29-
type=Path,
29+
type=Optional[Path],
3030
# Use os.path.abspath() to get absolute path instead of resolve() as it
3131
# does not work the same in all platforms (see #4427).
3232
# Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012).

testing/code/test_source.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def c() -> None:
238238
c(1) # type: ignore
239239
finally:
240240
if teardown:
241-
teardown()
241+
teardown() # type: ignore[unreachable]
242242
source = excinfo.traceback[-1].statement
243243
assert str(source).strip() == "c(1) # type: ignore"
244244

testing/test_assertrewrite.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ def f6() -> None:
381381
)
382382

383383
def f7() -> None:
384-
assert False or x()
384+
assert False or x() # type: ignore[unreachable]
385385

386386
assert (
387387
getmsg(f7, {"x": x})
@@ -416,7 +416,7 @@ def f11() -> None:
416416

417417
def test_short_circuit_evaluation(self) -> None:
418418
def f1() -> None:
419-
assert True or explode # type: ignore[name-defined] # noqa: F821
419+
assert True or explode # type: ignore[name-defined,unreachable] # noqa: F821
420420

421421
getmsg(f1, must_pass=True)
422422

@@ -471,7 +471,7 @@ def f1() -> None:
471471
assert getmsg(f1) == "assert ((3 % 2) and False)"
472472

473473
def f2() -> None:
474-
assert False or 4 % 2
474+
assert False or 4 % 2 # type: ignore[unreachable]
475475

476476
assert getmsg(f2) == "assert (False or (4 % 2))"
477477

testing/test_monkeypatch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ def test_issue156_undo_staticmethod(Sample: "Type[Sample]") -> None:
360360
monkeypatch.setattr(Sample, "hello", None)
361361
assert Sample.hello is None
362362

363-
monkeypatch.undo()
363+
monkeypatch.undo() # type: ignore[unreachable]
364364
assert Sample.hello()
365365

366366

testing/test_pytester.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ def test_make_hook_recorder(testdir) -> None:
2323
recorder = testdir.make_hook_recorder(item.config.pluginmanager)
2424
assert not recorder.getfailures()
2525

26-
pytest.xfail("internal reportrecorder tests need refactoring")
26+
# (The silly condition is to fool mypy that the code below this is reachable)
27+
if 1 + 1 == 2:
28+
pytest.xfail("internal reportrecorder tests need refactoring")
2729

2830
class rep:
2931
excinfo = None

0 commit comments

Comments
 (0)