feat(types): Use typing.SupportsInt and typing.SupportsFloat and fix other typing based bugs. (#5540)

* init

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* remove import

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* remove uneeded function

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* style: pre-commit fixes

* Add missing import

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* style: pre-commit fixes

* Fix type behind detailed_message_enabled flag

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Fix type behind detailed_message_enabled flag

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Add io_name comment

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Extra loops to single function

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* style: pre-commit fixes

* Remove unneeded forward declaration

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Switch variable name away from macro

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Switch variable name away from macro

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* Switch variable name away from macro

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* clang-tidy

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* remove stack import

* Fix bug in std::function Callable type

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* style: pre-commit fixes

* remove is_annotation argument

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* style: pre-commit fixes

* Update function name and arg names

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>

* style: pre-commit fixes

---------

Signed-off-by: Michael Carlstrom <rmc@carlstrom.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Michael Carlstrom 2025-03-18 07:56:34 -07:00 committed by GitHub
parent 16b5abd428
commit dfe7e65b45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 324 additions and 198 deletions

View File

@ -241,7 +241,9 @@ public:
return PyLong_FromUnsignedLongLong((unsigned long long) src);
}
PYBIND11_TYPE_CASTER(T, const_name<std::is_integral<T>::value>("int", "float"));
PYBIND11_TYPE_CASTER(T,
io_name<std::is_integral<T>::value>(
"typing.SupportsInt", "int", "typing.SupportsFloat", "float"));
};
template <typename T>
@ -1231,7 +1233,7 @@ struct handle_type_name<buffer> {
};
template <>
struct handle_type_name<int_> {
static constexpr auto name = const_name("int");
static constexpr auto name = io_name("typing.SupportsInt", "int");
};
template <>
struct handle_type_name<iterable> {
@ -1243,7 +1245,7 @@ struct handle_type_name<iterator> {
};
template <>
struct handle_type_name<float_> {
static constexpr auto name = const_name("float");
static constexpr auto name = io_name("typing.SupportsFloat", "float");
};
template <>
struct handle_type_name<function> {
@ -1604,6 +1606,16 @@ 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_function_signature(const char *type_caster_name_field,
function_record *func_rec,
const std::type_info *const *types,
size_t &type_index,
size_t &arg_index);
// Declared in pytypes.h:
template <typename T, enable_if_t<!is_pyobject<T>::value, int>>
object object_or_cast(T &&o) {
@ -1624,7 +1636,11 @@ str_attr_accessor object_api<D>::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<T>::name.text;
const char *text = make_caster<T>::name.text;
size_t unused = 0;
ann[key] = generate_function_signature(text, nullptr, nullptr, unused, unused);
return {derived(), key};
}

View File

@ -106,6 +106,19 @@ constexpr descr<N1 + N2 + 1> io_name(char const (&text1)[N1], char const (&text2
+ const_name("@");
}
// Ternary description for io_name (like the numeric type_caster)
template <bool B, size_t N1, size_t N2, size_t N3, size_t N4>
constexpr enable_if_t<B, descr<N1 + N2 + 1>>
io_name(char const (&text1)[N1], char const (&text2)[N2], char const (&)[N3], char const (&)[N4]) {
return io_name(text1, text2);
}
template <bool B, size_t N1, size_t N2, size_t N3, size_t N4>
constexpr enable_if_t<!B, descr<N3 + N4 + 1>>
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.

View File

@ -138,11 +138,12 @@ public:
return cpp_function(std::forward<Func>(f_), policy).release();
}
PYBIND11_TYPE_CASTER(type,
const_name("Callable[[")
+ ::pybind11::detail::concat(make_caster<Args>::name...)
+ const_name("], ") + make_caster<retval_type>::name
+ const_name("]"));
PYBIND11_TYPE_CASTER(
type,
const_name("Callable[[")
+ ::pybind11::detail::concat(::pybind11::detail::arg_descr(make_caster<Args>::name)...)
+ const_name("], ") + ::pybind11::detail::return_descr(make_caster<retval_type>::name)
+ const_name("]"));
};
PYBIND11_NAMESPACE_END(detail)

View File

@ -104,6 +104,134 @@ 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_function_signature(const char *type_caster_name_field,
detail::function_record *func_rec,
const std::type_info *const *types,
size_t &type_index,
size_t &arg_index) {
std::string signature;
bool is_starred = false;
bool is_annotation = func_rec == nullptr;
// `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<bool> 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 = type_caster_name_field; *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 (!func_rec->has_args && arg_index == func_rec->nargs_pos) {
signature += "*, ";
}
if (arg_index < func_rec->args.size() && func_rec->args[arg_index].name) {
signature += func_rec->args[arg_index].name;
} else if (arg_index == 0 && func_rec->is_method) {
signature += "self";
} else {
signature += "arg" + std::to_string(arg_index - (func_rec->is_method ? 1 : 0));
}
signature += ": ";
} else if (c == '}') {
// Write default value if available.
if (!is_starred && arg_index < func_rec->args.size()
&& func_rec->args[arg_index].descr) {
signature += " = ";
signature += detail::replace_newlines_and_squash(func_rec->args[arg_index].descr);
}
// Separator for positional-only arguments (placed after the
// argument, rather than before like *
if (func_rec->nargs_pos_only > 0 && (arg_index + 1) == func_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<std::string>() + "."
+ th.attr("__qualname__").cast<std::string>();
} else if (func_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 += func_rec->scope.attr("__module__").cast<std::string>() + "."
+ func_rec->scope.attr("__qualname__").cast<std::string>();
} 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<text1, text2>).
// For named arguments (py::arg()) with noconvert set, return value type is used.
++pc;
if (!is_return_value.top()
&& (is_annotation
|| !(arg_index < func_rec->args.size()
&& !func_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
@ -439,124 +567,9 @@ protected:
}
#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<bool> 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<std::string>() + "."
+ th.attr("__qualname__").cast<std::string>();
} 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<std::string>() + "."
+ rec->scope.attr("__qualname__").cast<std::string>();
} 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<text1, text2>).
// 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_function_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)");

View File

@ -228,7 +228,9 @@ struct handle_type_name<typing::Optional<T>> {
template <typename T>
struct handle_type_name<typing::Final<T>> {
static constexpr auto name = const_name("Final[") + make_caster<T>::name + const_name("]");
static constexpr auto name = const_name("Final[")
+ ::pybind11::detail::return_descr(make_caster<T>::name)
+ const_name("]");
};
template <typename T>

View File

@ -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",

View File

@ -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 test_int_convert():
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 test_int_convert():
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
"""

View File

@ -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():

View File

@ -155,13 +155,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_"

View File

@ -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
"""

View File

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

View File

@ -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
"""
)

View File

@ -7,13 +7,31 @@ from pybind11_tests import kwargs_and_defaults as m
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"
)

View File

@ -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():

View File

@ -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]"
)

View File

@ -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]",
]

View File

@ -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\.$'
):

View File

@ -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
"""

View File

@ -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))