Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stubtest: Private parameters can be omitted #16507

Merged
merged 12 commits into from Feb 5, 2024
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,9 @@

Stubgen will now include `__all__` in its output if it is in the input file (PR [16356](https://github.com/python/mypy/pull/16356)).

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.7

We’ve just uploaded mypy 1.7 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
Expand Up @@ -914,7 +914,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}"'
srittau marked this conversation as resolved.
Show resolved Hide resolved
else:
yield f'runtime argument "{runtime_arg.name}" is not keyword-only'

Expand Down Expand Up @@ -954,7 +955,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 @@ -969,6 +971,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
59 changes: 59 additions & 0 deletions mypy/test/teststubtest.py
Expand Up @@ -341,6 +341,65 @@ 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 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",
)

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