diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index f2c029113a..225c54d770 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -241,7 +241,9 @@ struct type_caster::value && !is_std_char_t return PyLong_FromUnsignedLongLong((unsigned long long) src); } - PYBIND11_TYPE_CASTER(T, const_name::value>("int", "float")); + PYBIND11_TYPE_CASTER(T, + io_name::value>( + "typing.SupportsInt", "int", "typing.SupportsFloat", "float")); }; template @@ -952,7 +954,7 @@ struct handle_type_name { }; template <> struct handle_type_name { - static constexpr auto name = const_name("int"); + static constexpr auto name = io_name("typing.SupportsInt", "int"); }; template <> struct handle_type_name { @@ -964,7 +966,7 @@ struct handle_type_name { }; template <> struct handle_type_name { - static constexpr auto name = const_name("float"); + static constexpr auto name = io_name("typing.SupportsFloat", "float"); }; template <> struct handle_type_name { @@ -1325,6 +1327,17 @@ inline void object::cast() && { PYBIND11_NAMESPACE_BEGIN(detail) +// forward declaration (definition in attr.h) +struct function_record; + +// forward declaration (definition in pybind11.h) +std::string generate_signature(const char *text, + function_record *rec, + const std::type_info *const *types, + size_t &type_index, + size_t &arg_index, + bool is_annotation); + // Declared in pytypes.h: template ::value, int>> object object_or_cast(T &&o) { @@ -1345,7 +1358,11 @@ str_attr_accessor object_api::attr_with_type_hint(const char *key) const { if (ann.contains(key)) { throw std::runtime_error("__annotations__[\"" + std::string(key) + "\"] was set already."); } - ann[key] = make_caster::name.text; + + const char *text = make_caster::name.text; + + size_t unused = 0; + ann[key] = generate_signature(text, nullptr, nullptr, unused, unused, true); return {derived(), key}; } diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index a5f17f8695..e5f829d2eb 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -106,6 +106,19 @@ constexpr descr io_name(char const (&text1)[N1], char const (&text2 + const_name("@"); } +// Ternary description for io_name (like the numeric type_caster) +template +constexpr enable_if_t> +io_name(char const (&text1)[N1], char const (&text2)[N2], char const (&)[N3], char const (&)[N4]) { + return io_name(text1, text2); +} + +template +constexpr enable_if_t> +io_name(char const (&)[N1], char const (&)[N2], char const (&text3)[N3], char const (&text4)[N4]) { + return io_name(text3, text4); +} + // If "_" is defined as a macro, py::detail::_ cannot be provided. // It is therefore best to use py::detail::const_name universally. // This block is for backward compatibility only. diff --git a/include/pybind11/functional.h b/include/pybind11/functional.h index 4b3610117c..22748f91f6 100644 --- a/include/pybind11/functional.h +++ b/include/pybind11/functional.h @@ -138,11 +138,12 @@ struct type_caster> { return cpp_function(std::forward(f_), policy).release(); } - PYBIND11_TYPE_CASTER(type, - const_name("Callable[[") - + ::pybind11::detail::concat(make_caster::name...) - + const_name("], ") + make_caster::name - + const_name("]")); + PYBIND11_TYPE_CASTER( + type, + const_name("Callable[[") + + ::pybind11::detail::concat(::pybind11::detail::arg_descr(make_caster::name)...) + + const_name("], ") + ::pybind11::detail::return_descr(make_caster::name) + + const_name("]")); }; PYBIND11_NAMESPACE_END(detail) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 8a1739e193..e4a9e60389 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -102,6 +102,132 @@ inline std::string replace_newlines_and_squash(const char *text) { return result.substr(str_begin, str_range); } +/* Generate a proper function signature */ +inline std::string generate_signature(const char *text, + detail::function_record *rec, + const std::type_info *const *types, + size_t &type_index, + size_t &arg_index, + bool is_annotation = false) { + std::string signature; + bool is_starred = false; + // `is_return_value.top()` is true if we are currently inside the return type of the + // signature. Using `@^`/`@$` we can force types to be arg/return types while `@!` pops + // back to the previous state. + std::stack is_return_value({false}); + // The following characters have special meaning in the signature parsing. Literals + // containing these are escaped with `!`. + std::string special_chars("!@%{}-"); + for (const auto *pc = text; *pc != '\0'; ++pc) { + const auto c = *pc; + if (c == '{') { + // Write arg name for everything except *args and **kwargs. + is_starred = *(pc + 1) == '*'; + if (is_starred) { + continue; + } + // Separator for keyword-only arguments, placed before the kw + // arguments start (unless we are already putting an *args) + if (!rec->has_args && arg_index == rec->nargs_pos) { + signature += "*, "; + } + if (arg_index < rec->args.size() && rec->args[arg_index].name) { + signature += rec->args[arg_index].name; + } else if (arg_index == 0 && rec->is_method) { + signature += "self"; + } else { + signature += "arg" + std::to_string(arg_index - (rec->is_method ? 1 : 0)); + } + signature += ": "; + } else if (c == '}') { + // Write default value if available. + if (!is_starred && arg_index < rec->args.size() && rec->args[arg_index].descr) { + signature += " = "; + signature += detail::replace_newlines_and_squash(rec->args[arg_index].descr); + } + // Separator for positional-only arguments (placed after the + // argument, rather than before like * + if (rec->nargs_pos_only > 0 && (arg_index + 1) == rec->nargs_pos_only) { + signature += ", /"; + } + if (!is_starred) { + arg_index++; + } + } else if (c == '%') { + const std::type_info *t = types[type_index++]; + if (!t) { + pybind11_fail("Internal error while parsing type signature (1)"); + } + if (auto *tinfo = detail::get_type_info(*t)) { + handle th((PyObject *) tinfo->type); + signature += th.attr("__module__").cast() + "." + + th.attr("__qualname__").cast(); + } else if (rec->is_new_style_constructor && arg_index == 0) { + // A new-style `__init__` takes `self` as `value_and_holder`. + // Rewrite it to the proper class type. + signature += rec->scope.attr("__module__").cast() + "." + + rec->scope.attr("__qualname__").cast(); + } else { + signature += detail::quote_cpp_type_name(detail::clean_type_id(t->name())); + } + } else if (c == '!' && special_chars.find(*(pc + 1)) != std::string::npos) { + // typing::Literal escapes special characters with ! + signature += *++pc; + } else if (c == '@') { + // `@^ ... @!` and `@$ ... @!` are used to force arg/return value type (see + // typing::Callable/detail::arg_descr/detail::return_descr) + if (*(pc + 1) == '^') { + is_return_value.emplace(false); + ++pc; + continue; + } + if (*(pc + 1) == '$') { + is_return_value.emplace(true); + ++pc; + continue; + } + if (*(pc + 1) == '!') { + is_return_value.pop(); + ++pc; + continue; + } + // Handle types that differ depending on whether they appear + // in an argument or a return value position (see io_name). + // For named arguments (py::arg()) with noconvert set, return value type is used. + ++pc; + if (!is_return_value.top() + && (is_annotation + || !(arg_index < rec->args.size() && !rec->args[arg_index].convert))) { + while (*pc != '\0' && *pc != '@') { + signature += *pc++; + } + if (*pc == '@') { + ++pc; + } + while (*pc != '\0' && *pc != '@') { + ++pc; + } + } else { + while (*pc != '\0' && *pc != '@') { + ++pc; + } + if (*pc == '@') { + ++pc; + } + while (*pc != '\0' && *pc != '@') { + signature += *pc++; + } + } + } else { + if (c == '-' && *(pc + 1) == '>') { + is_return_value.emplace(true); + } + signature += c; + } + } + return signature; +} + #if defined(_MSC_VER) # define PYBIND11_COMPAT_STRDUP _strdup #else @@ -437,124 +563,9 @@ class cpp_function : public function { } #endif - /* Generate a proper function signature */ - std::string signature; size_t type_index = 0, arg_index = 0; - bool is_starred = false; - // `is_return_value.top()` is true if we are currently inside the return type of the - // signature. Using `@^`/`@$` we can force types to be arg/return types while `@!` pops - // back to the previous state. - std::stack is_return_value({false}); - // The following characters have special meaning in the signature parsing. Literals - // containing these are escaped with `!`. - std::string special_chars("!@%{}-"); - for (const auto *pc = text; *pc != '\0'; ++pc) { - const auto c = *pc; - - if (c == '{') { - // Write arg name for everything except *args and **kwargs. - is_starred = *(pc + 1) == '*'; - if (is_starred) { - continue; - } - // Separator for keyword-only arguments, placed before the kw - // arguments start (unless we are already putting an *args) - if (!rec->has_args && arg_index == rec->nargs_pos) { - signature += "*, "; - } - if (arg_index < rec->args.size() && rec->args[arg_index].name) { - signature += rec->args[arg_index].name; - } else if (arg_index == 0 && rec->is_method) { - signature += "self"; - } else { - signature += "arg" + std::to_string(arg_index - (rec->is_method ? 1 : 0)); - } - signature += ": "; - } else if (c == '}') { - // Write default value if available. - if (!is_starred && arg_index < rec->args.size() && rec->args[arg_index].descr) { - signature += " = "; - signature += detail::replace_newlines_and_squash(rec->args[arg_index].descr); - } - // Separator for positional-only arguments (placed after the - // argument, rather than before like * - if (rec->nargs_pos_only > 0 && (arg_index + 1) == rec->nargs_pos_only) { - signature += ", /"; - } - if (!is_starred) { - arg_index++; - } - } else if (c == '%') { - const std::type_info *t = types[type_index++]; - if (!t) { - pybind11_fail("Internal error while parsing type signature (1)"); - } - if (auto *tinfo = detail::get_type_info(*t)) { - handle th((PyObject *) tinfo->type); - signature += th.attr("__module__").cast() + "." - + th.attr("__qualname__").cast(); - } else if (rec->is_new_style_constructor && arg_index == 0) { - // A new-style `__init__` takes `self` as `value_and_holder`. - // Rewrite it to the proper class type. - signature += rec->scope.attr("__module__").cast() + "." - + rec->scope.attr("__qualname__").cast(); - } else { - signature += detail::quote_cpp_type_name(detail::clean_type_id(t->name())); - } - } else if (c == '!' && special_chars.find(*(pc + 1)) != std::string::npos) { - // typing::Literal escapes special characters with ! - signature += *++pc; - } else if (c == '@') { - // `@^ ... @!` and `@$ ... @!` are used to force arg/return value type (see - // typing::Callable/detail::arg_descr/detail::return_descr) - if (*(pc + 1) == '^') { - is_return_value.emplace(false); - ++pc; - continue; - } - if (*(pc + 1) == '$') { - is_return_value.emplace(true); - ++pc; - continue; - } - if (*(pc + 1) == '!') { - is_return_value.pop(); - ++pc; - continue; - } - // Handle types that differ depending on whether they appear - // in an argument or a return value position (see io_name). - // For named arguments (py::arg()) with noconvert set, return value type is used. - ++pc; - if (!is_return_value.top() - && !(arg_index < rec->args.size() && !rec->args[arg_index].convert)) { - while (*pc != '\0' && *pc != '@') { - signature += *pc++; - } - if (*pc == '@') { - ++pc; - } - while (*pc != '\0' && *pc != '@') { - ++pc; - } - } else { - while (*pc != '\0' && *pc != '@') { - ++pc; - } - if (*pc == '@') { - ++pc; - } - while (*pc != '\0' && *pc != '@') { - signature += *pc++; - } - } - } else { - if (c == '-' && *(pc + 1) == '>') { - is_return_value.emplace(true); - } - signature += c; - } - } + std::string signature + = detail::generate_signature(text, rec, types, type_index, arg_index); if (arg_index != args - rec->has_args - rec->has_kwargs || types[type_index] != nullptr) { pybind11_fail("Internal error while parsing type signature (2)"); diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index c5f342aed0..09ff5d801c 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -228,7 +228,9 @@ struct handle_type_name> { template struct handle_type_name> { - static constexpr auto name = const_name("Final[") + make_caster::name + const_name("]"); + static constexpr auto name = const_name("Final[") + + ::pybind11::detail::return_descr(make_caster::name) + + const_name("]"); }; template diff --git a/tests/test_builtin_casters.cpp b/tests/test_builtin_casters.cpp index 292304a63d..c516f8de7e 100644 --- a/tests/test_builtin_casters.cpp +++ b/tests/test_builtin_casters.cpp @@ -236,6 +236,10 @@ TEST_SUBMODULE(builtin_casters, m) { m.def("int_passthrough", [](int arg) { return arg; }); m.def("int_passthrough_noconvert", [](int arg) { return arg; }, py::arg{}.noconvert()); + // test_float_convert + m.def("float_passthrough", [](float arg) { return arg; }); + m.def("float_passthrough_noconvert", [](float arg) { return arg; }, py::arg{}.noconvert()); + // test_tuple m.def( "pair_passthrough", diff --git a/tests/test_builtin_casters.py b/tests/test_builtin_casters.py index 240be85e7e..7a2c6a4d8f 100644 --- a/tests/test_builtin_casters.py +++ b/tests/test_builtin_casters.py @@ -247,7 +247,7 @@ def test_integer_casting(): assert "incompatible function arguments" in str(excinfo.value) -def test_int_convert(): +def test_int_convert(doc): class Int: def __int__(self): return 42 @@ -286,6 +286,9 @@ def __int__(self): convert, noconvert = m.int_passthrough, m.int_passthrough_noconvert + assert doc(convert) == "int_passthrough(arg0: typing.SupportsInt) -> int" + assert doc(noconvert) == "int_passthrough_noconvert(arg0: int) -> int" + def requires_conversion(v): pytest.raises(TypeError, noconvert, v) @@ -318,6 +321,22 @@ def cant_convert(v): requires_conversion(RaisingValueErrorOnIndex()) +def test_float_convert(doc): + class Float: + def __float__(self): + return 41.45 + + convert, noconvert = m.float_passthrough, m.float_passthrough_noconvert + assert doc(convert) == "float_passthrough(arg0: typing.SupportsFloat) -> float" + assert doc(noconvert) == "float_passthrough_noconvert(arg0: float) -> float" + + def requires_conversion(v): + pytest.raises(TypeError, noconvert, v) + + requires_conversion(Float()) + assert pytest.approx(convert(Float())) == 41.45 + + def test_numpy_int_convert(): np = pytest.importorskip("numpy") @@ -362,7 +381,7 @@ def test_tuple(doc): assert ( doc(m.tuple_passthrough) == """ - tuple_passthrough(arg0: tuple[bool, str, int]) -> tuple[int, str, bool] + tuple_passthrough(arg0: tuple[bool, str, typing.SupportsInt]) -> tuple[int, str, bool] Return a triple in reversed order """ diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index 7919ad0ae4..fbe56b9257 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -138,8 +138,14 @@ def test_cpp_function_roundtrip(): def test_function_signatures(doc): - assert doc(m.test_callback3) == "test_callback3(arg0: Callable[[int], int]) -> str" - assert doc(m.test_callback4) == "test_callback4() -> Callable[[int], int]" + assert ( + doc(m.test_callback3) + == "test_callback3(arg0: Callable[[typing.SupportsInt], int]) -> str" + ) + assert ( + doc(m.test_callback4) + == "test_callback4() -> Callable[[typing.SupportsInt], int]" + ) def test_movable_object(): diff --git a/tests/test_class.py b/tests/test_class.py index 01963d0122..c53f926136 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -145,13 +145,13 @@ def test_qualname(doc): assert ( doc(m.NestBase.Nested.fn) == """ - fn(self: m.class_.NestBase.Nested, arg0: int, arg1: m.class_.NestBase, arg2: m.class_.NestBase.Nested) -> None + fn(self: m.class_.NestBase.Nested, arg0: typing.SupportsInt, arg1: m.class_.NestBase, arg2: m.class_.NestBase.Nested) -> None """ ) assert ( doc(m.NestBase.Nested.fa) == """ - fa(self: m.class_.NestBase.Nested, a: int, b: m.class_.NestBase, c: m.class_.NestBase.Nested) -> None + fa(self: m.class_.NestBase.Nested, a: typing.SupportsInt, b: m.class_.NestBase, c: m.class_.NestBase.Nested) -> None """ ) assert m.NestBase.__module__ == "pybind11_tests.class_" diff --git a/tests/test_custom_type_casters.py b/tests/test_custom_type_casters.py index dcf3ca734a..bf31d3f374 100644 --- a/tests/test_custom_type_casters.py +++ b/tests/test_custom_type_casters.py @@ -75,7 +75,7 @@ def test_noconvert_args(msg): msg(excinfo.value) == """ ints_preferred(): incompatible function arguments. The following argument types are supported: - 1. (i: int) -> int + 1. (i: typing.SupportsInt) -> int Invoked with: 4.0 """ diff --git a/tests/test_docstring_options.py b/tests/test_docstring_options.py index 09fc8ac254..f2a10480ca 100644 --- a/tests/test_docstring_options.py +++ b/tests/test_docstring_options.py @@ -19,9 +19,13 @@ def test_docstring_options(): assert m.test_overloaded3.__doc__ == "Overload docstr" # options.enable_function_signatures() - assert m.test_function3.__doc__.startswith("test_function3(a: int, b: int) -> None") + assert m.test_function3.__doc__.startswith( + "test_function3(a: typing.SupportsInt, b: typing.SupportsInt) -> None" + ) - assert m.test_function4.__doc__.startswith("test_function4(a: int, b: int) -> None") + assert m.test_function4.__doc__.startswith( + "test_function4(a: typing.SupportsInt, b: typing.SupportsInt) -> None" + ) assert m.test_function4.__doc__.endswith("A custom docstring\n") # options.disable_function_signatures() @@ -32,7 +36,9 @@ def test_docstring_options(): assert m.test_function6.__doc__ == "A custom docstring" # RAII destructor - assert m.test_function7.__doc__.startswith("test_function7(a: int, b: int) -> None") + assert m.test_function7.__doc__.startswith( + "test_function7(a: typing.SupportsInt, b: typing.SupportsInt) -> None" + ) assert m.test_function7.__doc__.endswith("A custom docstring\n") # when all options are disabled, no docstring (instead of an empty one) should be generated diff --git a/tests/test_factory_constructors.py b/tests/test_factory_constructors.py index 1d3a9bcddd..67f859b9ab 100644 --- a/tests/test_factory_constructors.py +++ b/tests/test_factory_constructors.py @@ -78,10 +78,10 @@ def test_init_factory_signature(msg): msg(excinfo.value) == """ __init__(): incompatible constructor arguments. The following argument types are supported: - 1. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int) + 1. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: typing.SupportsInt) 2. m.factory_constructors.TestFactory1(arg0: str) 3. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.pointer_tag) - 4. m.factory_constructors.TestFactory1(arg0: object, arg1: int, arg2: object) + 4. m.factory_constructors.TestFactory1(arg0: object, arg1: typing.SupportsInt, arg2: object) Invoked with: 'invalid', 'constructor', 'arguments' """ @@ -93,13 +93,13 @@ def test_init_factory_signature(msg): __init__(*args, **kwargs) Overloaded function. - 1. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int) -> None + 1. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: typing.SupportsInt) -> None 2. __init__(self: m.factory_constructors.TestFactory1, arg0: str) -> None 3. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.pointer_tag) -> None - 4. __init__(self: m.factory_constructors.TestFactory1, arg0: object, arg1: int, arg2: object) -> None + 4. __init__(self: m.factory_constructors.TestFactory1, arg0: object, arg1: typing.SupportsInt, arg2: object) -> None """ ) diff --git a/tests/test_kwargs_and_defaults.py b/tests/test_kwargs_and_defaults.py index e558d8ad2a..a8e19f15bb 100644 --- a/tests/test_kwargs_and_defaults.py +++ b/tests/test_kwargs_and_defaults.py @@ -7,13 +7,31 @@ def test_function_signatures(doc): - assert doc(m.kw_func0) == "kw_func0(arg0: int, arg1: int) -> str" - assert doc(m.kw_func1) == "kw_func1(x: int, y: int) -> str" - assert doc(m.kw_func2) == "kw_func2(x: int = 100, y: int = 200) -> str" + assert ( + doc(m.kw_func0) + == "kw_func0(arg0: typing.SupportsInt, arg1: typing.SupportsInt) -> str" + ) + assert ( + doc(m.kw_func1) + == "kw_func1(x: typing.SupportsInt, y: typing.SupportsInt) -> str" + ) + assert ( + doc(m.kw_func2) + == "kw_func2(x: typing.SupportsInt = 100, y: typing.SupportsInt = 200) -> str" + ) assert doc(m.kw_func3) == "kw_func3(data: str = 'Hello world!') -> None" - assert doc(m.kw_func4) == "kw_func4(myList: list[int] = [13, 17]) -> str" - assert doc(m.kw_func_udl) == "kw_func_udl(x: int, y: int = 300) -> str" - assert doc(m.kw_func_udl_z) == "kw_func_udl_z(x: int, y: int = 0) -> str" + assert ( + doc(m.kw_func4) + == "kw_func4(myList: list[typing.SupportsInt] = [13, 17]) -> str" + ) + assert ( + doc(m.kw_func_udl) + == "kw_func_udl(x: typing.SupportsInt, y: typing.SupportsInt = 300) -> str" + ) + assert ( + doc(m.kw_func_udl_z) + == "kw_func_udl_z(x: typing.SupportsInt, y: typing.SupportsInt = 0) -> str" + ) assert doc(m.args_function) == "args_function(*args) -> tuple" assert ( doc(m.args_kwargs_function) == "args_kwargs_function(*args, **kwargs) -> tuple" @@ -24,11 +42,11 @@ def test_function_signatures(doc): ) assert ( doc(m.KWClass.foo0) - == "foo0(self: m.kwargs_and_defaults.KWClass, arg0: int, arg1: float) -> None" + == "foo0(self: m.kwargs_and_defaults.KWClass, arg0: typing.SupportsInt, arg1: typing.SupportsFloat) -> None" ) assert ( doc(m.KWClass.foo1) - == "foo1(self: m.kwargs_and_defaults.KWClass, x: int, y: float) -> None" + == "foo1(self: m.kwargs_and_defaults.KWClass, x: typing.SupportsInt, y: typing.SupportsFloat) -> None" ) assert ( doc(m.kw_lb_func0) @@ -120,7 +138,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: int, arg1: float, *args) -> tuple + 1. (arg0: typing.SupportsInt, arg1: typing.SupportsFloat, *args) -> tuple Invoked with: 1 """ @@ -131,7 +149,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: int, arg1: float, *args) -> tuple + 1. (arg0: typing.SupportsInt, arg1: typing.SupportsFloat, *args) -> tuple Invoked with: """ @@ -165,7 +183,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: int = 1, j: float = 3.14159, *args, **kwargs) -> tuple + 1. (i: typing.SupportsInt = 1, j: typing.SupportsFloat = 3.14159, *args, **kwargs) -> tuple Invoked with: 1; kwargs: i=1 """ @@ -176,7 +194,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: int = 1, j: float = 3.14159, *args, **kwargs) -> tuple + 1. (i: typing.SupportsInt = 1, j: typing.SupportsFloat = 3.14159, *args, **kwargs) -> tuple Invoked with: 1, 2; kwargs: j=1 """ @@ -193,7 +211,7 @@ def test_mixed_args_and_kwargs(msg): msg(excinfo.value) == """ args_kwonly(): incompatible function arguments. The following argument types are supported: - 1. (i: int, j: float, *args, z: int) -> tuple + 1. (i: typing.SupportsInt, j: typing.SupportsFloat, *args, z: typing.SupportsInt) -> tuple Invoked with: 2, 2.5, 22 """ @@ -215,12 +233,12 @@ def test_mixed_args_and_kwargs(msg): ) assert ( m.args_kwonly_kwargs.__doc__ - == "args_kwonly_kwargs(i: int, j: float, *args, z: int, **kwargs) -> tuple\n" + == "args_kwonly_kwargs(i: typing.SupportsInt, j: typing.SupportsFloat, *args, z: typing.SupportsInt, **kwargs) -> tuple\n" ) assert ( m.args_kwonly_kwargs_defaults.__doc__ - == "args_kwonly_kwargs_defaults(i: int = 1, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n" + == "args_kwonly_kwargs_defaults(i: typing.SupportsInt = 1, j: typing.SupportsFloat = 3.14159, *args, z: typing.SupportsInt = 42, **kwargs) -> tuple\n" ) assert m.args_kwonly_kwargs_defaults() == (1, 3.14159, (), 42, {}) assert m.args_kwonly_kwargs_defaults(2) == (2, 3.14159, (), 42, {}) @@ -276,11 +294,11 @@ def test_keyword_only_args(msg): x.method(i=1, j=2) assert ( m.first_arg_kw_only.__init__.__doc__ - == "__init__(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 0) -> None\n" + == "__init__(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: typing.SupportsInt = 0) -> None\n" ) assert ( m.first_arg_kw_only.method.__doc__ - == "method(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 1, j: int = 2) -> None\n" + == "method(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: typing.SupportsInt = 1, j: typing.SupportsInt = 2) -> None\n" ) @@ -326,7 +344,7 @@ def test_positional_only_args(): # Mix it with args and kwargs: assert ( m.args_kwonly_full_monty.__doc__ - == "args_kwonly_full_monty(arg0: int = 1, arg1: int = 2, /, j: float = 3.14159, *args, z: int = 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\n" ) assert m.args_kwonly_full_monty() == (1, 2, 3.14159, (), 42, {}) assert m.args_kwonly_full_monty(8) == (8, 2, 3.14159, (), 42, {}) @@ -369,18 +387,30 @@ def test_positional_only_args(): # https://github.com/pybind/pybind11/pull/3402#issuecomment-963341987 assert ( m.first_arg_kw_only.pos_only.__doc__ - == "pos_only(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, /, i: int, j: int) -> None\n" + == "pos_only(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, /, i: typing.SupportsInt, j: typing.SupportsInt) -> None\n" ) def test_signatures(): - assert m.kw_only_all.__doc__ == "kw_only_all(*, i: int, j: int) -> tuple\n" - assert m.kw_only_mixed.__doc__ == "kw_only_mixed(i: int, *, j: int) -> tuple\n" - assert m.pos_only_all.__doc__ == "pos_only_all(i: int, j: int, /) -> tuple\n" - assert m.pos_only_mix.__doc__ == "pos_only_mix(i: int, /, j: int) -> tuple\n" + assert ( + m.kw_only_all.__doc__ + == "kw_only_all(*, i: typing.SupportsInt, j: typing.SupportsInt) -> tuple\n" + ) + assert ( + m.kw_only_mixed.__doc__ + == "kw_only_mixed(i: typing.SupportsInt, *, j: typing.SupportsInt) -> tuple\n" + ) + assert ( + m.pos_only_all.__doc__ + == "pos_only_all(i: typing.SupportsInt, j: typing.SupportsInt, /) -> tuple\n" + ) + assert ( + m.pos_only_mix.__doc__ + == "pos_only_mix(i: typing.SupportsInt, /, j: typing.SupportsInt) -> tuple\n" + ) assert ( m.pos_kw_only_mix.__doc__ - == "pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple\n" + == "pos_kw_only_mix(i: typing.SupportsInt, /, j: typing.SupportsInt, *, k: typing.SupportsInt) -> tuple\n" ) diff --git a/tests/test_methods_and_attributes.py b/tests/test_methods_and_attributes.py index cecc184647..9dc28ffdaa 100644 --- a/tests/test_methods_and_attributes.py +++ b/tests/test_methods_and_attributes.py @@ -251,7 +251,7 @@ def test_no_mixed_overloads(): "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more details" if not detailed_error_messages_enabled else "error while attempting to bind static method ExampleMandA.overload_mixed1" - "(arg0: float) -> str" + "(arg0: typing.SupportsFloat) -> str" ) ) @@ -264,7 +264,7 @@ def test_no_mixed_overloads(): "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more details" if not detailed_error_messages_enabled else "error while attempting to bind instance method ExampleMandA.overload_mixed2" - "(self: pybind11_tests.methods_and_attributes.ExampleMandA, arg0: int, arg1: int)" + "(self: pybind11_tests.methods_and_attributes.ExampleMandA, arg0: typing.SupportsInt, arg1: typing.SupportsInt)" " -> str" ) ) @@ -479,7 +479,7 @@ def test_str_issue(msg): msg(excinfo.value) == """ __init__(): incompatible constructor arguments. The following argument types are supported: - 1. m.methods_and_attributes.StrIssue(arg0: int) + 1. m.methods_and_attributes.StrIssue(arg0: typing.SupportsInt) 2. m.methods_and_attributes.StrIssue() Invoked with: 'no', 'such', 'constructor' @@ -521,18 +521,22 @@ def test_overload_ordering(): assert m.overload_order("string") == 1 assert m.overload_order(0) == 4 - assert "1. overload_order(arg0: int) -> int" in m.overload_order.__doc__ + assert ( + "1. overload_order(arg0: typing.SupportsInt) -> int" in m.overload_order.__doc__ + ) assert "2. overload_order(arg0: str) -> int" in m.overload_order.__doc__ assert "3. overload_order(arg0: str) -> int" in m.overload_order.__doc__ - assert "4. overload_order(arg0: int) -> int" in m.overload_order.__doc__ + assert ( + "4. overload_order(arg0: typing.SupportsInt) -> int" in m.overload_order.__doc__ + ) with pytest.raises(TypeError) as err: m.overload_order(1.1) - assert "1. (arg0: int) -> int" in str(err.value) + assert "1. (arg0: typing.SupportsInt) -> int" in str(err.value) assert "2. (arg0: str) -> int" in str(err.value) assert "3. (arg0: str) -> int" in str(err.value) - assert "4. (arg0: int) -> int" in str(err.value) + assert "4. (arg0: typing.SupportsInt) -> int" in str(err.value) def test_rvalue_ref_param(): diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py index 685a76fd35..c9940b90c5 100644 --- a/tests/test_numpy_dtypes.py +++ b/tests/test_numpy_dtypes.py @@ -373,7 +373,7 @@ def test_complex_array(): def test_signature(doc): assert ( doc(m.create_rec_nested) - == "create_rec_nested(arg0: int) -> numpy.typing.NDArray[NestedStruct]" + == "create_rec_nested(arg0: typing.SupportsInt) -> numpy.typing.NDArray[NestedStruct]" ) diff --git a/tests/test_numpy_vectorize.py b/tests/test_numpy_vectorize.py index 0768759d15..d405e68002 100644 --- a/tests/test_numpy_vectorize.py +++ b/tests/test_numpy_vectorize.py @@ -211,11 +211,11 @@ def test_passthrough_arguments(doc): "vec_passthrough(" + ", ".join( [ - "arg0: float", + "arg0: typing.SupportsFloat", "arg1: typing.Annotated[numpy.typing.ArrayLike, numpy.float64]", "arg2: typing.Annotated[numpy.typing.ArrayLike, numpy.float64]", "arg3: typing.Annotated[numpy.typing.ArrayLike, numpy.int32]", - "arg4: int", + "arg4: typing.SupportsInt", "arg5: m.numpy_vectorize.NonPODClass", "arg6: typing.Annotated[numpy.typing.ArrayLike, numpy.float64]", ] diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 18932311e7..deb4b06d41 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -917,7 +917,7 @@ def test_inplace_rshift(a, b): def test_tuple_nonempty_annotations(doc): assert ( doc(m.annotate_tuple_float_str) - == "annotate_tuple_float_str(arg0: tuple[float, str]) -> None" + == "annotate_tuple_float_str(arg0: tuple[typing.SupportsFloat, str]) -> None" ) @@ -930,19 +930,22 @@ def test_tuple_empty_annotations(doc): def test_tuple_variable_length_annotations(doc): assert ( doc(m.annotate_tuple_variable_length) - == "annotate_tuple_variable_length(arg0: tuple[float, ...]) -> None" + == "annotate_tuple_variable_length(arg0: tuple[typing.SupportsFloat, ...]) -> None" ) def test_dict_annotations(doc): assert ( doc(m.annotate_dict_str_int) - == "annotate_dict_str_int(arg0: dict[str, int]) -> None" + == "annotate_dict_str_int(arg0: dict[str, typing.SupportsInt]) -> None" ) def test_list_annotations(doc): - assert doc(m.annotate_list_int) == "annotate_list_int(arg0: list[int]) -> None" + assert ( + doc(m.annotate_list_int) + == "annotate_list_int(arg0: list[typing.SupportsInt]) -> None" + ) def test_set_annotations(doc): @@ -959,7 +962,7 @@ def test_iterable_annotations(doc): def test_iterator_annotations(doc): assert ( doc(m.annotate_iterator_int) - == "annotate_iterator_int(arg0: Iterator[int]) -> None" + == "annotate_iterator_int(arg0: Iterator[typing.SupportsInt]) -> None" ) @@ -978,13 +981,15 @@ def test_fn_return_only(doc): def test_type_annotation(doc): - assert doc(m.annotate_type) == "annotate_type(arg0: type[int]) -> type" + assert ( + doc(m.annotate_type) == "annotate_type(arg0: type[typing.SupportsInt]) -> type" + ) def test_union_annotations(doc): assert ( doc(m.annotate_union) - == "annotate_union(arg0: list[Union[str, int, object]], arg1: str, arg2: int, arg3: object) -> list[Union[str, int, object]]" + == "annotate_union(arg0: list[Union[str, typing.SupportsInt, object]], arg1: str, arg2: typing.SupportsInt, arg3: object) -> list[Union[str, int, object]]" ) @@ -998,7 +1003,7 @@ def test_union_typing_only(doc): def test_union_object_annotations(doc): assert ( doc(m.annotate_union_to_object) - == "annotate_union_to_object(arg0: Union[int, str]) -> object" + == "annotate_union_to_object(arg0: Union[typing.SupportsInt, str]) -> object" ) @@ -1031,7 +1036,7 @@ def test_never_annotation(doc): def test_optional_object_annotations(doc): assert ( doc(m.annotate_optional_to_object) - == "annotate_optional_to_object(arg0: Optional[int]) -> object" + == "annotate_optional_to_object(arg0: Optional[typing.SupportsInt]) -> object" ) @@ -1150,7 +1155,7 @@ def get_annotations_helper(o): def test_module_attribute_types() -> None: module_annotations = get_annotations_helper(m) - assert module_annotations["list_int"] == "list[int]" + assert module_annotations["list_int"] == "list[typing.SupportsInt]" assert module_annotations["set_str"] == "set[str]" @@ -1167,7 +1172,7 @@ def test_get_annotations_compliance() -> None: module_annotations = get_annotations(m) - assert module_annotations["list_int"] == "list[int]" + assert module_annotations["list_int"] == "list[typing.SupportsInt]" assert module_annotations["set_str"] == "set[str]" @@ -1181,8 +1186,10 @@ def test_class_attribute_types() -> None: instance_annotations = get_annotations_helper(m.Instance) assert empty_annotations is None - assert static_annotations["x"] == "ClassVar[float]" - assert static_annotations["dict_str_int"] == "ClassVar[dict[str, int]]" + assert static_annotations["x"] == "ClassVar[typing.SupportsFloat]" + assert ( + static_annotations["dict_str_int"] == "ClassVar[dict[str, typing.SupportsInt]]" + ) assert m.Static.x == 1.0 @@ -1193,7 +1200,7 @@ def test_class_attribute_types() -> None: static.dict_str_int["hi"] = 3 assert m.Static().dict_str_int == {"hi": 3} - assert instance_annotations["y"] == "float" + assert instance_annotations["y"] == "typing.SupportsFloat" instance1 = m.Instance() instance1.y = 4.0 @@ -1210,7 +1217,7 @@ def test_class_attribute_types() -> None: def test_redeclaration_attr_with_type_hint() -> None: obj = m.Instance() m.attr_with_type_hint_float_x(obj) - assert get_annotations_helper(obj)["x"] == "float" + assert get_annotations_helper(obj)["x"] == "typing.SupportsFloat" with pytest.raises( RuntimeError, match=r'^__annotations__\["x"\] was set already\.$' ): diff --git a/tests/test_stl.py b/tests/test_stl.py index f2ff727d97..29a6bf119f 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -20,7 +20,7 @@ def test_vector(doc): assert m.load_bool_vector((True, False)) assert doc(m.cast_vector) == "cast_vector() -> list[int]" - assert doc(m.load_vector) == "load_vector(arg0: list[int]) -> bool" + assert doc(m.load_vector) == "load_vector(arg0: list[typing.SupportsInt]) -> bool" # Test regression caused by 936: pointers to stl containers weren't castable assert m.cast_ptr_vector() == ["lvalue", "lvalue"] @@ -45,7 +45,7 @@ def test_array(doc): assert doc(m.cast_array) == "cast_array() -> Annotated[list[int], FixedSize(2)]" assert ( doc(m.load_array) - == "load_array(arg0: Annotated[list[int], FixedSize(2)]) -> bool" + == "load_array(arg0: Annotated[list[typing.SupportsInt], FixedSize(2)]) -> bool" ) @@ -64,7 +64,9 @@ def test_valarray(doc): assert m.load_valarray(tuple(lst)) assert doc(m.cast_valarray) == "cast_valarray() -> list[int]" - assert doc(m.load_valarray) == "load_valarray(arg0: list[int]) -> bool" + assert ( + doc(m.load_valarray) == "load_valarray(arg0: list[typing.SupportsInt]) -> bool" + ) def test_map(doc): @@ -325,7 +327,8 @@ def test_variant(doc): assert m.cast_variant() == (5, "Hello") assert ( - doc(m.load_variant) == "load_variant(arg0: Union[int, str, float, None]) -> str" + doc(m.load_variant) + == "load_variant(arg0: Union[typing.SupportsInt, str, typing.SupportsFloat, None]) -> str" ) @@ -341,7 +344,7 @@ def test_variant_monostate(doc): assert ( doc(m.load_monostate_variant) - == "load_monostate_variant(arg0: Union[None, int, str]) -> str" + == "load_monostate_variant(arg0: Union[None, typing.SupportsInt, str]) -> str" ) @@ -361,7 +364,7 @@ def test_stl_pass_by_pointer(msg): msg(excinfo.value) == """ stl_pass_by_pointer(): incompatible function arguments. The following argument types are supported: - 1. (v: list[int] = None) -> list[int] + 1. (v: list[typing.SupportsInt] = None) -> list[int] Invoked with: """ @@ -373,7 +376,7 @@ def test_stl_pass_by_pointer(msg): msg(excinfo.value) == """ stl_pass_by_pointer(): incompatible function arguments. The following argument types are supported: - 1. (v: list[int] = None) -> list[int] + 1. (v: list[typing.SupportsInt] = None) -> list[int] Invoked with: None """ diff --git a/tests/test_type_caster_pyobject_ptr.py b/tests/test_type_caster_pyobject_ptr.py index ebc24e3182..5df8ca0196 100644 --- a/tests/test_type_caster_pyobject_ptr.py +++ b/tests/test_type_caster_pyobject_ptr.py @@ -102,7 +102,9 @@ def test_return_list_pyobject_ptr_reference(): def test_type_caster_name_via_incompatible_function_arguments_type_error(): - with pytest.raises(TypeError, match=r"1\. \(arg0: object, arg1: int\) -> None"): + with pytest.raises( + TypeError, match=r"1\. \(arg0: object, arg1: typing.SupportsInt\) -> None" + ): m.pass_pyobject_ptr_and_int(ValueHolder(101), ValueHolder(202))