Skip to content

Adds positional and kwarg only symbols into generated stubs #19340

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

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test_stubgenc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
- 'mypy/stubgenc.py'
- 'mypy/stubdoc.py'
- 'mypy/stubutil.py'
- 'test-data/stubgen/**'
- 'test-data/pybind11_fixtures/**'

permissions:
contents: read
Expand Down
18 changes: 17 additions & 1 deletion mypy/stubdoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ class FunctionSig(NamedTuple):
ret_type: str | None
type_args: str = "" # TODO implement in stubgenc and remove the default
docstring: str | None = None
pos_only_index: int | None = None
kwarg_only_index: int | None = None

def is_special_method(self) -> bool:
return bool(
Expand Down Expand Up @@ -139,6 +141,11 @@ def format_sig(

args.append(arg_def)

if self.pos_only_index:
args.insert(self.pos_only_index, "/")
if self.kwarg_only_index:
args.insert(self.kwarg_only_index, "*")

retfield = ""
ret_type = self.ret_type if self.ret_type else any_val
if ret_type is not None:
Expand Down Expand Up @@ -182,6 +189,7 @@ def __init__(self, function_name: str) -> None:
self.args: list[ArgSig] = []
self.pos_only: int | None = None
self.keyword_only: int | None = None
self.keyword_only_index: int | None = None
# Valid signatures found so far.
self.signatures: list[FunctionSig] = []

Expand Down Expand Up @@ -265,6 +273,8 @@ def add_token(self, token: tokenize.TokenInfo) -> None:
self.reset()
return
self.keyword_only = len(self.args)
pos_offset = 1 if self.pos_only is not None else 0
self.keyword_only_index = self.keyword_only + pos_offset
self.accumulator = ""
else:
if self.accumulator.startswith("*"):
Expand Down Expand Up @@ -342,7 +352,13 @@ def add_token(self, token: tokenize.TokenInfo) -> None:

if self.found:
self.signatures.append(
FunctionSig(name=self.function_name, args=self.args, ret_type=self.ret_type)
FunctionSig(
name=self.function_name,
args=self.args,
pos_only_index=self.pos_only,
kwarg_only_index=self.keyword_only_index,
ret_type=self.ret_type,
)
)
self.found = False
self.args = []
Expand Down
66 changes: 58 additions & 8 deletions mypy/test/teststubgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,28 +437,45 @@ def test_infer_sig_from_docstring_args_kwargs_errors(self) -> None:
def test_infer_sig_from_docstring_positional_only_arguments(self) -> None:
assert_equal(
infer_sig_from_docstring("func(self, /) -> str", "func"),
[FunctionSig(name="func", args=[ArgSig(name="self")], ret_type="str")],
[
FunctionSig(
name="func", args=[ArgSig(name="self")], ret_type="str", pos_only_index=1
)
],
)

assert_equal(
infer_sig_from_docstring("func(self, x, /) -> str", "func"),
[
FunctionSig(
name="func", args=[ArgSig(name="self"), ArgSig(name="x")], ret_type="str"
name="func",
args=[ArgSig(name="self"), ArgSig(name="x")],
ret_type="str",
pos_only_index=2,
)
],
)

assert_equal(
infer_sig_from_docstring("func(x, /, y) -> int", "func"),
[FunctionSig(name="func", args=[ArgSig(name="x"), ArgSig(name="y")], ret_type="int")],
[
FunctionSig(
name="func",
args=[ArgSig(name="x"), ArgSig(name="y")],
ret_type="int",
pos_only_index=1,
)
],
)

assert_equal(
infer_sig_from_docstring("func(x, /, *args) -> str", "func"),
[
FunctionSig(
name="func", args=[ArgSig(name="x"), ArgSig(name="*args")], ret_type="str"
name="func",
args=[ArgSig(name="x"), ArgSig(name="*args")],
ret_type="str",
pos_only_index=1,
)
],
)
Expand All @@ -470,24 +487,44 @@ def test_infer_sig_from_docstring_positional_only_arguments(self) -> None:
name="func",
args=[ArgSig(name="x"), ArgSig(name="kwonly"), ArgSig(name="**kwargs")],
ret_type="str",
pos_only_index=1,
kwarg_only_index=2,
)
],
)

def test_infer_sig_from_docstring_keyword_only_arguments(self) -> None:
assert_equal(
infer_sig_from_docstring("func(*, x) -> str", "func"),
[FunctionSig(name="func", args=[ArgSig(name="x")], ret_type="str")],
[
FunctionSig(
name="func", args=[ArgSig(name="x")], ret_type="str", kwarg_only_index=0
)
],
)

assert_equal(
infer_sig_from_docstring("func(x, *, y) -> str", "func"),
[FunctionSig(name="func", args=[ArgSig(name="x"), ArgSig(name="y")], ret_type="str")],
[
FunctionSig(
name="func",
args=[ArgSig(name="x"), ArgSig(name="y")],
ret_type="str",
kwarg_only_index=1,
)
],
)

assert_equal(
infer_sig_from_docstring("func(*, x, y) -> str", "func"),
[FunctionSig(name="func", args=[ArgSig(name="x"), ArgSig(name="y")], ret_type="str")],
[
FunctionSig(
name="func",
args=[ArgSig(name="x"), ArgSig(name="y")],
ret_type="str",
kwarg_only_index=0,
)
],
)

assert_equal(
Expand All @@ -497,14 +534,23 @@ def test_infer_sig_from_docstring_keyword_only_arguments(self) -> None:
name="func",
args=[ArgSig(name="x"), ArgSig(name="kwonly"), ArgSig("**kwargs")],
ret_type="str",
kwarg_only_index=1,
)
],
)

def test_infer_sig_from_docstring_pos_only_and_keyword_only_arguments(self) -> None:
assert_equal(
infer_sig_from_docstring("func(x, /, *, y) -> str", "func"),
[FunctionSig(name="func", args=[ArgSig(name="x"), ArgSig(name="y")], ret_type="str")],
[
FunctionSig(
name="func",
args=[ArgSig(name="x"), ArgSig(name="y")],
ret_type="str",
pos_only_index=1,
kwarg_only_index=2,
)
],
)

assert_equal(
Expand All @@ -514,6 +560,8 @@ def test_infer_sig_from_docstring_pos_only_and_keyword_only_arguments(self) -> N
name="func",
args=[ArgSig(name="x"), ArgSig(name="y"), ArgSig(name="z")],
ret_type="str",
pos_only_index=1,
kwarg_only_index=3,
)
],
)
Expand All @@ -530,6 +578,8 @@ def test_infer_sig_from_docstring_pos_only_and_keyword_only_arguments(self) -> N
ArgSig("**kwargs"),
],
ret_type="str",
pos_only_index=1,
kwarg_only_index=3,
)
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,9 @@ class Point:

def answer() -> int: ...
def midpoint(left: float, right: float) -> float: ...
def pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple: ...
def pos_only_all(i: int, j: int, /) -> tuple: ...
def pos_only_def_mix(i: int, j: int = ..., /, k: int = ...) -> tuple: ...
def pos_only_mix(i: int, /, j: int) -> tuple: ...
def sum(arg0: int, arg1: int) -> int: ...
def weighted_midpoint(left: float, right: float, alpha: float = ...) -> float: ...
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ def func_incomplete_signature(*args, **kwargs):
def func_returning_optional() -> int | None:
"""func_returning_optional() -> Optional[int]"""
def func_returning_pair() -> tuple[int, float]:
"""func_returning_pair() -> Tuple[int, float]"""
"""func_returning_pair() -> tuple[int, float]"""
def func_returning_path() -> os.PathLike:
"""func_returning_path() -> os.PathLike"""
def func_returning_vector() -> list[float]:
"""func_returning_vector() -> List[float]"""
"""func_returning_vector() -> list[float]"""
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ class Point:
"""__ne__(self: object, other: object) -> bool"""
@property
def name(self) -> str:
"""name(self: handle) -> str
"""name(self: object) -> str

name(self: handle) -> str
name(self: object) -> str
"""
@property
def value(self) -> int:
Expand Down Expand Up @@ -63,9 +63,9 @@ class Point:
"""__ne__(self: object, other: object) -> bool"""
@property
def name(self) -> str:
"""name(self: handle) -> str
"""name(self: object) -> str

name(self: handle) -> str
name(self: object) -> str
"""
@property
def value(self) -> int:
Expand Down Expand Up @@ -96,7 +96,7 @@ class Point:
2. __init__(self: pybind11_fixtures.demo.Point, x: float, y: float) -> None
"""
def as_list(self) -> list[float]:
"""as_list(self: pybind11_fixtures.demo.Point) -> List[float]"""
"""as_list(self: pybind11_fixtures.demo.Point) -> list[float]"""
@overload
def distance_to(self, x: float, y: float) -> float:
"""distance_to(*args, **kwargs)
Expand Down Expand Up @@ -126,6 +126,14 @@ def answer() -> int:
'''
def midpoint(left: float, right: float) -> float:
"""midpoint(left: float, right: float) -> float"""
def pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple:
"""pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple"""
def pos_only_all(i: int, j: int, /) -> tuple:
"""pos_only_all(i: int, j: int, /) -> tuple"""
def pos_only_def_mix(i: int, j: int = ..., /, k: int = ...) -> tuple:
"""pos_only_def_mix(i: int, j: int = 2, /, k: int = 3) -> tuple"""
def pos_only_mix(i: int, /, j: int) -> tuple:
"""pos_only_mix(i: int, /, j: int) -> tuple"""
def sum(arg0: int, arg1: int) -> int:
'''sum(arg0: int, arg1: int) -> int

Expand Down
2 changes: 1 addition & 1 deletion test-data/pybind11_fixtures/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ requires = [
"wheel",
# Officially supported pybind11 version. This is pinned to guarantee 100% reproducible CI.
# As a result, the version needs to be bumped manually at will.
"pybind11==2.9.2",
"pybind11==2.13.6",
]

build-backend = "setuptools.build_meta"
29 changes: 29 additions & 0 deletions test-data/pybind11_fixtures/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,35 @@ void bind_demo(py::module& m) {
// Module-level attributes
m.attr("PI") = std::acos(-1);
m.attr("__version__") = "0.0.1";

// test_positional_only_args
m.def(
"pos_only_all",
[](int i, int j) { return py::make_tuple(i, j); },
py::arg("i"),
py::arg("j"),
py::pos_only());
m.def(
"pos_only_mix",
[](int i, int j) { return py::make_tuple(i, j); },
py::arg("i"),
py::pos_only(),
py::arg("j"));
m.def(
"pos_kw_only_mix",
[](int i, int j, int k) { return py::make_tuple(i, j, k); },
py::arg("i"),
py::pos_only(),
py::arg("j"),
py::kw_only(),
py::arg("k"));
m.def(
"pos_only_def_mix",
[](int i, int j, int k) { return py::make_tuple(i, j, k); },
py::arg("i"),
py::arg("j") = 2,
py::pos_only(),
py::arg("k") = 3);
}

// ----------------------------------------------------------------------------
Expand Down