Skip to content

Commit b956e6a

Browse files
authored
stubtest: Private parameters can be omitted (#16507)
Fixes #16443
1 parent ede0b20 commit b956e6a

File tree

3 files changed

+87
-2
lines changed

3 files changed

+87
-2
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Next release
44

5+
Stubtest will ignore private function/method parameters when they are missing from the stub. Private parameters
6+
names start with a single underscore and have a default (PR [16507](https://github.com/python/mypy/pull/16507)).
7+
58
## Mypy 1.8
69

710
We’ve just uploaded mypy 1.8 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type checker for Python. This release includes new features, performance improvements and bug fixes. You can install it as follows:

mypy/stubtest.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -940,7 +940,8 @@ def _verify_signature(
940940
elif len(stub.pos) < len(runtime.pos):
941941
for runtime_arg in runtime.pos[len(stub.pos) :]:
942942
if runtime_arg.name not in stub.kwonly:
943-
yield f'stub does not have argument "{runtime_arg.name}"'
943+
if not _is_private_parameter(runtime_arg):
944+
yield f'stub does not have argument "{runtime_arg.name}"'
944945
else:
945946
yield f'runtime argument "{runtime_arg.name}" is not keyword-only'
946947

@@ -980,7 +981,8 @@ def _verify_signature(
980981
):
981982
yield f'stub argument "{arg}" is not keyword-only'
982983
else:
983-
yield f'stub does not have argument "{arg}"'
984+
if not _is_private_parameter(runtime.kwonly[arg]):
985+
yield f'stub does not have argument "{arg}"'
984986

985987
# Checks involving **kwargs
986988
if stub.varkw is None and runtime.varkw is not None:
@@ -995,6 +997,14 @@ def _verify_signature(
995997
yield f'runtime does not have **kwargs argument "{stub.varkw.variable.name}"'
996998

997999

1000+
def _is_private_parameter(arg: inspect.Parameter) -> bool:
1001+
return (
1002+
arg.name.startswith("_")
1003+
and not arg.name.startswith("__")
1004+
and arg.default is not inspect.Parameter.empty
1005+
)
1006+
1007+
9981008
@verify.register(nodes.FuncItem)
9991009
def verify_funcitem(
10001010
stub: nodes.FuncItem, runtime: MaybeMissing[Any], object_path: list[str]

mypy/test/teststubtest.py

+72
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,78 @@ def test_arg_kind(self) -> Iterator[Case]:
346346
error="stub_posonly_570",
347347
)
348348

349+
@collect_cases
350+
def test_private_parameters(self) -> Iterator[Case]:
351+
# Private parameters can optionally be omitted.
352+
yield Case(
353+
stub="def priv_pos_arg_missing() -> None: ...",
354+
runtime="def priv_pos_arg_missing(_p1=None): pass",
355+
error=None,
356+
)
357+
yield Case(
358+
stub="def multi_priv_args() -> None: ...",
359+
runtime="def multi_priv_args(_p='', _q=''): pass",
360+
error=None,
361+
)
362+
yield Case(
363+
stub="def priv_kwarg_missing() -> None: ...",
364+
runtime="def priv_kwarg_missing(*, _p2=''): pass",
365+
error=None,
366+
)
367+
# But if they are included, they must be correct.
368+
yield Case(
369+
stub="def priv_pos_arg_wrong(_p: int = ...) -> None: ...",
370+
runtime="def priv_pos_arg_wrong(_p=None): pass",
371+
error="priv_pos_arg_wrong",
372+
)
373+
yield Case(
374+
stub="def priv_kwarg_wrong(*, _p: int = ...) -> None: ...",
375+
runtime="def priv_kwarg_wrong(*, _p=None): pass",
376+
error="priv_kwarg_wrong",
377+
)
378+
# Private parameters must have a default and start with exactly one
379+
# underscore.
380+
yield Case(
381+
stub="def pos_arg_no_default() -> None: ...",
382+
runtime="def pos_arg_no_default(_np): pass",
383+
error="pos_arg_no_default",
384+
)
385+
yield Case(
386+
stub="def kwarg_no_default() -> None: ...",
387+
runtime="def kwarg_no_default(*, _np): pass",
388+
error="kwarg_no_default",
389+
)
390+
yield Case(
391+
stub="def double_underscore_pos_arg() -> None: ...",
392+
runtime="def double_underscore_pos_arg(__np = None): pass",
393+
error="double_underscore_pos_arg",
394+
)
395+
yield Case(
396+
stub="def double_underscore_kwarg() -> None: ...",
397+
runtime="def double_underscore_kwarg(*, __np = None): pass",
398+
error="double_underscore_kwarg",
399+
)
400+
# But spot parameters that are accidentally not marked kw-only and
401+
# vice-versa.
402+
yield Case(
403+
stub="def priv_arg_is_kwonly(_p=...) -> None: ...",
404+
runtime="def priv_arg_is_kwonly(*, _p=''): pass",
405+
error="priv_arg_is_kwonly",
406+
)
407+
yield Case(
408+
stub="def priv_arg_is_positional(*, _p=...) -> None: ...",
409+
runtime="def priv_arg_is_positional(_p=''): pass",
410+
error="priv_arg_is_positional",
411+
)
412+
# Private parameters not at the end of the parameter list must be
413+
# included so that users can pass the following arguments using
414+
# positional syntax.
415+
yield Case(
416+
stub="def priv_args_not_at_end(*, q='') -> None: ...",
417+
runtime="def priv_args_not_at_end(_p='', q=''): pass",
418+
error="priv_args_not_at_end",
419+
)
420+
349421
@collect_cases
350422
def test_default_presence(self) -> Iterator[Case]:
351423
yield Case(

0 commit comments

Comments
 (0)