Merge branch 'master' into sh_merge_master

This commit is contained in:
Ralf W. Grosse-Kunstleve 2021-11-07 16:26:55 -08:00
commit 58c7f076bc
12 changed files with 220 additions and 52 deletions

View File

@ -45,7 +45,7 @@ repos:
# Black, the code formatter, natively supports pre-commit # Black, the code formatter, natively supports pre-commit
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 21.9b0 # Keep in sync with blacken-docs rev: 21.10b0 # Keep in sync with blacken-docs
hooks: hooks:
- id: black - id: black
@ -54,7 +54,7 @@ repos:
hooks: hooks:
- id: blacken-docs - id: blacken-docs
additional_dependencies: additional_dependencies:
- black==21.9b0 # keep in sync with black hook - black==21.10b0 # keep in sync with black hook
# Changes tabs to spaces # Changes tabs to spaces
- repo: https://github.com/Lucas-C/pre-commit-hooks - repo: https://github.com/Lucas-C/pre-commit-hooks

View File

@ -306,8 +306,9 @@ The class ``py::args`` derives from ``py::tuple`` and ``py::kwargs`` derives
from ``py::dict``. from ``py::dict``.
You may also use just one or the other, and may combine these with other You may also use just one or the other, and may combine these with other
arguments as long as the ``py::args`` and ``py::kwargs`` arguments are the last arguments. Note, however, that ``py::kwargs`` must always be the last argument
arguments accepted by the function. of the function, and ``py::args`` implies that any further arguments are
keyword-only (see :ref:`keyword_only_arguments`).
Please refer to the other examples for details on how to iterate over these, Please refer to the other examples for details on how to iterate over these,
and on how to cast their entries into C++ objects. A demonstration is also and on how to cast their entries into C++ objects. A demonstration is also
@ -366,6 +367,8 @@ like so:
py::class_<MyClass>("MyClass") py::class_<MyClass>("MyClass")
.def("myFunction", py::arg("arg") = static_cast<SomeType *>(nullptr)); .def("myFunction", py::arg("arg") = static_cast<SomeType *>(nullptr));
.. _keyword_only_arguments:
Keyword-only arguments Keyword-only arguments
====================== ======================
@ -397,6 +400,15 @@ feature does *not* require Python 3 to work.
.. versionadded:: 2.6 .. versionadded:: 2.6
As of pybind11 2.9, a ``py::args`` argument implies that any following arguments
are keyword-only, as if ``py::kw_only()`` had been specified in the same
relative location of the argument list as the ``py::args`` argument. The
``py::kw_only()`` may be included to be explicit about this, but is not
required. (Prior to 2.9 ``py::args`` may only occur at the end of the argument
list, or immediately before a ``py::kwargs`` argument at the end).
.. versionadded:: 2.9
Positional-only arguments Positional-only arguments
========================= =========================

View File

@ -109,7 +109,7 @@ a file named :file:`example.cpp` with the following contents:
PYBIND11_MODULE(example, m) { PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example plugin"; // optional module docstring m.doc() = "pybind11 example plugin"; // optional module docstring
m.def("add", &add, "A function which adds two numbers"); m.def("add", &add, "A function that adds two numbers");
} }
.. [#f1] In practice, implementation and binding code will generally be located .. [#f1] In practice, implementation and binding code will generally be located

View File

@ -22,6 +22,9 @@ the version just below.
To release a new version of pybind11: To release a new version of pybind11:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you don't have nox, you should either use ``pipx run nox`` instead, or use
``pipx install nox`` or ``brew install nox`` (Unix).
- Update the version number - Update the version number
- Update ``PYBIND11_VERSION_MAJOR`` etc. in - Update ``PYBIND11_VERSION_MAJOR`` etc. in
``include/pybind11/detail/common.h``. PATCH should be a simple integer. ``include/pybind11/detail/common.h``. PATCH should be a simple integer.
@ -51,14 +54,12 @@ To release a new version of pybind11:
notifications to users watching releases, and also uploads PyPI packages). notifications to users watching releases, and also uploads PyPI packages).
(Note: if you do not use an existing tag, this creates a new lightweight tag (Note: if you do not use an existing tag, this creates a new lightweight tag
for you, so you could skip the above step.) for you, so you could skip the above step.)
- GUI method: Under `releases <https://github.com/pybind/pybind11/releases>`_ - GUI method: Under `releases <https://github.com/pybind/pybind11/releases>`_
click "Draft a new release" on the far right, fill in the tag name click "Draft a new release" on the far right, fill in the tag name
(if you didn't tag above, it will be made here), fill in a release name (if you didn't tag above, it will be made here), fill in a release name
like "Version X.Y.Z", and copy-and-paste the markdown-formatted (!) changelog like "Version X.Y.Z", and copy-and-paste the markdown-formatted (!) changelog
into the description (usually ``cat docs/changelog.rst | pandoc -f rst -t gfm``). into the description (usually ``cat docs/changelog.rst | pandoc -f rst -t gfm``).
Check "pre-release" if this is a beta/RC. Check "pre-release" if this is a beta/RC.
- CLI method: with ``gh`` installed, run ``gh release create vX.Y.Z -t "Version X.Y.Z"`` - CLI method: with ``gh`` installed, run ``gh release create vX.Y.Z -t "Version X.Y.Z"``
If this is a pre-release, add ``-p``. If this is a pre-release, add ``-p``.
@ -90,9 +91,7 @@ If you need to manually upload releases, you can download the releases from the
.. code-block:: bash .. code-block:: bash
python3 -m pip install build nox -s build
python3 -m build
PYBIND11_SDIST_GLOBAL=1 python3 -m build
twine upload dist/* twine upload dist/*
This makes SDists and wheels, and the final line uploads them. This makes SDists and wheels, and the final line uploads them.

View File

@ -174,7 +174,7 @@ struct function_record {
function_record() function_record()
: is_constructor(false), is_new_style_constructor(false), is_stateless(false), : is_constructor(false), is_new_style_constructor(false), is_stateless(false),
is_operator(false), is_method(false), has_args(false), is_operator(false), is_method(false), has_args(false),
has_kwargs(false), has_kw_only_args(false), prepend(false) { } has_kwargs(false), prepend(false) { }
/// Function name /// Function name
char *name = nullptr; /* why no C++ strings? They generate heavier code.. */ char *name = nullptr; /* why no C++ strings? They generate heavier code.. */
@ -221,17 +221,15 @@ struct function_record {
/// True if the function has a '**kwargs' argument /// True if the function has a '**kwargs' argument
bool has_kwargs : 1; bool has_kwargs : 1;
/// True once a 'py::kw_only' is encountered (any following args are keyword-only)
bool has_kw_only_args : 1;
/// True if this function is to be inserted at the beginning of the overload resolution chain /// True if this function is to be inserted at the beginning of the overload resolution chain
bool prepend : 1; bool prepend : 1;
/// Number of arguments (including py::args and/or py::kwargs, if present) /// Number of arguments (including py::args and/or py::kwargs, if present)
std::uint16_t nargs; std::uint16_t nargs;
/// Number of trailing arguments (counted in `nargs`) that are keyword-only /// Number of leading positional arguments, which are terminated by a py::args or py::kwargs
std::uint16_t nargs_kw_only = 0; /// argument or by a py::kw_only annotation.
std::uint16_t nargs_pos = 0;
/// Number of leading arguments (counted in `nargs`) that are positional-only /// Number of leading arguments (counted in `nargs`) that are positional-only
std::uint16_t nargs_pos_only = 0; std::uint16_t nargs_pos_only = 0;
@ -411,10 +409,9 @@ template <> struct process_attribute<is_new_style_constructor> : process_attribu
static void init(const is_new_style_constructor &, function_record *r) { r->is_new_style_constructor = true; } static void init(const is_new_style_constructor &, function_record *r) { r->is_new_style_constructor = true; }
}; };
inline void process_kw_only_arg(const arg &a, function_record *r) { inline void check_kw_only_arg(const arg &a, function_record *r) {
if (!a.name || a.name[0] == '\0') if (r->args.size() > r->nargs_pos && (!a.name || a.name[0] == '\0'))
pybind11_fail("arg(): cannot specify an unnamed argument after an kw_only() annotation"); pybind11_fail("arg(): cannot specify an unnamed argument after a kw_only() annotation or args() argument");
++r->nargs_kw_only;
} }
/// Process a keyword argument attribute (*without* a default value) /// Process a keyword argument attribute (*without* a default value)
@ -424,7 +421,7 @@ template <> struct process_attribute<arg> : process_attribute_default<arg> {
r->args.emplace_back("self", nullptr, handle(), true /*convert*/, false /*none not allowed*/); 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);
if (r->has_kw_only_args) process_kw_only_arg(a, r); check_kw_only_arg(a, r);
} }
}; };
@ -457,14 +454,16 @@ template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
} }
r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert, a.flag_none); r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert, a.flag_none);
if (r->has_kw_only_args) process_kw_only_arg(a, r); check_kw_only_arg(a, r);
} }
}; };
/// 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) {
r->has_kw_only_args = true; 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)");
r->nargs_pos = static_cast<std::uint16_t>(r->args.size());
} }
}; };
@ -472,6 +471,9 @@ template <> struct process_attribute<kw_only> : process_attribute_default<kw_onl
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) {
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)
pybind11_fail("pos_only(): cannot follow a py::args() argument");
// It also can't follow a kw_only, but a static_assert in pybind11.h checks that
} }
}; };

View File

@ -1159,6 +1159,9 @@ constexpr arg operator"" _a(const char *name, size_t) { return arg(name); }
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
template <typename T> using is_kw_only = std::is_same<intrinsic_t<T>, kw_only>;
template <typename T> using is_pos_only = std::is_same<intrinsic_t<T>, pos_only>;
// forward declaration (definition in attr.h) // forward declaration (definition in attr.h)
struct function_record; struct function_record;
@ -1194,17 +1197,18 @@ class argument_loader {
template <typename Arg> using argument_is_args = std::is_same<intrinsic_t<Arg>, args>; template <typename Arg> using argument_is_args = std::is_same<intrinsic_t<Arg>, args>;
template <typename Arg> using argument_is_kwargs = std::is_same<intrinsic_t<Arg>, kwargs>; template <typename Arg> using argument_is_kwargs = std::is_same<intrinsic_t<Arg>, kwargs>;
// Get args/kwargs argument positions relative to the end of the argument list: // Get kwargs argument position, or -1 if not present:
static constexpr auto args_pos = constexpr_first<argument_is_args, Args...>() - (int) sizeof...(Args), static constexpr auto kwargs_pos = constexpr_last<argument_is_kwargs, Args...>();
kwargs_pos = constexpr_first<argument_is_kwargs, Args...>() - (int) sizeof...(Args);
static constexpr bool args_kwargs_are_last = kwargs_pos >= - 1 && args_pos >= kwargs_pos - 1; static_assert(kwargs_pos == -1 || kwargs_pos == (int) sizeof...(Args) - 1, "py::kwargs is only permitted as the last argument of a function");
static_assert(args_kwargs_are_last, "py::args/py::kwargs are only permitted as the last argument(s) of a function");
public: public:
static constexpr bool has_kwargs = kwargs_pos < 0; static constexpr bool has_kwargs = kwargs_pos != -1;
static constexpr bool has_args = args_pos < 0;
// py::args argument position; -1 if not present.
static constexpr int args_pos = constexpr_last<argument_is_args, Args...>();
static_assert(args_pos == -1 || args_pos == constexpr_first<argument_is_args, Args...>(), "py::args cannot be specified more than once");
static constexpr auto arg_names = concat(type_descr(make_caster<Args>::name)...); static constexpr auto arg_names = concat(type_descr(make_caster<Args>::name)...);

View File

@ -117,7 +117,7 @@ public:
template <typename Return, typename Class, typename... Arg, typename... Extra> template <typename Return, typename Class, typename... Arg, typename... Extra>
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
cpp_function(Return (Class::*f)(Arg...)&, const Extra&... extra) { cpp_function(Return (Class::*f)(Arg...)&, const Extra&... extra) {
initialize([f](Class *c, Arg... args) -> Return { return (c->*f)(args...); }, initialize([f](Class *c, Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
(Return (*) (Class *, Arg...)) nullptr, extra...); (Return (*) (Class *, Arg...)) nullptr, extra...);
} }
@ -135,7 +135,7 @@ public:
template <typename Return, typename Class, typename... Arg, typename... Extra> template <typename Return, typename Class, typename... Arg, typename... Extra>
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
cpp_function(Return (Class::*f)(Arg...) const&, const Extra&... extra) { cpp_function(Return (Class::*f)(Arg...) const&, const Extra&... extra) {
initialize([f](const Class *c, Arg... args) -> Return { return (c->*f)(args...); }, initialize([f](const Class *c, Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
(Return (*)(const Class *, Arg ...)) nullptr, extra...); (Return (*)(const Class *, Arg ...)) nullptr, extra...);
} }
@ -205,7 +205,7 @@ protected:
conditional_t<std::is_void<Return>::value, void_type, Return> conditional_t<std::is_void<Return>::value, void_type, Return>
>; >;
static_assert(expected_num_args<Extra...>(sizeof...(Args), cast_in::has_args, cast_in::has_kwargs), static_assert(expected_num_args<Extra...>(sizeof...(Args), cast_in::args_pos >= 0, cast_in::has_kwargs),
"The number of argument annotations does not match the number of function arguments"); "The number of argument annotations does not match the number of function arguments");
/* Dispatch code which converts function arguments and performs the actual function call */ /* Dispatch code which converts function arguments and performs the actual function call */
@ -240,17 +240,27 @@ protected:
return result; return result;
}; };
rec->nargs_pos = cast_in::args_pos >= 0
? static_cast<std::uint16_t>(cast_in::args_pos)
: sizeof...(Args) - cast_in::has_kwargs; // Will get reduced more if we have a kw_only
rec->has_args = cast_in::args_pos >= 0;
rec->has_kwargs = cast_in::has_kwargs;
/* Process any user-provided function attributes */ /* Process any user-provided function attributes */
process_attributes<Extra...>::init(extra..., rec); process_attributes<Extra...>::init(extra..., rec);
{ {
constexpr bool has_kw_only_args = any_of<std::is_same<kw_only, Extra>...>::value, constexpr bool has_kw_only_args = any_of<std::is_same<kw_only, Extra>...>::value,
has_pos_only_args = any_of<std::is_same<pos_only, Extra>...>::value, has_pos_only_args = any_of<std::is_same<pos_only, Extra>...>::value,
has_args = any_of<std::is_same<args, Args>...>::value,
has_arg_annotations = any_of<is_keyword<Extra>...>::value; has_arg_annotations = any_of<is_keyword<Extra>...>::value;
static_assert(has_arg_annotations || !has_kw_only_args, "py::kw_only requires the use of argument annotations"); static_assert(has_arg_annotations || !has_kw_only_args, "py::kw_only requires the use of argument annotations");
static_assert(has_arg_annotations || !has_pos_only_args, "py::pos_only requires the use of argument annotations (for docstrings and aligning the annotations to the argument)"); static_assert(has_arg_annotations || !has_pos_only_args, "py::pos_only requires the use of argument annotations (for docstrings and aligning the annotations to the argument)");
static_assert(!(has_args && has_kw_only_args), "py::kw_only cannot be combined with a py::args argument");
static_assert(constexpr_sum(is_kw_only<Extra>::value...) <= 1, "py::kw_only may be specified only once");
static_assert(constexpr_sum(is_pos_only<Extra>::value...) <= 1, "py::pos_only may be specified only once");
constexpr auto kw_only_pos = constexpr_first<is_kw_only, Extra...>();
constexpr auto pos_only_pos = constexpr_first<is_pos_only, Extra...>();
static_assert(!(has_kw_only_args && has_pos_only_args) || pos_only_pos < kw_only_pos, "py::pos_only must come before py::kw_only");
} }
/* Generate a readable signature describing the function's arguments and return value types */ /* Generate a readable signature describing the function's arguments and return value types */
@ -261,9 +271,6 @@ protected:
// Pass on the ownership over the `unique_rec` to `initialize_generic`. `rec` stays valid. // Pass on the ownership over the `unique_rec` to `initialize_generic`. `rec` stays valid.
initialize_generic(std::move(unique_rec), signature.text, types.data(), sizeof...(Args)); initialize_generic(std::move(unique_rec), signature.text, types.data(), sizeof...(Args));
if (cast_in::has_args) rec->has_args = true;
if (cast_in::has_kwargs) rec->has_kwargs = true;
/* Stash some additional information used by an important optimization in 'functional.h' */ /* Stash some additional information used by an important optimization in 'functional.h' */
using FunctionType = Return (*)(Args...); using FunctionType = Return (*)(Args...);
constexpr bool is_function_ptr = constexpr bool is_function_ptr =
@ -342,16 +349,18 @@ protected:
/* Generate a proper function signature */ /* Generate a proper function signature */
std::string signature; std::string signature;
size_t type_index = 0, arg_index = 0; size_t type_index = 0, arg_index = 0;
bool is_starred = false;
for (auto *pc = text; *pc != '\0'; ++pc) { for (auto *pc = text; *pc != '\0'; ++pc) {
const auto c = *pc; const auto c = *pc;
if (c == '{') { if (c == '{') {
// Write arg name for everything except *args and **kwargs. // Write arg name for everything except *args and **kwargs.
if (*(pc + 1) == '*') is_starred = *(pc + 1) == '*';
if (is_starred)
continue; continue;
// Separator for keyword-only arguments, placed before the kw // Separator for keyword-only arguments, placed before the kw
// arguments start // arguments start (unless we are already putting an *args)
if (rec->nargs_kw_only > 0 && arg_index + rec->nargs_kw_only == args) if (!rec->has_args && arg_index == rec->nargs_pos)
signature += "*, "; signature += "*, ";
if (arg_index < rec->args.size() && rec->args[arg_index].name) { if (arg_index < rec->args.size() && rec->args[arg_index].name) {
signature += rec->args[arg_index].name; signature += rec->args[arg_index].name;
@ -363,7 +372,7 @@ protected:
signature += ": "; signature += ": ";
} else if (c == '}') { } else if (c == '}') {
// Write default value if available. // Write default value if available.
if (arg_index < rec->args.size() && rec->args[arg_index].descr) { if (!is_starred && arg_index < rec->args.size() && rec->args[arg_index].descr) {
signature += " = "; signature += " = ";
signature += rec->args[arg_index].descr; signature += rec->args[arg_index].descr;
} }
@ -371,7 +380,8 @@ protected:
// argument, rather than before like * // argument, rather than before like *
if (rec->nargs_pos_only > 0 && (arg_index + 1) == rec->nargs_pos_only) if (rec->nargs_pos_only > 0 && (arg_index + 1) == rec->nargs_pos_only)
signature += ", /"; signature += ", /";
arg_index++; if (!is_starred)
arg_index++;
} else if (c == '%') { } else if (c == '%') {
const std::type_info *t = types[type_index++]; const std::type_info *t = types[type_index++];
if (!t) if (!t)
@ -397,7 +407,7 @@ protected:
} }
} }
if (arg_index != args || types[type_index] != nullptr) if (arg_index != args - rec->has_args - rec->has_kwargs || types[type_index] != nullptr)
pybind11_fail("Internal error while parsing type signature (2)"); pybind11_fail("Internal error while parsing type signature (2)");
#if PY_MAJOR_VERSION < 3 #if PY_MAJOR_VERSION < 3
@ -633,7 +643,7 @@ protected:
named positional arguments weren't *also* specified via kwarg. named positional arguments weren't *also* specified via kwarg.
2. If we weren't given enough, try to make up the omitted ones by checking 2. If we weren't given enough, try to make up the omitted ones by checking
whether they were provided by a kwarg matching the `py::arg("name")` name. If whether they were provided by a kwarg matching the `py::arg("name")` name. If
so, use it (and remove it from kwargs; if not, see if the function binding so, use it (and remove it from kwargs); if not, see if the function binding
provided a default that we can use. provided a default that we can use.
3. Ensure that either all keyword arguments were "consumed", or that the function 3. Ensure that either all keyword arguments were "consumed", or that the function
takes a kwargs argument to accept unconsumed kwargs. takes a kwargs argument to accept unconsumed kwargs.
@ -651,7 +661,7 @@ protected:
size_t num_args = func.nargs; // Number of positional arguments that we need size_t num_args = func.nargs; // Number of positional arguments that we need
if (func.has_args) --num_args; // (but don't count py::args if (func.has_args) --num_args; // (but don't count py::args
if (func.has_kwargs) --num_args; // or py::kwargs) if (func.has_kwargs) --num_args; // or py::kwargs)
size_t pos_args = num_args - func.nargs_kw_only; size_t pos_args = func.nargs_pos;
if (!func.has_args && n_args_in > pos_args) if (!func.has_args && n_args_in > pos_args)
continue; // Too many positional arguments for this overload continue; // Too many positional arguments for this overload
@ -697,6 +707,10 @@ protected:
if (bad_arg) if (bad_arg)
continue; // Maybe it was meant for another overload (issue #688) continue; // Maybe it was meant for another overload (issue #688)
// Keep track of how many position args we copied out in case we need to come back
// to copy the rest into a py::args argument.
size_t positional_args_copied = args_copied;
// We'll need to copy this if we steal some kwargs for defaults // We'll need to copy this if we steal some kwargs for defaults
dict kwargs = reinterpret_borrow<dict>(kwargs_in); dict kwargs = reinterpret_borrow<dict>(kwargs_in);
@ -749,6 +763,10 @@ protected:
} }
if (value) { if (value) {
// If we're at the py::args index then first insert a stub for it to be replaced later
if (func.has_args && call.args.size() == func.nargs_pos)
call.args.push_back(none());
call.args.push_back(value); call.args.push_back(value);
call.args_convert.push_back(arg_rec.convert); call.args_convert.push_back(arg_rec.convert);
} }
@ -771,16 +789,19 @@ protected:
// We didn't copy out any position arguments from the args_in tuple, so we // We didn't copy out any position arguments from the args_in tuple, so we
// can reuse it directly without copying: // can reuse it directly without copying:
extra_args = reinterpret_borrow<tuple>(args_in); extra_args = reinterpret_borrow<tuple>(args_in);
} else if (args_copied >= n_args_in) { } else if (positional_args_copied >= n_args_in) {
extra_args = tuple(0); extra_args = tuple(0);
} else { } else {
size_t args_size = n_args_in - args_copied; size_t args_size = n_args_in - positional_args_copied;
extra_args = tuple(args_size); extra_args = tuple(args_size);
for (size_t i = 0; i < args_size; ++i) { for (size_t i = 0; i < args_size; ++i) {
extra_args[i] = PyTuple_GET_ITEM(args_in, args_copied + i); extra_args[i] = PyTuple_GET_ITEM(args_in, positional_args_copied + i);
} }
} }
call.args.push_back(extra_args); if (call.args.size() <= func.nargs_pos)
call.args.push_back(extra_args);
else
call.args[func.nargs_pos] = extra_args;
call.args_convert.push_back(false); call.args_convert.push_back(false);
call.args_ref = std::move(extra_args); call.args_ref = std::move(extra_args);
} }

View File

@ -85,5 +85,7 @@ def build(session: nox.Session) -> None:
""" """
session.install("build") session.install("build")
session.log("Building normal files")
session.run("python", "-m", "build") session.run("python", "-m", "build")
session.log("Building pybind11-global files (PYBIND11_GLOBAL_SDIST=1)")
session.run("python", "-m", "build", env={"PYBIND11_GLOBAL_SDIST": "1"}) session.run("python", "-m", "build", env={"PYBIND11_GLOBAL_SDIST": "1"})

View File

@ -56,6 +56,23 @@ TEST_SUBMODULE(kwargs_and_defaults, m) {
m.def("mixed_plus_args_kwargs_defaults", mixed_plus_both, m.def("mixed_plus_args_kwargs_defaults", mixed_plus_both,
py::arg("i") = 1, py::arg("j") = 3.14159); py::arg("i") = 1, py::arg("j") = 3.14159);
m.def("args_kwonly",
[](int i, double j, const py::args &args, int z) { return py::make_tuple(i, j, args, z); },
"i"_a, "j"_a, "z"_a);
m.def("args_kwonly_kwargs",
[](int i, double j, const py::args &args, int z, const py::kwargs &kwargs) {
return py::make_tuple(i, j, args, z, kwargs); },
"i"_a, "j"_a, py::kw_only{}, "z"_a);
m.def("args_kwonly_kwargs_defaults",
[](int i, double j, const py::args &args, int z, const py::kwargs &kwargs) {
return py::make_tuple(i, j, args, z, kwargs); },
"i"_a = 1, "j"_a = 3.14159, "z"_a = 42);
m.def("args_kwonly_full_monty",
[](int h, int i, double j, const py::args &args, int z, const py::kwargs &kwargs) {
return py::make_tuple(h, i, j, args, z, kwargs); },
py::arg() = 1, py::arg() = 2, py::pos_only{}, "j"_a = 3.14159, "z"_a = 42);
// test_args_refcount // test_args_refcount
// PyPy needs a garbage collection to get the reference count values to match CPython's behaviour // PyPy needs a garbage collection to get the reference count values to match CPython's behaviour
#ifdef PYPY_VERSION #ifdef PYPY_VERSION

View File

@ -141,6 +141,53 @@ def test_mixed_args_and_kwargs(msg):
""" # noqa: E501 line too long """ # noqa: E501 line too long
) )
# Arguments after a py::args are automatically keyword-only (pybind 2.9+)
assert m.args_kwonly(2, 2.5, z=22) == (2, 2.5, (), 22)
assert m.args_kwonly(2, 2.5, "a", "b", "c", z=22) == (2, 2.5, ("a", "b", "c"), 22)
assert m.args_kwonly(z=22, i=4, j=16) == (4, 16, (), 22)
with pytest.raises(TypeError) as excinfo:
assert m.args_kwonly(2, 2.5, 22) # missing z= keyword
assert (
msg(excinfo.value)
== """
args_kwonly(): incompatible function arguments. The following argument types are supported:
1. (i: int, j: float, *args, z: int) -> tuple
Invoked with: 2, 2.5, 22
"""
)
assert m.args_kwonly_kwargs(i=1, k=4, j=10, z=-1, y=9) == (
1,
10,
(),
-1,
{"k": 4, "y": 9},
)
assert m.args_kwonly_kwargs(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, z=11, y=12) == (
1,
2,
(3, 4, 5, 6, 7, 8, 9, 10),
11,
{"y": 12},
)
assert (
m.args_kwonly_kwargs.__doc__
== "args_kwonly_kwargs(i: int, j: float, *args, z: int, **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" # noqa: E501 line too long
)
assert m.args_kwonly_kwargs_defaults() == (1, 3.14159, (), 42, {})
assert m.args_kwonly_kwargs_defaults(2) == (2, 3.14159, (), 42, {})
assert m.args_kwonly_kwargs_defaults(z=-99) == (1, 3.14159, (), -99, {})
assert m.args_kwonly_kwargs_defaults(5, 6, 7, 8) == (5, 6, (7, 8), 42, {})
assert m.args_kwonly_kwargs_defaults(5, 6, 7, m=8) == (5, 6, (7,), 42, {"m": 8})
assert m.args_kwonly_kwargs_defaults(5, 6, 7, m=8, z=9) == (5, 6, (7,), 9, {"m": 8})
def test_keyword_only_args(msg): def test_keyword_only_args(msg):
assert m.kw_only_all(i=1, j=2) == (1, 2) assert m.kw_only_all(i=1, j=2) == (1, 2)
@ -178,7 +225,7 @@ def test_keyword_only_args(msg):
assert ( assert (
msg(excinfo.value) msg(excinfo.value)
== """ == """
arg(): cannot specify an unnamed argument after an kw_only() annotation arg(): cannot specify an unnamed argument after a kw_only() annotation or args() argument
""" """
) )
@ -222,6 +269,47 @@ def test_positional_only_args(msg):
m.pos_only_def_mix(1, j=4) m.pos_only_def_mix(1, j=4)
assert "incompatible function arguments" in str(excinfo.value) assert "incompatible function arguments" in str(excinfo.value)
# 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" # noqa: E501 line too long
)
assert m.args_kwonly_full_monty() == (1, 2, 3.14159, (), 42, {})
assert m.args_kwonly_full_monty(8) == (8, 2, 3.14159, (), 42, {})
assert m.args_kwonly_full_monty(8, 9) == (8, 9, 3.14159, (), 42, {})
assert m.args_kwonly_full_monty(8, 9, 10) == (8, 9, 10.0, (), 42, {})
assert m.args_kwonly_full_monty(3, 4, 5, 6, 7, m=8, z=9) == (
3,
4,
5.0,
(
6,
7,
),
9,
{"m": 8},
)
assert m.args_kwonly_full_monty(3, 4, 5, 6, 7, m=8, z=9) == (
3,
4,
5.0,
(
6,
7,
),
9,
{"m": 8},
)
assert m.args_kwonly_full_monty(5, j=7, m=8, z=9) == (5, 2, 7.0, (), 9, {"m": 8})
assert m.args_kwonly_full_monty(i=5, j=7, m=8, z=9) == (
1,
2,
7.0,
(),
9,
{"i": 5, "m": 8},
)
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__

View File

@ -159,6 +159,14 @@ struct RefQualified {
int constRefQualified(int other) const & { return value + other; } int constRefQualified(int other) const & { return value + other; }
}; };
// Test rvalue ref param
struct RValueRefParam {
std::size_t func1(std::string&& s) { return s.size(); }
std::size_t func2(std::string&& s) const { return s.size(); }
std::size_t func3(std::string&& s) & { return s.size(); }
std::size_t func4(std::string&& s) const & { return s.size(); }
};
TEST_SUBMODULE(methods_and_attributes, m) { TEST_SUBMODULE(methods_and_attributes, m) {
// test_methods_and_attributes // test_methods_and_attributes
py::class_<ExampleMandA> emna(m, "ExampleMandA"); py::class_<ExampleMandA> emna(m, "ExampleMandA");
@ -413,4 +421,11 @@ TEST_SUBMODULE(methods_and_attributes, m) {
.def_readonly("value", &RefQualified::value) .def_readonly("value", &RefQualified::value)
.def("refQualified", &RefQualified::refQualified) .def("refQualified", &RefQualified::refQualified)
.def("constRefQualified", &RefQualified::constRefQualified); .def("constRefQualified", &RefQualified::constRefQualified);
py::class_<RValueRefParam>(m, "RValueRefParam")
.def(py::init<>())
.def("func1", &RValueRefParam::func1)
.def("func2", &RValueRefParam::func2)
.def("func3", &RValueRefParam::func3)
.def("func4", &RValueRefParam::func4);
} }

View File

@ -517,3 +517,11 @@ def test_overload_ordering():
assert "2. (arg0: {}) -> int".format(uni_name) in str(err.value) assert "2. (arg0: {}) -> int".format(uni_name) in str(err.value)
assert "3. (arg0: {}) -> int".format(uni_name) in str(err.value) assert "3. (arg0: {}) -> int".format(uni_name) in str(err.value)
assert "4. (arg0: int) -> int" in str(err.value) assert "4. (arg0: int) -> int" in str(err.value)
def test_rvalue_ref_param():
r = m.RValueRefParam()
assert r.func1("123") == 3
assert r.func2("1234") == 4
assert r.func3("12345") == 5
assert r.func4("123456") == 6