Skip to content

Commit 5186052

Browse files
[stubtest] Improve checking of positional-only parameters in dunder methods (#19593)
Currently, mypy treats all special methods as being implicitly positional-only. This creates a blind spot for stubtest, since it means that stubtest isn't sensitive to whether special methods in stub files are typed as being positional-only or not. This PR adds an internal option to disable the positional-only-special-method heuristic when running stubtest. This way, stubtest can see the actual stub signatures as written in the stub files, so we can meaningfully compare them against the runtime signatures. Now, stubtest will warn about dunder methods that are positional-only at runtime but keyword-or-positional in the stubs. Note that it still doesn't warn about the reverse (keyword-or-positional at runtime but positional-only in the stubs). The latter might be worth revisiting as well, but the typeshed hits are less obvious and there are a number of cases where this is done deliberately that would need allowlist entries.
1 parent 5d6bde6 commit 5186052

4 files changed

Lines changed: 15 additions & 2 deletions

File tree

mypy/fastparse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,7 @@ def do_func_def(
913913

914914
lineno = n.lineno
915915
args = self.transform_args(n.args, lineno, no_type_check=no_type_check)
916-
if special_function_elide_names(n.name):
916+
if self.options.pos_only_special_methods and special_function_elide_names(n.name):
917917
for arg in args:
918918
arg.pos_only = True
919919

mypy/options.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,9 @@ def __init__(self) -> None:
413413
# Export line-level, limited, fine-grained dependency information in cache data
414414
# (undocumented feature).
415415
self.export_ref_info = False
416+
# Treat special methods as being implicitly positional-only.
417+
# Set to False when running stubtest.
418+
self.pos_only_special_methods = True
416419

417420
self.disable_bytearray_promotion = False
418421
self.disable_memoryview_promotion = False

mypy/stubtest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1081,7 +1081,6 @@ def _verify_signature(
10811081
and not stub_arg.variable.name.startswith("__")
10821082
and stub_arg.variable.name.strip("_") != "self"
10831083
and stub_arg.variable.name.strip("_") != "cls"
1084-
and not is_dunder(function_name, exclude_special=True) # noisy for dunder methods
10851084
):
10861085
yield (
10871086
f'stub parameter "{stub_arg.variable.name}" should be positional-only '
@@ -2301,6 +2300,7 @@ def test_stubs(args: _Arguments, use_builtins_fixtures: bool = False) -> int:
23012300
options.use_builtins_fixtures = use_builtins_fixtures
23022301
options.show_traceback = args.show_traceback
23032302
options.pdb = args.pdb
2303+
options.pos_only_special_methods = False
23042304

23052305
if options.config_file:
23062306

mypy/test/teststubtest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1724,6 +1724,16 @@ def test_dunders(self) -> Iterator[Case]:
17241724
runtime="class D:\n def __class_getitem__(cls, type): ...",
17251725
error=None,
17261726
)
1727+
yield Case(
1728+
stub="class E:\n def __getitem__(self, item: object) -> object: ...",
1729+
runtime="class E:\n def __getitem__(self, item: object, /) -> object: ...",
1730+
error="E.__getitem__",
1731+
)
1732+
yield Case(
1733+
stub="class F:\n def __getitem__(self, item: object, /) -> object: ...",
1734+
runtime="class F:\n def __getitem__(self, item: object) -> object: ...",
1735+
error=None,
1736+
)
17271737

17281738
@collect_cases
17291739
def test_not_subclassable(self) -> Iterator[Case]:

0 commit comments

Comments
 (0)