Skip to content

Commit

Permalink
stubtest: Private parameters can be omitted (#16507)
Browse files Browse the repository at this point in the history
Fixes #16443
  • Loading branch information
srittau authored Feb 5, 2024
1 parent ede0b20 commit b956e6a
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 2 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Next release

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

## Mypy 1.8

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:
Expand Down
14 changes: 12 additions & 2 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,8 @@ def _verify_signature(
elif len(stub.pos) < len(runtime.pos):
for runtime_arg in runtime.pos[len(stub.pos) :]:
if runtime_arg.name not in stub.kwonly:
yield f'stub does not have argument "{runtime_arg.name}"'
if not _is_private_parameter(runtime_arg):
yield f'stub does not have argument "{runtime_arg.name}"'
else:
yield f'runtime argument "{runtime_arg.name}" is not keyword-only'

Expand Down Expand Up @@ -980,7 +981,8 @@ def _verify_signature(
):
yield f'stub argument "{arg}" is not keyword-only'
else:
yield f'stub does not have argument "{arg}"'
if not _is_private_parameter(runtime.kwonly[arg]):
yield f'stub does not have argument "{arg}"'

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


def _is_private_parameter(arg: inspect.Parameter) -> bool:
return (
arg.name.startswith("_")
and not arg.name.startswith("__")
and arg.default is not inspect.Parameter.empty
)


@verify.register(nodes.FuncItem)
def verify_funcitem(
stub: nodes.FuncItem, runtime: MaybeMissing[Any], object_path: list[str]
Expand Down
72 changes: 72 additions & 0 deletions mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,78 @@ def test_arg_kind(self) -> Iterator[Case]:
error="stub_posonly_570",
)

@collect_cases
def test_private_parameters(self) -> Iterator[Case]:
# Private parameters can optionally be omitted.
yield Case(
stub="def priv_pos_arg_missing() -> None: ...",
runtime="def priv_pos_arg_missing(_p1=None): pass",
error=None,
)
yield Case(
stub="def multi_priv_args() -> None: ...",
runtime="def multi_priv_args(_p='', _q=''): pass",
error=None,
)
yield Case(
stub="def priv_kwarg_missing() -> None: ...",
runtime="def priv_kwarg_missing(*, _p2=''): pass",
error=None,
)
# But if they are included, they must be correct.
yield Case(
stub="def priv_pos_arg_wrong(_p: int = ...) -> None: ...",
runtime="def priv_pos_arg_wrong(_p=None): pass",
error="priv_pos_arg_wrong",
)
yield Case(
stub="def priv_kwarg_wrong(*, _p: int = ...) -> None: ...",
runtime="def priv_kwarg_wrong(*, _p=None): pass",
error="priv_kwarg_wrong",
)
# Private parameters must have a default and start with exactly one
# underscore.
yield Case(
stub="def pos_arg_no_default() -> None: ...",
runtime="def pos_arg_no_default(_np): pass",
error="pos_arg_no_default",
)
yield Case(
stub="def kwarg_no_default() -> None: ...",
runtime="def kwarg_no_default(*, _np): pass",
error="kwarg_no_default",
)
yield Case(
stub="def double_underscore_pos_arg() -> None: ...",
runtime="def double_underscore_pos_arg(__np = None): pass",
error="double_underscore_pos_arg",
)
yield Case(
stub="def double_underscore_kwarg() -> None: ...",
runtime="def double_underscore_kwarg(*, __np = None): pass",
error="double_underscore_kwarg",
)
# But spot parameters that are accidentally not marked kw-only and
# vice-versa.
yield Case(
stub="def priv_arg_is_kwonly(_p=...) -> None: ...",
runtime="def priv_arg_is_kwonly(*, _p=''): pass",
error="priv_arg_is_kwonly",
)
yield Case(
stub="def priv_arg_is_positional(*, _p=...) -> None: ...",
runtime="def priv_arg_is_positional(_p=''): pass",
error="priv_arg_is_positional",
)
# Private parameters not at the end of the parameter list must be
# included so that users can pass the following arguments using
# positional syntax.
yield Case(
stub="def priv_args_not_at_end(*, q='') -> None: ...",
runtime="def priv_args_not_at_end(_p='', q=''): pass",
error="priv_args_not_at_end",
)

@collect_cases
def test_default_presence(self) -> Iterator[Case]:
yield Case(
Expand Down

0 comments on commit b956e6a

Please sign in to comment.