mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-25 14:45:12 +00:00
Fix py::kw_only when used before the first arg of a method (#3488)
* Fix py::kw_only when used before the first arg of a method The implicit space for the `self` argument isn't added until we hit the first argument, but this wasn't being done for kw_only or pos_only, and so a kw_only before the first argument would break. This fixes it by properly checking whether we need to add the self arg. (The pos_only issue here was extremely mild -- you didn't get the `/` in the docstring, but AFAICT it has no other effect since there are no meaningful arguments before it anyway). * Style changes - rename check_have_self_arg -> append_self_arg_if_needed - move the argument name inline comments before the args instead of after
This commit is contained in:
parent
56322dafc9
commit
673b4be3d7
@ -414,11 +414,15 @@ inline void check_kw_only_arg(const arg &a, function_record *r) {
|
|||||||
pybind11_fail("arg(): cannot specify an unnamed argument after a kw_only() annotation or args() argument");
|
pybind11_fail("arg(): cannot specify an unnamed argument after a kw_only() annotation or args() argument");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void append_self_arg_if_needed(function_record *r) {
|
||||||
|
if (r->is_method && r->args.empty())
|
||||||
|
r->args.emplace_back("self", nullptr, handle(), /*convert=*/ true, /*none=*/ false);
|
||||||
|
}
|
||||||
|
|
||||||
/// Process a keyword argument attribute (*without* a default value)
|
/// Process a keyword argument attribute (*without* a default value)
|
||||||
template <> struct process_attribute<arg> : process_attribute_default<arg> {
|
template <> struct process_attribute<arg> : process_attribute_default<arg> {
|
||||||
static void init(const arg &a, function_record *r) {
|
static void init(const arg &a, function_record *r) {
|
||||||
if (r->is_method && r->args.empty())
|
append_self_arg_if_needed(r);
|
||||||
r->args.emplace_back("self", nullptr, handle(), true /*convert*/, false /*none not allowed*/);
|
|
||||||
r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert, a.flag_none);
|
r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert, a.flag_none);
|
||||||
|
|
||||||
check_kw_only_arg(a, r);
|
check_kw_only_arg(a, r);
|
||||||
@ -429,7 +433,7 @@ template <> struct process_attribute<arg> : process_attribute_default<arg> {
|
|||||||
template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
|
template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
|
||||||
static void init(const arg_v &a, function_record *r) {
|
static void init(const arg_v &a, function_record *r) {
|
||||||
if (r->is_method && r->args.empty())
|
if (r->is_method && r->args.empty())
|
||||||
r->args.emplace_back("self", nullptr /*descr*/, handle() /*parent*/, true /*convert*/, false /*none not allowed*/);
|
r->args.emplace_back("self", /*descr=*/ nullptr, /*parent=*/ handle(), /*convert=*/ true, /*none=*/ false);
|
||||||
|
|
||||||
if (!a.value) {
|
if (!a.value) {
|
||||||
#if !defined(NDEBUG)
|
#if !defined(NDEBUG)
|
||||||
@ -461,6 +465,7 @@ template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
|
|||||||
/// Process a keyword-only-arguments-follow pseudo argument
|
/// Process a keyword-only-arguments-follow pseudo argument
|
||||||
template <> struct process_attribute<kw_only> : process_attribute_default<kw_only> {
|
template <> struct process_attribute<kw_only> : process_attribute_default<kw_only> {
|
||||||
static void init(const kw_only &, function_record *r) {
|
static void init(const kw_only &, function_record *r) {
|
||||||
|
append_self_arg_if_needed(r);
|
||||||
if (r->has_args && r->nargs_pos != static_cast<std::uint16_t>(r->args.size()))
|
if (r->has_args && r->nargs_pos != static_cast<std::uint16_t>(r->args.size()))
|
||||||
pybind11_fail("Mismatched args() and kw_only(): they must occur at the same relative argument location (or omit kw_only() entirely)");
|
pybind11_fail("Mismatched args() and kw_only(): they must occur at the same relative argument location (or omit kw_only() entirely)");
|
||||||
r->nargs_pos = static_cast<std::uint16_t>(r->args.size());
|
r->nargs_pos = static_cast<std::uint16_t>(r->args.size());
|
||||||
@ -470,6 +475,7 @@ template <> struct process_attribute<kw_only> : process_attribute_default<kw_onl
|
|||||||
/// Process a positional-only-argument maker
|
/// Process a positional-only-argument maker
|
||||||
template <> struct process_attribute<pos_only> : process_attribute_default<pos_only> {
|
template <> struct process_attribute<pos_only> : process_attribute_default<pos_only> {
|
||||||
static void init(const pos_only &, function_record *r) {
|
static void init(const pos_only &, function_record *r) {
|
||||||
|
append_self_arg_if_needed(r);
|
||||||
r->nargs_pos_only = static_cast<std::uint16_t>(r->args.size());
|
r->nargs_pos_only = static_cast<std::uint16_t>(r->args.size());
|
||||||
if (r->nargs_pos_only > r->nargs_pos)
|
if (r->nargs_pos_only > r->nargs_pos)
|
||||||
pybind11_fail("pos_only(): cannot follow a py::args() argument");
|
pybind11_fail("pos_only(): cannot follow a py::args() argument");
|
||||||
|
@ -167,4 +167,21 @@ TEST_SUBMODULE(kwargs_and_defaults, m) {
|
|||||||
"class_default_argument",
|
"class_default_argument",
|
||||||
[](py::object a) { return py::repr(std::move(a)); },
|
[](py::object a) { return py::repr(std::move(a)); },
|
||||||
"a"_a = py::module_::import("decimal").attr("Decimal"));
|
"a"_a = py::module_::import("decimal").attr("Decimal"));
|
||||||
|
|
||||||
|
// Initial implementation of kw_only was broken when used on a method/constructor before any
|
||||||
|
// other arguments
|
||||||
|
// https://github.com/pybind/pybind11/pull/3402#issuecomment-963341987
|
||||||
|
|
||||||
|
struct first_arg_kw_only {};
|
||||||
|
py::class_<first_arg_kw_only>(m, "first_arg_kw_only")
|
||||||
|
.def(py::init([](int) { return first_arg_kw_only(); }),
|
||||||
|
py::kw_only(), // This being before any args was broken
|
||||||
|
py::arg("i") = 0)
|
||||||
|
.def("method", [](first_arg_kw_only&, int, int) {},
|
||||||
|
py::kw_only(), // and likewise here
|
||||||
|
py::arg("i") = 1, py::arg("j") = 2)
|
||||||
|
// Closely related: pos_only marker didn't show up properly when it was before any other
|
||||||
|
// arguments (although that is fairly useless in practice).
|
||||||
|
.def("pos_only", [](first_arg_kw_only&, int, int) {},
|
||||||
|
py::pos_only{}, py::arg("i"), py::arg("j"));
|
||||||
}
|
}
|
||||||
|
@ -229,6 +229,19 @@ def test_keyword_only_args(msg):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# https://github.com/pybind/pybind11/pull/3402#issuecomment-963341987
|
||||||
|
x = m.first_arg_kw_only(i=1)
|
||||||
|
x.method()
|
||||||
|
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" # noqa: E501 line too long
|
||||||
|
)
|
||||||
|
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" # noqa: E501 line too long
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_positional_only_args(msg):
|
def test_positional_only_args(msg):
|
||||||
assert m.pos_only_all(1, 2) == (1, 2)
|
assert m.pos_only_all(1, 2) == (1, 2)
|
||||||
@ -310,6 +323,14 @@ def test_positional_only_args(msg):
|
|||||||
{"i": 5, "m": 8},
|
{"i": 5, "m": 8},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# pos_only at the beginning of the argument list was "broken" in how it was displayed (though
|
||||||
|
# this is fairly useless in practice). Related to:
|
||||||
|
# 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" # noqa: E501 line too long
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_signatures():
|
def test_signatures():
|
||||||
assert "kw_only_all(*, i: int, j: int) -> tuple\n" == m.kw_only_all.__doc__
|
assert "kw_only_all(*, i: int, j: int) -> tuple\n" == m.kw_only_all.__doc__
|
||||||
|
Loading…
Reference in New Issue
Block a user