Skip to content

Commit

Permalink
Merge branch 'master' into sh_merge_master
Browse files Browse the repository at this point in the history
  • Loading branch information
Ralf W. Grosse-Kunstleve committed Aug 13, 2024
2 parents 7a9f1c7 + 0d44d72 commit 28b0163
Show file tree
Hide file tree
Showing 10 changed files with 448 additions and 42 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/emscripten.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ on:
pull_request:
branches:
- master
- stable
- v*

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand All @@ -23,8 +25,6 @@ jobs:
- uses: pypa/[email protected]
env:
PYODIDE_BUILD_EXPORTS: whole_archive
CFLAGS: -fexceptions
LDFLAGS: -fexceptions
with:
package-dir: tests
only: cp312-pyodide_wasm32
2 changes: 1 addition & 1 deletion .github/workflows/pip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ jobs:
- uses: actions/download-artifact@v4

- name: Generate artifact attestation for sdist and wheel
uses: actions/attest-build-provenance@210c1913531870065f03ce1f9440dd87bc0938cd # v1.4.0
uses: actions/attest-build-provenance@310b0a4a3b0b78ef57ecda988ee04b132db73ef8 # v1.4.1
with:
subject-path: "*/pybind11*"

Expand Down
71 changes: 71 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,77 @@ IN DEVELOPMENT

Changes will be summarized here periodically.

New Features:

* Support for Python 3.7 was removed. (Official end-of-life: 2023-06-27).
`#5191 <https://github.com/pybind/pybind11/pull/5191>`_

Support for CMake older than 3.15 and some older compilers will also be removed.

Version 2.13.3 (August 13, 2024)
--------------------------------

Bug fixes:

* Quote paths from pybind11-config
`#5302 <https://github.com/pybind/pybind11/pull/5302>`_


* Fix typo in Emscripten support when in config mode (CMake)
`#5301 <https://github.com/pybind/pybind11/pull/5301>`_


Version 2.13.2 (August 13, 2024)
--------------------------------

New Features:

* A ``pybind11::detail::type_caster_std_function_specializations`` feature was added, to support specializations for
``std::function``'s with return types that require custom to-Python conversion behavior (to primary use case is to catch and
convert exceptions).
`#4597 <https://github.com/pybind/pybind11/pull/4597>`_


Changes:


* Use ``PyMutex`` instead of ``std::mutex`` for internal locking in the free-threaded build.
`#5219 <https://github.com/pybind/pybind11/pull/5219>`_

* Add a special type annotation for C++ empty tuple.
`#5214 <https://github.com/pybind/pybind11/pull/5214>`_

* When compiling for WebAssembly, add the required exception flags (CMake 3.13+).
`#5298 <https://github.com/pybind/pybind11/pull/5298>`_

Bug fixes:

* Make ``gil_safe_call_once_and_store`` thread-safe in free-threaded CPython.
`#5246 <https://github.com/pybind/pybind11/pull/5246>`_

* A missing ``#include <algorithm>`` in pybind11/typing.h was added to fix build errors (in case user code does not already depend
on that include).
`#5208 <https://github.com/pybind/pybind11/pull/5208>`_

* Fix regression introduced in #5201 for GCC<10.3 in C++20 mode.
`#5205 <https://github.com/pybind/pybind11/pull/5205>`_


.. fix(cmake)
* Remove extra = when assigning flto value in the case for Clang in CMake.
`#5207 <https://github.com/pybind/pybind11/pull/5207>`_


Tests:

* Adding WASM testing to our CI (Pyodide / Emscripten via scikit-build-core).
`#4745 <https://github.com/pybind/pybind11/pull/4745>`_

* clang-tidy (in GitHub Actions) was updated from clang 15 to clang 18.
`#5272 <https://github.com/pybind/pybind11/pull/5272>`_


Version 2.13.1 (June 26, 2024)
------------------------------

Expand Down
210 changes: 177 additions & 33 deletions include/pybind11/stl.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "detail/common.h"

#include <deque>
#include <initializer_list>
#include <list>
#include <map>
#include <ostream>
Expand All @@ -35,6 +36,89 @@
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)

//
// Begin: Equivalent of
// https://github.com/google/clif/blob/ae4eee1de07cdf115c0c9bf9fec9ff28efce6f6c/clif/python/runtime.cc#L388-L438
/*
The three `PyObjectTypeIsConvertibleTo*()` functions below are
the result of converging the behaviors of pybind11 and PyCLIF
(http://github.com/google/clif).
Originally PyCLIF was extremely far on the permissive side of the spectrum,
while pybind11 was very far on the strict side. Originally PyCLIF accepted any
Python iterable as input for a C++ `vector`/`set`/`map` argument, as long as
the elements were convertible. The obvious (in hindsight) problem was that
any empty Python iterable could be passed to any of these C++ types, e.g. `{}`
was accepted for C++ `vector`/`set` arguments, or `[]` for C++ `map` arguments.
The functions below strike a practical permissive-vs-strict compromise,
informed by tens of thousands of use cases in the wild. A main objective is
to prevent accidents and improve readability:
- Python literals must match the C++ types.
- For C++ `set`: The potentially reducing conversion from a Python sequence
(e.g. Python `list` or `tuple`) to a C++ `set` must be explicit, by going
through a Python `set`.
- However, a Python `set` can still be passed to a C++ `vector`. The rationale
is that this conversion is not reducing. Implicit conversions of this kind
are also fairly commonly used, therefore enforcing explicit conversions
would have an unfavorable cost : benefit ratio; more sloppily speaking,
such an enforcement would be more annoying than helpful.
*/

inline bool PyObjectIsInstanceWithOneOfTpNames(PyObject *obj,
std::initializer_list<const char *> tp_names) {
if (PyType_Check(obj)) {
return false;
}
const char *obj_tp_name = Py_TYPE(obj)->tp_name;
for (const auto *tp_name : tp_names) {
if (std::strcmp(obj_tp_name, tp_name) == 0) {
return true;
}
}
return false;
}

inline bool PyObjectTypeIsConvertibleToStdVector(PyObject *obj) {
if (PySequence_Check(obj) != 0) {
return !PyUnicode_Check(obj) && !PyBytes_Check(obj);
}
return (PyGen_Check(obj) != 0) || (PyAnySet_Check(obj) != 0)
|| PyObjectIsInstanceWithOneOfTpNames(
obj, {"dict_keys", "dict_values", "dict_items", "map", "zip"});
}

inline bool PyObjectTypeIsConvertibleToStdSet(PyObject *obj) {
return (PyAnySet_Check(obj) != 0) || PyObjectIsInstanceWithOneOfTpNames(obj, {"dict_keys"});
}

inline bool PyObjectTypeIsConvertibleToStdMap(PyObject *obj) {
if (PyDict_Check(obj)) {
return true;
}
// Implicit requirement in the conditions below:
// A type with `.__getitem__()` & `.items()` methods must implement these
// to be compatible with https://docs.python.org/3/c-api/mapping.html
if (PyMapping_Check(obj) == 0) {
return false;
}
PyObject *items = PyObject_GetAttrString(obj, "items");
if (items == nullptr) {
PyErr_Clear();
return false;
}
bool is_convertible = (PyCallable_Check(items) != 0);
Py_DECREF(items);
return is_convertible;
}

//
// End: Equivalent of clif/python/runtime.cc
//

/// Extracts an const lvalue reference or rvalue reference for U based on the type of T (e.g. for
/// forwarding a container element). Typically used indirect via forwarded_type(), below.
template <typename T, typename U>
Expand Down Expand Up @@ -66,24 +150,40 @@ struct set_caster {
}
void reserve_maybe(const anyset &, void *) {}

public:
bool load(handle src, bool convert) {
if (!isinstance<anyset>(src)) {
return false;
}
auto s = reinterpret_borrow<anyset>(src);
value.clear();
reserve_maybe(s, &value);
for (auto entry : s) {
bool convert_iterable(const iterable &itbl, bool convert) {
for (const auto &it : itbl) {
key_conv conv;
if (!conv.load(entry, convert)) {
if (!conv.load(it, convert)) {
return false;
}
value.insert(cast_op<Key &&>(std::move(conv)));
}
return true;
}

bool convert_anyset(anyset s, bool convert) {
value.clear();
reserve_maybe(s, &value);
return convert_iterable(s, convert);
}

public:
bool load(handle src, bool convert) {
if (!PyObjectTypeIsConvertibleToStdSet(src.ptr())) {
return false;
}
if (isinstance<anyset>(src)) {
value.clear();
return convert_anyset(reinterpret_borrow<anyset>(src), convert);
}
if (!convert) {
return false;
}
assert(isinstance<iterable>(src));
value.clear();
return convert_iterable(reinterpret_borrow<iterable>(src), convert);
}

template <typename T>
static handle cast(T &&src, return_value_policy policy, handle parent) {
if (!std::is_lvalue_reference<T>::value) {
Expand Down Expand Up @@ -115,15 +215,10 @@ struct map_caster {
}
void reserve_maybe(const dict &, void *) {}

public:
bool load(handle src, bool convert) {
if (!isinstance<dict>(src)) {
return false;
}
auto d = reinterpret_borrow<dict>(src);
bool convert_elements(const dict &d, bool convert) {
value.clear();
reserve_maybe(d, &value);
for (auto it : d) {
for (const auto &it : d) {
key_conv kconv;
value_conv vconv;
if (!kconv.load(it.first.ptr(), convert) || !vconv.load(it.second.ptr(), convert)) {
Expand All @@ -134,6 +229,25 @@ struct map_caster {
return true;
}

public:
bool load(handle src, bool convert) {
if (!PyObjectTypeIsConvertibleToStdMap(src.ptr())) {
return false;
}
if (isinstance<dict>(src)) {
return convert_elements(reinterpret_borrow<dict>(src), convert);
}
if (!convert) {
return false;
}
auto items = reinterpret_steal<object>(PyMapping_Items(src.ptr()));
if (!items) {
throw error_already_set();
}
assert(isinstance<iterable>(items));
return convert_elements(dict(reinterpret_borrow<iterable>(items)), convert);
}

template <typename T>
static handle cast(T &&src, return_value_policy policy, handle parent) {
dict d;
Expand Down Expand Up @@ -166,13 +280,35 @@ struct list_caster {
using value_conv = make_caster<Value>;

bool load(handle src, bool convert) {
if (!isinstance<sequence>(src) || isinstance<bytes>(src) || isinstance<str>(src)) {
if (!PyObjectTypeIsConvertibleToStdVector(src.ptr())) {
return false;
}
if (isinstance<sequence>(src)) {
return convert_elements(src, convert);
}
if (!convert) {
return false;
}
auto s = reinterpret_borrow<sequence>(src);
// Designed to be behavior-equivalent to passing tuple(src) from Python:
// The conversion to a tuple will first exhaust the generator object, to ensure that
// the generator is not left in an unpredictable (to the caller) partially-consumed
// state.
assert(isinstance<iterable>(src));
return convert_elements(tuple(reinterpret_borrow<iterable>(src)), convert);
}

private:
template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0>
void reserve_maybe(const sequence &s, Type *) {
value.reserve(s.size());
}
void reserve_maybe(const sequence &, void *) {}

bool convert_elements(handle seq, bool convert) {
auto s = reinterpret_borrow<sequence>(seq);
value.clear();
reserve_maybe(s, &value);
for (const auto &it : s) {
for (const auto &it : seq) {
value_conv conv;
if (!conv.load(it, convert)) {
return false;
Expand All @@ -182,13 +318,6 @@ struct list_caster {
return true;
}

private:
template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0>
void reserve_maybe(const sequence &s, Type *) {
value.reserve(s.size());
}
void reserve_maybe(const sequence &, void *) {}

public:
template <typename T>
static handle cast(T &&src, return_value_policy policy, handle parent) {
Expand Down Expand Up @@ -237,12 +366,8 @@ struct array_caster {
return size == Size;
}

public:
bool load(handle src, bool convert) {
if (!isinstance<sequence>(src)) {
return false;
}
auto l = reinterpret_borrow<sequence>(src);
bool convert_elements(handle seq, bool convert) {
auto l = reinterpret_borrow<sequence>(seq);
if (!require_size(l.size())) {
return false;
}
Expand All @@ -257,6 +382,25 @@ struct array_caster {
return true;
}

public:
bool load(handle src, bool convert) {
if (!PyObjectTypeIsConvertibleToStdVector(src.ptr())) {
return false;
}
if (isinstance<sequence>(src)) {
return convert_elements(src, convert);
}
if (!convert) {
return false;
}
// Designed to be behavior-equivalent to passing tuple(src) from Python:
// The conversion to a tuple will first exhaust the generator object, to ensure that
// the generator is not left in an unpredictable (to the caller) partially-consumed
// state.
assert(isinstance<iterable>(src));
return convert_elements(tuple(reinterpret_borrow<iterable>(src)), convert);
}

template <typename T>
static handle cast(T &&src, return_value_policy policy, handle parent) {
list l(src.size());
Expand Down
Loading

0 comments on commit 28b0163

Please sign in to comment.