From f15fb974965bbceb6fc6a8f9d70a696996d849e5 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 25 Oct 2025 12:25:44 -0700 Subject: [PATCH 1/5] Type hint make_tuple / fix *args/**kwargs return type Signed-off-by: Michael Carlstrom --- include/pybind11/cast.h | 22 ++++++++++++++++------ include/pybind11/detail/init.h | 12 +++++++++--- include/pybind11/pybind11.h | 5 +++-- include/pybind11/typing.h | 5 +---- tests/test_kwargs_and_defaults.py | 31 ++++++++++++++++--------------- 5 files changed, 45 insertions(+), 30 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 4708101d80..1ca3f35c5b 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1465,21 +1465,24 @@ template <> struct handle_type_name { static constexpr auto name = const_name("weakref.ReferenceType"); }; +// args/Args/kwargs/KWArgs have name as well as typehint included template <> struct handle_type_name { - static constexpr auto name = const_name("*args"); + static constexpr auto name = io_name("*args", "tuple"); }; template struct handle_type_name> { - static constexpr auto name = const_name("*args: ") + make_caster::name; + static constexpr auto name + = io_name("*args: ", "tuple[") + make_caster::name + io_name("", ", ...]"); }; template <> struct handle_type_name { - static constexpr auto name = const_name("**kwargs"); + static constexpr auto name = io_name("**kwargs", "dict[str, typing.Any]"); }; template struct handle_type_name> { - static constexpr auto name = const_name("**kwargs: ") + make_caster::name; + static constexpr auto name + = io_name("**kwargs: ", "dict[str, ") + make_caster::name + io_name("", "]"); }; template <> struct handle_type_name { @@ -1905,13 +1908,20 @@ inline cast_error cast_error_unable_to_convert_call_arg(const std::string &name, } #endif +namespace typing { +template +class Tuple : public tuple { + using tuple::tuple; +}; +} // namespace typing + template -tuple make_tuple() { +typing::Tuple<> make_tuple() { return tuple(0); } template -tuple make_tuple(Args &&...args_) { +typing::Tuple make_tuple(Args &&...args_) { constexpr size_t size = sizeof...(Args); std::array args{{reinterpret_steal( detail::make_caster::cast(std::forward(args_), policy, nullptr))...}}; diff --git a/include/pybind11/detail/init.h b/include/pybind11/detail/init.h index 9589d74d2a..d7c84cb841 100644 --- a/include/pybind11/detail/init.h +++ b/include/pybind11/detail/init.h @@ -501,9 +501,15 @@ template struct pickle_factory { - static_assert(std::is_same, intrinsic_t>::value, - "The type returned by `__getstate__` must be the same " - "as the argument accepted by `__setstate__`"); + using Ret = intrinsic_t; + using Arg = intrinsic_t; + + // Subclasses are now allowed for support between type hint and generic versions of types + // (e.g.) typing::List <--> list + static_assert(std::is_same::value || std::is_base_of::value + || std::is_base_of::value, + "The type returned by `__getstate__` must be the same or subclass of the " + "argument accepted by `__setstate__`"); remove_reference_t get; remove_reference_t set; diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 8ab4681c76..5b4398009c 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -120,7 +120,8 @@ inline std::string generate_function_signature(const char *type_caster_name_fiel const auto c = *pc; if (c == '{') { // Write arg name for everything except *args and **kwargs. - is_starred = *(pc + 1) == '*'; + // Detect {@*args...} or {@**kwargs...} + is_starred = *(pc + 1) == '@' && *(pc + 2) == '*'; if (is_starred) { continue; } @@ -155,7 +156,7 @@ inline std::string generate_function_signature(const char *type_caster_name_fiel } else if (c == '%') { const std::type_info *t = types[type_index++]; if (!t) { - pybind11_fail("Internal error while parsing type signature (1)"); + // pybind11_fail("Internal error while parsing type signature (1)"); } if (auto *tinfo = detail::get_type_info(*t)) { handle th((PyObject *) tinfo->type); diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 1715026efa..43e2187b9e 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -34,10 +34,7 @@ PYBIND11_NAMESPACE_BEGIN(typing) There is no additional enforcement of types at runtime. */ -template -class Tuple : public tuple { - using tuple::tuple; -}; +// Tuple type hint defined in cast.h for use in py::make_tuple to avoid circular includes template class Dict : public dict { diff --git a/tests/test_kwargs_and_defaults.py b/tests/test_kwargs_and_defaults.py index b62e4b7412..4a8c6bd6bb 100644 --- a/tests/test_kwargs_and_defaults.py +++ b/tests/test_kwargs_and_defaults.py @@ -34,11 +34,12 @@ def test_function_signatures(doc): ) assert doc(m.args_function) == "args_function(*args) -> tuple" assert ( - doc(m.args_kwargs_function) == "args_kwargs_function(*args, **kwargs) -> tuple" + doc(m.args_kwargs_function) + == "args_kwargs_function(*args, **kwargs) -> tuple[tuple, dict[str, typing.Any]]" ) assert ( doc(m.args_kwargs_subclass_function) - == "args_kwargs_subclass_function(*args: str, **kwargs: str) -> tuple" + == "args_kwargs_subclass_function(*args: str, **kwargs: str) -> tuple[tuple[str, ...], dict[str, str]]" ) assert ( doc(m.KWClass.foo0) @@ -138,7 +139,7 @@ def test_mixed_args_and_kwargs(msg): msg(excinfo.value) == """ mixed_plus_args(): incompatible function arguments. The following argument types are supported: - 1. (arg0: typing.SupportsInt, arg1: typing.SupportsFloat, *args) -> tuple + 1. (arg0: typing.SupportsInt, arg1: typing.SupportsFloat, *args) -> tuple[int, float, tuple] Invoked with: 1 """ @@ -149,7 +150,7 @@ def test_mixed_args_and_kwargs(msg): msg(excinfo.value) == """ mixed_plus_args(): incompatible function arguments. The following argument types are supported: - 1. (arg0: typing.SupportsInt, arg1: typing.SupportsFloat, *args) -> tuple + 1. (arg0: typing.SupportsInt, arg1: typing.SupportsFloat, *args) -> tuple[int, float, tuple] Invoked with: """ @@ -183,7 +184,7 @@ def test_mixed_args_and_kwargs(msg): msg(excinfo.value) == """ mixed_plus_args_kwargs_defaults(): incompatible function arguments. The following argument types are supported: - 1. (i: typing.SupportsInt = 1, j: typing.SupportsFloat = 3.14159, *args, **kwargs) -> tuple + 1. (i: typing.SupportsInt = 1, j: typing.SupportsFloat = 3.14159, *args, **kwargs) -> tuple[int, float, tuple, dict[str, typing.Any]] Invoked with: 1; kwargs: i=1 """ @@ -194,7 +195,7 @@ def test_mixed_args_and_kwargs(msg): msg(excinfo.value) == """ mixed_plus_args_kwargs_defaults(): incompatible function arguments. The following argument types are supported: - 1. (i: typing.SupportsInt = 1, j: typing.SupportsFloat = 3.14159, *args, **kwargs) -> tuple + 1. (i: typing.SupportsInt = 1, j: typing.SupportsFloat = 3.14159, *args, **kwargs) -> tuple[int, float, tuple, dict[str, typing.Any]] Invoked with: 1, 2; kwargs: j=1 """ @@ -211,7 +212,7 @@ def test_mixed_args_and_kwargs(msg): msg(excinfo.value) == """ args_kwonly(): incompatible function arguments. The following argument types are supported: - 1. (i: typing.SupportsInt, j: typing.SupportsFloat, *args, z: typing.SupportsInt) -> tuple + 1. (i: typing.SupportsInt, j: typing.SupportsFloat, *args, z: typing.SupportsInt) -> tuple[int, float, tuple, int] Invoked with: 2, 2.5, 22 """ @@ -233,12 +234,12 @@ def test_mixed_args_and_kwargs(msg): ) assert ( m.args_kwonly_kwargs.__doc__ - == "args_kwonly_kwargs(i: typing.SupportsInt, j: typing.SupportsFloat, *args, z: typing.SupportsInt, **kwargs) -> tuple\n" + == "args_kwonly_kwargs(i: typing.SupportsInt, j: typing.SupportsFloat, *args, z: typing.SupportsInt, **kwargs) -> tuple[int, float, tuple, int, dict[str, typing.Any]]\n" ) assert ( m.args_kwonly_kwargs_defaults.__doc__ - == "args_kwonly_kwargs_defaults(i: typing.SupportsInt = 1, j: typing.SupportsFloat = 3.14159, *args, z: typing.SupportsInt = 42, **kwargs) -> tuple\n" + == "args_kwonly_kwargs_defaults(i: typing.SupportsInt = 1, j: typing.SupportsFloat = 3.14159, *args, z: typing.SupportsInt = 42, **kwargs) -> tuple[int, float, tuple, int, dict[str, typing.Any]]\n" ) assert m.args_kwonly_kwargs_defaults() == (1, 3.14159, (), 42, {}) assert m.args_kwonly_kwargs_defaults(2) == (2, 3.14159, (), 42, {}) @@ -344,7 +345,7 @@ def test_positional_only_args(): # Mix it with args and kwargs: assert ( m.args_kwonly_full_monty.__doc__ - == "args_kwonly_full_monty(arg0: typing.SupportsInt = 1, arg1: typing.SupportsInt = 2, /, j: typing.SupportsFloat = 3.14159, *args, z: typing.SupportsInt = 42, **kwargs) -> tuple\n" + == "args_kwonly_full_monty(arg0: typing.SupportsInt = 1, arg1: typing.SupportsInt = 2, /, j: typing.SupportsFloat = 3.14159, *args, z: typing.SupportsInt = 42, **kwargs) -> tuple[int, int, float, tuple, int, dict[str, typing.Any]]\n" ) assert m.args_kwonly_full_monty() == (1, 2, 3.14159, (), 42, {}) assert m.args_kwonly_full_monty(8) == (8, 2, 3.14159, (), 42, {}) @@ -394,23 +395,23 @@ def test_positional_only_args(): def test_signatures(): assert ( m.kw_only_all.__doc__ - == "kw_only_all(*, i: typing.SupportsInt, j: typing.SupportsInt) -> tuple\n" + == "kw_only_all(*, i: typing.SupportsInt, j: typing.SupportsInt) -> tuple[int, int]\n" ) assert ( m.kw_only_mixed.__doc__ - == "kw_only_mixed(i: typing.SupportsInt, *, j: typing.SupportsInt) -> tuple\n" + == "kw_only_mixed(i: typing.SupportsInt, *, j: typing.SupportsInt) -> tuple[int, int]\n" ) assert ( m.pos_only_all.__doc__ - == "pos_only_all(i: typing.SupportsInt, j: typing.SupportsInt, /) -> tuple\n" + == "pos_only_all(i: typing.SupportsInt, j: typing.SupportsInt, /) -> tuple[int, int]\n" ) assert ( m.pos_only_mix.__doc__ - == "pos_only_mix(i: typing.SupportsInt, /, j: typing.SupportsInt) -> tuple\n" + == "pos_only_mix(i: typing.SupportsInt, /, j: typing.SupportsInt) -> tuple[int, int]\n" ) assert ( m.pos_kw_only_mix.__doc__ - == "pos_kw_only_mix(i: typing.SupportsInt, /, j: typing.SupportsInt, *, k: typing.SupportsInt) -> tuple\n" + == "pos_kw_only_mix(i: typing.SupportsInt, /, j: typing.SupportsInt, *, k: typing.SupportsInt) -> tuple[int, int, int]\n" ) From 158744841ac0da5b9bbe1f1356372417d10e4a0e Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 25 Oct 2025 17:00:53 -0700 Subject: [PATCH 2/5] add back commented out panic --- include/pybind11/pybind11.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 5b4398009c..5cc9e9e1c8 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -156,7 +156,7 @@ inline std::string generate_function_signature(const char *type_caster_name_fiel } else if (c == '%') { const std::type_info *t = types[type_index++]; if (!t) { - // pybind11_fail("Internal error while parsing type signature (1)"); + pybind11_fail("Internal error while parsing type signature (1)"); } if (auto *tinfo = detail::get_type_info(*t)) { handle th((PyObject *) tinfo->type); From e7b2f2fe7a551d90b1118afaf5032946526f998b Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 25 Oct 2025 17:46:58 -0700 Subject: [PATCH 3/5] ignore return std move clang Signed-off-by: Michael Carlstrom --- include/pybind11/cast.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 1ca3f35c5b..3c6f72abdf 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1940,7 +1940,12 @@ typing::Tuple make_tuple(Args &&...args_) { for (auto &arg_value : args) { PyTuple_SET_ITEM(result.ptr(), counter++, arg_value.release().ptr()); } + PYBIND11_WARNING_PUSH +#ifdef PYBIND11_DETECTED_CLANG_WITH_MISLEADING_CALL_STD_MOVE_EXPLICITLY_WARNING + PYBIND11_WARNING_DISABLE_CLANG("-Wreturn-std-move") +#endif return result; + PYBIND11_WARNING_POP } /// \ingroup annotations From 5ae4342ed37c79a0b356445c52b34053729477a9 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 25 Oct 2025 19:25:30 -0700 Subject: [PATCH 4/5] fix for mingmw Signed-off-by: Michael Carlstrom --- tests/test_factory_constructors.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_factory_constructors.cpp b/tests/test_factory_constructors.cpp index a387cd2e76..c573879ca8 100644 --- a/tests/test_factory_constructors.cpp +++ b/tests/test_factory_constructors.cpp @@ -376,9 +376,9 @@ TEST_SUBMODULE(factory_constructors, m) { py::print("noisy placement new"); return p; } - static void operator delete(void *p, size_t) { + static void operator delete(void *p, size_t s) { py::print("noisy delete"); - ::operator delete(p); + ::operator delete(p, s); } static void operator delete(void *, void *) { py::print("noisy placement delete"); } }; From 00d9f81ddca40d8f8f7514bac12113e1f3f3cd49 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 25 Oct 2025 20:13:14 -0700 Subject: [PATCH 5/5] added missing case Signed-off-by: Michael Carlstrom --- tests/test_factory_constructors.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_factory_constructors.cpp b/tests/test_factory_constructors.cpp index c573879ca8..e50494b33a 100644 --- a/tests/test_factory_constructors.cpp +++ b/tests/test_factory_constructors.cpp @@ -376,9 +376,13 @@ TEST_SUBMODULE(factory_constructors, m) { py::print("noisy placement new"); return p; } - static void operator delete(void *p, size_t s) { + static void operator delete(void *p) noexcept { py::print("noisy delete"); - ::operator delete(p, s); + ::operator delete(p); + } + static void operator delete(void *p, size_t) { + py::print("noisy delete size"); + ::operator delete(p); } static void operator delete(void *, void *) { py::print("noisy placement delete"); } };