diff --git a/.github/workflows/test_stubgenc.yml b/.github/workflows/test_stubgenc.yml index 4676acf8695b..6cf3cb71c3ff 100644 --- a/.github/workflows/test_stubgenc.yml +++ b/.github/workflows/test_stubgenc.yml @@ -11,7 +11,7 @@ on: - 'mypy/stubgenc.py' - 'mypy/stubdoc.py' - 'mypy/stubutil.py' - - 'test-data/stubgen/**' + - 'test-data/pybind11_fixtures/**' permissions: contents: read diff --git a/mypy/stubdoc.py b/mypy/stubdoc.py index 89db6cb3378f..c5e918c01225 100644 --- a/mypy/stubdoc.py +++ b/mypy/stubdoc.py @@ -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( @@ -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: @@ -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] = [] @@ -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("*"): @@ -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 = [] diff --git a/mypy/test/teststubgen.py b/mypy/test/teststubgen.py index 43974cf8ec68..8ee76547d4a2 100644 --- a/mypy/test/teststubgen.py +++ b/mypy/test/teststubgen.py @@ -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, ) ], ) @@ -470,6 +487,8 @@ 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, ) ], ) @@ -477,17 +496,35 @@ def test_infer_sig_from_docstring_positional_only_arguments(self) -> None: 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( @@ -497,6 +534,7 @@ 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, ) ], ) @@ -504,7 +542,15 @@ def test_infer_sig_from_docstring_keyword_only_arguments(self) -> None: 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( @@ -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, ) ], ) @@ -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, ) ], ) diff --git a/test-data/pybind11_fixtures/expected_stubs_no_docs/pybind11_fixtures/demo.pyi b/test-data/pybind11_fixtures/expected_stubs_no_docs/pybind11_fixtures/demo.pyi index 87b8ec0e4ad6..a7318b38908f 100644 --- a/test-data/pybind11_fixtures/expected_stubs_no_docs/pybind11_fixtures/demo.pyi +++ b/test-data/pybind11_fixtures/expected_stubs_no_docs/pybind11_fixtures/demo.pyi @@ -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: ... diff --git a/test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/__init__.pyi b/test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/__init__.pyi index 0eeb788d4278..3c97d03938d7 100644 --- a/test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/__init__.pyi +++ b/test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/__init__.pyi @@ -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]""" diff --git a/test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/demo.pyi b/test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/demo.pyi index 6e285f202f1a..2e8bbf21801a 100644 --- a/test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/demo.pyi +++ b/test-data/pybind11_fixtures/expected_stubs_with_docs/pybind11_fixtures/demo.pyi @@ -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: @@ -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: @@ -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) @@ -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 diff --git a/test-data/pybind11_fixtures/pyproject.toml b/test-data/pybind11_fixtures/pyproject.toml index 773d036e62f5..ea3ed4734feb 100644 --- a/test-data/pybind11_fixtures/pyproject.toml +++ b/test-data/pybind11_fixtures/pyproject.toml @@ -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" diff --git a/test-data/pybind11_fixtures/src/main.cpp b/test-data/pybind11_fixtures/src/main.cpp index 4d275ab1fd70..fde1b391a222 100644 --- a/test-data/pybind11_fixtures/src/main.cpp +++ b/test-data/pybind11_fixtures/src/main.cpp @@ -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); } // ----------------------------------------------------------------------------