From de59f662cf9e78816f6f9a2a62d4b770eea2348b Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Sat, 2 Aug 2025 13:55:03 +0800 Subject: [PATCH 1/7] feat!: use C++20 for extension build --- .clang-format | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/tests.yml | 31 ------------------------------- CMakeLists.txt | 4 ++-- README.md | 2 +- 5 files changed, 5 insertions(+), 36 deletions(-) diff --git a/.clang-format b/.clang-format index 8f6cc725..70d30051 100644 --- a/.clang-format +++ b/.clang-format @@ -7,7 +7,7 @@ AccessModifierOffset: -4 ColumnLimit: 100 Language: Cpp -Standard: c++17 +Standard: c++20 AlignAfterOpenBracket: Align AlignEscapedNewlines: Right AllowAllArgumentsOnNextLine: false diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index be91bf1a..93613490 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -83,7 +83,7 @@ jobs: run: make clang-format - name: clang-tidy - run: make clang-tidy CMAKE_CXX_STANDARD=17 + run: make clang-tidy CMAKE_CXX_STANDARD=20 - name: cpplint run: make cpplint diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7edc4e89..52b254ac 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -194,37 +194,6 @@ jobs: echo "::endgroup::" echo "pybind11_VERSION=HEAD" | tee -a "${GITHUB_ENV}" - - name: Test installable with C++17 - shell: bash - if: runner.os != 'Windows' - run: | - ( - set -ex - ${{ env.PYTHON }} -m venv venv - source venv/bin/activate - OPTREE_CXX_WERROR=OFF CMAKE_CXX_STANDARD=17 \ - ${{ env.PYTHON }} -m pip install -v . - pushd tests - ${{ env.PYTHON }} -X dev -Walways -Werror -c 'import optree' - popd - rm -rf venv - ) - - if [[ "$?" -ne 0 ]]; then - echo "::error::Failed to install with C++17." >&2 - exit 1 - fi - CORE_DUMP_FILES="$( - find . -type d -path "./venv" -prune \ - -o '(' -iname "core.*.[1-9]*" -o -iname "core_*.dmp" ')' -print - )" - if [[ -n "${CORE_DUMP_FILES}" ]]; then - echo "::error::Coredump files found, indicating a crash during tests." >&2 - echo "Coredump files:" >&2 - ls -alh ${CORE_DUMP_FILES} >&2 - exit 1 - fi - - name: Test buildable without Python frontend if: runner.os != 'Windows' run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index a21d4800..9bad2234 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,8 +39,8 @@ endif() if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 20) # for likely/unlikely attributes endif() -if (CMAKE_CXX_STANDARD VERSION_LESS 17) - message(FATAL_ERROR "C++17 or higher is required") +if (CMAKE_CXX_STANDARD VERSION_LESS 20) + message(FATAL_ERROR "C++20 or higher is required") endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) message(STATUS "Use C++ standard: C++${CMAKE_CXX_STANDARD}") diff --git a/README.md b/README.md index cdc203a6..1ed7a929 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ The following options are available while building the Python C extension from t ```bash export CMAKE_COMMAND="/path/to/custom/cmake" export CMAKE_BUILD_TYPE="Debug" -export CMAKE_CXX_STANDARD="20" # C++17 is tested on Linux/macOS (C++20 is required on Windows) +export CMAKE_CXX_STANDARD="20" export OPTREE_CXX_WERROR="OFF" export _GLIBCXX_USE_CXX11_ABI="1" export pybind11_DIR="/path/to/custom/pybind11" From 678d28888292130e5bfff1c3ad7ae78553d8ae7e Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Sat, 2 Aug 2025 14:02:40 +0800 Subject: [PATCH 2/7] feat!: use C++20 for `requires` expression --- include/optree/pytypes.h | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/include/optree/pytypes.h b/include/optree/pytypes.h index b031929b..61df3c39 100644 --- a/include/optree/pytypes.h +++ b/include/optree/pytypes.h @@ -19,7 +19,7 @@ limitations under the License. #include // std::rethrow_exception, std::current_exception #include // std::string -#include // std::enable_if_t, std::is_base_of_v +#include // std::is_base_of_v #include // std::unordered_map #include // std::move, std::pair, std::make_pair @@ -105,15 +105,19 @@ inline Py_ALWAYS_INLINE py::ssize_t DictGetSize(const py::handle &dict) { #endif } -template >> -inline Py_ALWAYS_INLINE T TupleGetItemAs(const py::handle &tuple, const py::ssize_t &index) { +template +inline Py_ALWAYS_INLINE T TupleGetItemAs(const py::handle &tuple, const py::ssize_t &index) + requires(std::is_base_of_v) +{ return py::reinterpret_borrow(PyTuple_GET_ITEM(tuple.ptr(), index)); } inline Py_ALWAYS_INLINE py::object TupleGetItem(const py::handle &tuple, const py::ssize_t &index) { return TupleGetItemAs(tuple, index); } -template >> -inline Py_ALWAYS_INLINE T ListGetItemAs(const py::handle &list, const py::ssize_t &index) { +template +inline Py_ALWAYS_INLINE T ListGetItemAs(const py::handle &list, const py::ssize_t &index) + requires(std::is_base_of_v) +{ #if PY_VERSION_HEX >= 0x030D00A4 // Python 3.13.0a4 PyObject * const item = PyList_GetItemRef(list.ptr(), index); if (item == nullptr) [[unlikely]] { @@ -127,8 +131,10 @@ inline Py_ALWAYS_INLINE T ListGetItemAs(const py::handle &list, const py::ssize_ inline Py_ALWAYS_INLINE py::object ListGetItem(const py::handle &list, const py::ssize_t &index) { return ListGetItemAs(list, index); } -template >> -inline Py_ALWAYS_INLINE T DictGetItemAs(const py::handle &dict, const py::handle &key) { +template +inline Py_ALWAYS_INLINE T DictGetItemAs(const py::handle &dict, const py::handle &key) + requires(std::is_base_of_v) +{ #if PY_VERSION_HEX >= 0x030D00A1 // Python 3.13.0a1 PyObject *value = nullptr; if (PyDict_GetItemRef(dict.ptr(), key.ptr(), &value) < 0) [[unlikely]] { From fe4382e05a9f523a7b4833a89569fb72518e923f Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Sat, 2 Aug 2025 14:23:02 +0800 Subject: [PATCH 3/7] feat!: use C++20 for `std::ranges` --- src/treespec/constructors.cpp | 6 ++--- src/treespec/richcomparison.cpp | 14 +++++----- src/treespec/treespec.cpp | 46 +++++++++++++++------------------ 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/treespec/constructors.cpp b/src/treespec/constructors.cpp index f8c37e5b..acd42e88 100644 --- a/src/treespec/constructors.cpp +++ b/src/treespec/constructors.cpp @@ -15,7 +15,7 @@ limitations under the License. ================================================================================ */ -#include // std::copy +#include // std::ranges::copy #include // std::back_inserter #include // std::unique_ptr, std::make_unique #include // std::ostringstream @@ -262,9 +262,7 @@ template auto out = std::make_unique(); ssize_t num_leaves = ((node.kind == PyTreeKind::Leaf) ? 1 : 0); for (const PyTreeSpec &treespec : treespecs) { - std::copy(treespec.m_traversal.cbegin(), - treespec.m_traversal.cend(), - std::back_inserter(out->m_traversal)); + std::ranges::copy(treespec.m_traversal, std::back_inserter(out->m_traversal)); num_leaves += treespec.GetNumLeaves(); } node.num_leaves = num_leaves; diff --git a/src/treespec/richcomparison.cpp b/src/treespec/richcomparison.cpp index a553f6e4..f2c3fda5 100644 --- a/src/treespec/richcomparison.cpp +++ b/src/treespec/richcomparison.cpp @@ -15,7 +15,7 @@ limitations under the License. ================================================================================ */ -#include // std::copy, std::reverse +#include // std::ranges::copy, std::ranges::reverse #include // std::unordered_map #include // std::vector @@ -103,8 +103,8 @@ bool PyTreeSpec::IsPrefix(const PyTreeSpec &other, const bool &strict) const { other_offsets.emplace_back(other_offsets.back() + num_nodes); other_cur += num_nodes; } - std::reverse(other_num_nodes.begin(), other_num_nodes.end()); - std::reverse(other_offsets.begin(), other_offsets.end()); + std::ranges::reverse(other_num_nodes); + std::ranges::reverse(other_offsets); EXPECT_EQ(other_offsets.front(), b->num_nodes, "PyTreeSpec traversal out of range."); @@ -125,15 +125,15 @@ bool PyTreeSpec::IsPrefix(const PyTreeSpec &other, const bool &strict) const { reordered_other_offsets.emplace_back(reordered_other_offsets.back() + reordered_other_num_nodes[i]); } - std::reverse(reordered_other_offsets.begin(), reordered_other_offsets.end()); + std::ranges::reverse(reordered_other_offsets); EXPECT_EQ(reordered_other_offsets.front(), b->num_nodes, "PyTreeSpec traversal out of range."); auto original_b = other.m_traversal.crbegin() + (b - other_traversal.crbegin()); for (const auto &[i, j] : reordered_index_to_index) { - std::copy(original_b + other_offsets[j + 1], - original_b + other_offsets[j], - b + reordered_other_offsets[i + 1]); + std::ranges::copy(original_b + other_offsets[j + 1], + original_b + other_offsets[j], + b + reordered_other_offsets[i + 1]); } } break; diff --git a/src/treespec/treespec.cpp b/src/treespec/treespec.cpp index d952c82c..69dae0fc 100644 --- a/src/treespec/treespec.cpp +++ b/src/treespec/treespec.cpp @@ -15,7 +15,7 @@ limitations under the License. ================================================================================ */ -#include // std::copy, std::reverse +#include // std::ranges::copy, std::ranges::reverse #include // std::back_inserter #include // std::unique_ptr, std::make_unique #include // std::optional @@ -204,16 +204,16 @@ namespace optree { ssize_t other_cur = other_pos - 1; if (root.kind == PyTreeKind::Leaf) [[likely]] { - std::copy(other_traversal.crend() - (other_pos + 1), - other_traversal.crend() - (other_pos - other_root.num_nodes + 1), - std::back_inserter(nodes)); + std::ranges::copy(other_traversal.crend() - (other_pos + 1), + other_traversal.crend() - (other_pos - other_root.num_nodes + 1), + std::back_inserter(nodes)); other_cur -= other_root.num_nodes - 1; return {pos - cur, other_pos - other_cur, other_root.num_nodes, other_root.num_leaves}; } if (other_root.kind == PyTreeKind::Leaf) [[likely]] { - std::copy(traversal.crend() - (pos + 1), - traversal.crend() - (pos - root.num_nodes + 1), - std::back_inserter(nodes)); + std::ranges::copy(traversal.crend() - (pos + 1), + traversal.crend() - (pos - root.num_nodes + 1), + std::back_inserter(nodes)); cur -= root.num_nodes - 1; return {pos - cur, other_pos - other_cur, root.num_nodes, root.num_leaves}; } @@ -303,7 +303,7 @@ namespace optree { other_curs.emplace_back(other_cur); other_cur -= other_traversal.at(other_cur).num_nodes; } - std::reverse(other_curs.begin(), other_curs.end()); + std::ranges::reverse(other_curs); const ssize_t last_other_cur = other_cur; for (ssize_t i = root.arity - 1; i >= 0; --i) { const py::object key = ListGetItem(expected_keys, i); @@ -433,7 +433,7 @@ std::unique_ptr PyTreeSpec::BroadcastToCommonSuffix(const PyTreeSpec num_nodes - 1, other.m_traversal, other_num_nodes - 1); - std::reverse(treespec->m_traversal.begin(), treespec->m_traversal.end()); + std::ranges::reverse(treespec->m_traversal); EXPECT_EQ(num_nodes_walked, num_nodes, "`pos != 0` at end of PyTreeSpec::BroadcastToCommonSuffix() " @@ -462,7 +462,7 @@ std::unique_ptr PyTreeSpec::Transform(const std::optional(*this); } - const auto transform = + const auto transform_node = [this, &f_node, &f_leaf](const Node &node) -> std::unique_ptr { auto nodespec = GetOneLevel(node); @@ -487,7 +487,7 @@ std::unique_ptr PyTreeSpec::Transform(const std::optional>(4); for (const Node &node : m_traversal) { - auto transformed = transform(node); + auto transformed = transform_node(node); if (transformed->m_none_is_leaf != m_none_is_leaf) [[unlikely]] { std::ostringstream oss{}; oss << "Expected the PyTreeSpec transform function returns " @@ -540,9 +540,7 @@ std::unique_ptr PyTreeSpec::Transform(const std::optionalm_traversal.cbegin(), - transformed->m_traversal.cend(), - std::back_inserter(treespec->m_traversal)); + std::ranges::copy(transformed->m_traversal, std::back_inserter(treespec->m_traversal)); const ssize_t num_leaves = transformed->GetNumLeaves(); const ssize_t num_nodes = transformed->GetNumNodes(); num_extra_leaves += num_leaves - 1; @@ -603,9 +601,7 @@ std::unique_ptr PyTreeSpec::Compose(const PyTreeSpec &inner) const { const ssize_t num_inner_nodes = inner.GetNumNodes(); for (const Node &node : m_traversal) { if (node.kind == PyTreeKind::Leaf) [[likely]] { - std::copy(inner.m_traversal.cbegin(), - inner.m_traversal.cend(), - std::back_inserter(treespec->m_traversal)); + std::ranges::copy(inner.m_traversal, std::back_inserter(treespec->m_traversal)); } else [[unlikely]] { Node new_node{node}; new_node.num_leaves = node.num_leaves * num_inner_leaves; @@ -712,7 +708,7 @@ std::vector PyTreeSpec::Paths() const { } auto stack = reserved_vector(4); const ssize_t num_nodes_walked = PathsImpl(paths, stack, num_nodes - 1, 0); - std::reverse(paths.begin(), paths.end()); + std::ranges::reverse(paths); EXPECT_EQ(num_nodes_walked, num_nodes, "`pos != 0` at end of PyTreeSpec::Paths()."); EXPECT_EQ(py::ssize_t_cast(paths.size()), num_leaves, "PyTreeSpec::Paths() mismatched leaves."); return paths; @@ -819,7 +815,7 @@ std::vector PyTreeSpec::Accessors() const { const ssize_t num_nodes = GetNumNodes(); auto stack = reserved_vector(4); const ssize_t num_nodes_walked = AccessorsImpl(accessors, stack, num_nodes - 1, 0); - std::reverse(accessors.begin(), accessors.end()); + std::ranges::reverse(accessors); EXPECT_EQ(num_nodes_walked, num_nodes, "`pos != 0` at end of PyTreeSpec::Accessors()."); EXPECT_EQ(py::ssize_t_cast(accessors.size()), num_leaves, @@ -924,9 +920,9 @@ std::vector> PyTreeSpec::Children() const { children[i]->m_namespace = m_namespace; const Node &node = m_traversal.at(pos - 1); EXPECT_GE(pos, node.num_nodes, "PyTreeSpec::Children() walked off start of array."); - std::copy(m_traversal.cbegin() + pos - node.num_nodes, - m_traversal.cbegin() + pos, - std::back_inserter(children[i]->m_traversal)); + std::ranges::copy(m_traversal.cbegin() + (pos - node.num_nodes), + m_traversal.cbegin() + pos, + std::back_inserter(children[i]->m_traversal)); children[i]->m_traversal.shrink_to_fit(); PYTREESPEC_SANITY_CHECK(*children[i]); pos -= node.num_nodes; @@ -958,9 +954,9 @@ std::unique_ptr PyTreeSpec::Child(ssize_t index) const { child->m_namespace = m_namespace; const Node &node = m_traversal.at(pos - 1); EXPECT_GE(pos, node.num_nodes, "PyTreeSpec::Child() walked off start of array."); - std::copy(m_traversal.cbegin() + pos - node.num_nodes, - m_traversal.cbegin() + pos, - std::back_inserter(child->m_traversal)); + std::ranges::copy(m_traversal.cbegin() + (pos - node.num_nodes), + m_traversal.cbegin() + pos, + std::back_inserter(child->m_traversal)); child->m_traversal.shrink_to_fit(); PYTREESPEC_SANITY_CHECK(*child); return child; From 4d8b425743ea69d391522bb99bb041dca0772083 Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Sat, 2 Aug 2025 14:29:38 +0800 Subject: [PATCH 4/7] feat!: use C++20 for `std::span` --- include/optree/treespec.h | 13 +++++-------- src/treespec/hashing.cpp | 2 +- src/treespec/serialization.cpp | 2 +- src/treespec/traversal.cpp | 5 +++-- src/treespec/treespec.cpp | 12 ++++++------ src/treespec/unflatten.cpp | 8 +++++--- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/include/optree/treespec.h b/include/optree/treespec.h index fff08b8d..2f9ce3f6 100644 --- a/include/optree/treespec.h +++ b/include/optree/treespec.h @@ -19,6 +19,7 @@ limitations under the License. #include // std::unique_ptr #include // std::optional, std::nullopt +#include // std::span #include // std::string #include // std::thread::id #include // std::tuple @@ -265,10 +266,8 @@ class PyTreeSpec { const bool &inherit_global_namespace = true) { const scoped_read_lock lock{sm_is_dict_insertion_ordered_mutex}; - return (sm_is_dict_insertion_ordered.find(registry_namespace) != - sm_is_dict_insertion_ordered.end()) || - (inherit_global_namespace && - sm_is_dict_insertion_ordered.find("") != sm_is_dict_insertion_ordered.end()); + return (sm_is_dict_insertion_ordered.contains(registry_namespace)) || + (inherit_global_namespace && sm_is_dict_insertion_ordered.contains("")); } // Set the namespace to preserve the insertion order of the dictionary keys during flattening. @@ -337,10 +336,8 @@ class PyTreeSpec { [[nodiscard]] static std::string NodeKindToString(const Node &node); // Helper that manufactures an instance of a node given its children. - [[nodiscard]] static py::object MakeNode( - const Node &node, - const py::object children[], // NOLINT[hicpp-avoid-c-arrays] - const size_t &num_children); + [[nodiscard]] static py::object MakeNode(const Node &node, + const std::span &children); // Helper that identifies the path entry class for a node. [[nodiscard]] static py::object GetPathEntryType(const Node &node); diff --git a/src/treespec/hashing.cpp b/src/treespec/hashing.cpp index a36179d6..260b85b5 100644 --- a/src/treespec/hashing.cpp +++ b/src/treespec/hashing.cpp @@ -99,7 +99,7 @@ ssize_t PyTreeSpec::HashValue() const { const ThreadedIdentity ident{this, std::this_thread::get_id()}; { const scoped_read_lock lock{mutex}; - if (running.find(ident) != running.end()) [[unlikely]] { + if (running.contains(ident)) [[unlikely]] { return 0; } } diff --git a/src/treespec/serialization.cpp b/src/treespec/serialization.cpp index b80770c9..bd48f734 100644 --- a/src/treespec/serialization.cpp +++ b/src/treespec/serialization.cpp @@ -265,7 +265,7 @@ std::string PyTreeSpec::ToString() const { const ThreadedIdentity ident{this, std::this_thread::get_id()}; { const scoped_read_lock lock{mutex}; - if (running.find(ident) != running.end()) [[unlikely]] { + if (running.contains(ident)) [[unlikely]] { return "..."; } } diff --git a/src/treespec/traversal.cpp b/src/treespec/traversal.cpp index 36917362..0a48e180 100644 --- a/src/treespec/traversal.cpp +++ b/src/treespec/traversal.cpp @@ -16,6 +16,7 @@ limitations under the License. */ #include // std::optional +#include // std::span #include // std::ostringstream #include // std::runtime_error #include // std::move @@ -230,8 +231,8 @@ py::object PyTreeSpec::WalkImpl(const py::iterable &leaves, } else [[unlikely]] { const py::object out = MakeNode(node, - node.arity > 0 ? &agenda[size - node.arity] : nullptr, - node.arity); + node.arity > 0 ? std::span(&agenda[size - node.arity], node.arity) + : std::span{}); agenda.resize(size - node.arity); agenda.emplace_back( f_node ? EVALUATE_WITH_LOCK_HELD2((*f_node)(out), out, *f_node) : out); diff --git a/src/treespec/treespec.cpp b/src/treespec/treespec.cpp index 69dae0fc..2c41da3b 100644 --- a/src/treespec/treespec.cpp +++ b/src/treespec/treespec.cpp @@ -19,6 +19,7 @@ limitations under the License. #include // std::back_inserter #include // std::unique_ptr, std::make_unique #include // std::optional +#include // std::span #include // std::ostringstream #include // std::string #include // std::tuple @@ -32,10 +33,9 @@ namespace optree { // NOLINTNEXTLINE[readability-function-cognitive-complexity] /*static*/ py::object PyTreeSpec::MakeNode(const Node &node, // NOLINTNEXTLINE[cppcoreguidelines-avoid-c-arrays] - const py::object children[], - const size_t &num_children) { - EXPECT_EQ(py::ssize_t_cast(num_children), node.arity, "Node arity did not match."); - EXPECT_TRUE(children != nullptr || num_children == 0, "Node children is null."); + const std::span &children) { + EXPECT_EQ(py::ssize_t_cast(children.size()), node.arity, "Node arity did not match."); + EXPECT_TRUE(children.data() != nullptr || children.empty(), "Node children is null."); switch (node.kind) { case PyTreeKind::Leaf: @@ -714,8 +714,8 @@ std::vector PyTreeSpec::Paths() const { return paths; } -template -ssize_t PyTreeSpec::AccessorsImpl(Span &accessors, // NOLINT[misc-no-recursion] +template +ssize_t PyTreeSpec::AccessorsImpl(AccessorVector &accessors, // NOLINT[misc-no-recursion] Stack &stack, const ssize_t &pos, const ssize_t &depth) const { diff --git a/src/treespec/unflatten.cpp b/src/treespec/unflatten.cpp index db43a83e..e5389857 100644 --- a/src/treespec/unflatten.cpp +++ b/src/treespec/unflatten.cpp @@ -15,6 +15,7 @@ limitations under the License. ================================================================================ */ +#include // std::span #include // std::ostringstream #include // std::move @@ -57,9 +58,10 @@ py::object PyTreeSpec::UnflattenImpl(const Span &leaves) const { case PyTreeKind::StructSequence: case PyTreeKind::Custom: { const ssize_t size = py::ssize_t_cast(agenda.size()); - py::object out = MakeNode(node, - node.arity > 0 ? &agenda[size - node.arity] : nullptr, - node.arity); + py::object out = + MakeNode(node, + node.arity > 0 ? std::span(&agenda[size - node.arity], node.arity) + : std::span{}); agenda.resize(size - node.arity); agenda.emplace_back(std::move(out)); break; From 39c158799d36fcd57a3697b9e856dec4411dab53 Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Sat, 2 Aug 2025 16:11:52 +0800 Subject: [PATCH 5/7] feat!: use C++20 for `std::source_location` --- include/optree/exceptions.h | 56 +++++++++++++++++-------------------- src/optree.cpp | 2 +- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/include/optree/exceptions.h b/include/optree/exceptions.h index 9b2906cc..477f767e 100644 --- a/include/optree/exceptions.h +++ b/include/optree/exceptions.h @@ -17,58 +17,52 @@ limitations under the License. #pragma once -#include // std::size_t -#include // std::optional, std::nullopt -#include // std::ostringstream -#include // std::logic_error -#include // std::string, std::char_traits +#include // std::size_t +#include // std::source_location +#include // std::ostringstream +#include // std::logic_error +#include // std::string, std::char_traits +#include // std::string_view namespace optree { -constexpr std::size_t CURRENT_FILE_PATH_SIZE = std::char_traits::length(__FILE__); +constexpr std::size_t CURRENT_FILE_PATH_SIZE = + std::char_traits::length(std::source_location::current().file_name()); constexpr std::size_t CURRENT_FILE_RELPATH_FROM_PROJECT_ROOT_SIZE = std::char_traits::length("include/optree/exceptions.h"); static_assert(CURRENT_FILE_PATH_SIZE >= CURRENT_FILE_RELPATH_FROM_PROJECT_ROOT_SIZE, "SOURCE_PATH_PREFIX_SIZE must be greater than 0."); constexpr std::size_t SOURCE_PATH_PREFIX_SIZE = CURRENT_FILE_PATH_SIZE - CURRENT_FILE_RELPATH_FROM_PROJECT_ROOT_SIZE; -// NOLINTNEXTLINE[bugprone-reserved-identifier] -#define __FILE_RELPATH_FROM_PROJECT_ROOT__ ((const char *)&(__FILE__[SOURCE_PATH_PREFIX_SIZE])) + +constexpr std::string_view RelpathFromProjectRoot(const std::string_view &abspath) { + return abspath.substr(SOURCE_PATH_PREFIX_SIZE); +} +constexpr std::string_view RelpathFromProjectRoot( + const std::source_location &source_location = std::source_location::current()) { + return RelpathFromProjectRoot(source_location.file_name()); +} class InternalError : public std::logic_error { public: - explicit InternalError(const std::string &message) noexcept(noexcept(std::logic_error{message})) - : std::logic_error{message} {} - explicit InternalError(const std::string &message, - const std::string &file, - const std::size_t &lineno, - const std::optional function = - std::nullopt) noexcept(noexcept(std::logic_error{message})) - : InternalError([&message, &file, &lineno, &function]() -> std::string { + explicit InternalError( + const std::string_view &message, + const std::source_location &source_location = std::source_location::current()) + : std::logic_error{[&message, &source_location]() -> std::string { std::ostringstream oss{}; - oss << message << " ("; - if (function) [[likely]] { - oss << "function `" << *function << "` "; - } - oss << "at file " << file << ":" << lineno << ")\n\n" + oss << message << " (in function `" << source_location.function_name() << "` at file " + << RelpathFromProjectRoot(source_location) << ":" << source_location.line() << ":" + << source_location.column() << ")\n\n" << "Please file a bug report at https://github.com/metaopt/optree/issues."; return oss.str(); - }()) {} + }()} {} }; #define VA_FUNC2_(__0, __1, NAME, ...) NAME #define VA_FUNC3_(__0, __1, __2, NAME, ...) NAME -#if !defined(__GNUC__) -# define __PRETTY_FUNCTION__ std::nullopt // NOLINT[bugprone-reserved-identifier] -#endif - #define INTERNAL_ERROR0_() INTERNAL_ERROR1_("Unreachable code.") -#define INTERNAL_ERROR1_(message) \ - throw optree::InternalError((message), \ - __FILE_RELPATH_FROM_PROJECT_ROOT__, \ - __LINE__, \ - __PRETTY_FUNCTION__) +#define INTERNAL_ERROR1_(message) throw optree::InternalError(message) #define INTERNAL_ERROR(...) \ VA_FUNC2_(__0 __VA_OPT__(, ) __VA_ARGS__, INTERNAL_ERROR1_, INTERNAL_ERROR0_)(__VA_ARGS__) diff --git a/src/optree.cpp b/src/optree.cpp index 1cfbc9bc..25c831cd 100644 --- a/src/optree.cpp +++ b/src/optree.cpp @@ -49,7 +49,7 @@ void BuildModule(py::module_ &mod) { // NOLINT[runtime/references] GetCxxModule(mod); mod.doc() = "Optimized PyTree Utilities. (C extension module built from " + - std::string(__FILE_RELPATH_FROM_PROJECT_ROOT__) + ")"; + std::string(RelpathFromProjectRoot()) + ")"; mod.attr("Py_TPFLAGS_BASETYPE") = py::int_(Py_TPFLAGS_BASETYPE); // NOLINTNEXTLINE[bugprone-macro-parentheses] From 662abb295fb7e73e6e7c5997712f26fb8b54bfc2 Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Sat, 2 Aug 2025 16:32:43 +0800 Subject: [PATCH 6/7] feat!: use C++20 for `std::format` --- include/optree/exceptions.h | 18 ++-- include/optree/pytypes.h | 76 ++++++++++----- src/optree.cpp | 5 +- src/registry.cpp | 79 +++++++-------- src/treespec/constructors.cpp | 47 +++++---- src/treespec/flatten.cpp | 178 ++++++++++++++++++---------------- src/treespec/traversal.cpp | 24 ++--- src/treespec/treespec.cpp | 175 +++++++++++++++++---------------- src/treespec/unflatten.cpp | 15 ++- 9 files changed, 329 insertions(+), 288 deletions(-) diff --git a/include/optree/exceptions.h b/include/optree/exceptions.h index 477f767e..42098711 100644 --- a/include/optree/exceptions.h +++ b/include/optree/exceptions.h @@ -18,8 +18,8 @@ limitations under the License. #pragma once #include // std::size_t +#include // std::format #include // std::source_location -#include // std::ostringstream #include // std::logic_error #include // std::string, std::char_traits #include // std::string_view @@ -48,14 +48,14 @@ class InternalError : public std::logic_error { explicit InternalError( const std::string_view &message, const std::source_location &source_location = std::source_location::current()) - : std::logic_error{[&message, &source_location]() -> std::string { - std::ostringstream oss{}; - oss << message << " (in function `" << source_location.function_name() << "` at file " - << RelpathFromProjectRoot(source_location) << ":" << source_location.line() << ":" - << source_location.column() << ")\n\n" - << "Please file a bug report at https://github.com/metaopt/optree/issues."; - return oss.str(); - }()} {} + : std::logic_error{ + std::format("{} (in function `{}` at file {}:{}:{})\n\n" + "Please file a bug report at https://github.com/metaopt/optree/issues.", + message, + source_location.function_name(), + RelpathFromProjectRoot(source_location), + source_location.line(), + source_location.column())} {} }; #define VA_FUNC2_(__0, __1, NAME, ...) NAME diff --git a/include/optree/pytypes.h b/include/optree/pytypes.h index 61df3c39..467f6f05 100644 --- a/include/optree/pytypes.h +++ b/include/optree/pytypes.h @@ -18,6 +18,7 @@ limitations under the License. #pragma once #include // std::rethrow_exception, std::current_exception +#include // std::format, std::format_to, std::formatter #include // std::string #include // std::is_base_of_v #include // std::unordered_map @@ -49,6 +50,29 @@ inline Py_ALWAYS_INLINE std::string PyRepr(const std::string &string) { return static_cast(py::repr(py::str(string))); } +template +struct std::formatter { + template + constexpr ParseContext::iterator parse(const ParseContext &context) { + return context.begin(); + } + template + FormatContext::iterator format(const py::handle &object, FormatContext &context) const { + return std::format_to(context.out(), "{}", PyRepr(object)); + } +}; +template +struct std::formatter { + template + constexpr ParseContext::iterator parse(const ParseContext &context) { + return context.begin(); + } + template + FormatContext::iterator format(const py::object &object, FormatContext &context) const { + return std::format_to(context.out(), "{}", PyRepr(object)); + } +}; + // The maximum size of the type cache. constexpr py::ssize_t MAX_TYPE_CACHE_SIZE = 4096; @@ -173,31 +197,31 @@ inline Py_ALWAYS_INLINE void DictSetItem(const py::handle &dict, inline Py_ALWAYS_INLINE void AssertExactList(const py::handle &object) { if (!PyList_CheckExact(object.ptr())) [[unlikely]] { - throw py::value_error("Expected an instance of list, got " + PyRepr(object) + "."); + throw py::value_error(std::format("Expected an instance of list, got {}.", object)); } } inline Py_ALWAYS_INLINE void AssertExactTuple(const py::handle &object) { if (!PyTuple_CheckExact(object.ptr())) [[unlikely]] { - throw py::value_error("Expected an instance of tuple, got " + PyRepr(object) + "."); + throw py::value_error(std::format("Expected an instance of tuple, got {}.", object)); } } inline Py_ALWAYS_INLINE void AssertExactDict(const py::handle &object) { if (!PyDict_CheckExact(object.ptr())) [[unlikely]] { - throw py::value_error("Expected an instance of dict, got " + PyRepr(object) + "."); + throw py::value_error(std::format("Expected an instance of dict, got {}.", object)); } } inline Py_ALWAYS_INLINE void AssertExactOrderedDict(const py::handle &object) { if (!py::type::handle_of(object).is(PyOrderedDictTypeObject)) [[unlikely]] { - throw py::value_error("Expected an instance of collections.OrderedDict, got " + - PyRepr(object) + "."); + throw py::value_error( + std::format("Expected an instance of collections.OrderedDict, got {}.", object)); } } inline Py_ALWAYS_INLINE void AssertExactDefaultDict(const py::handle &object) { if (!py::type::handle_of(object).is(PyDefaultDictTypeObject)) [[unlikely]] { - throw py::value_error("Expected an instance of collections.defaultdict, got " + - PyRepr(object) + "."); + throw py::value_error( + std::format("Expected an instance of collections.defaultdict, got {}.", object)); } } @@ -206,16 +230,16 @@ inline Py_ALWAYS_INLINE void AssertExactStandardDict(const py::handle &object) { py::type::handle_of(object).is(PyOrderedDictTypeObject) || py::type::handle_of(object).is(PyDefaultDictTypeObject))) [[unlikely]] { throw py::value_error( - "Expected an instance of dict, collections.OrderedDict, or collections.defaultdict, " - "got " + - PyRepr(object) + "."); + std::format("Expected an instance of dict, collections.OrderedDict, " + "or collections.defaultdict, got {}.", + object)); } } inline Py_ALWAYS_INLINE void AssertExactDeque(const py::handle &object) { if (!py::type::handle_of(object).is(PyDequeTypeObject)) [[unlikely]] { - throw py::value_error("Expected an instance of collections.deque, got " + PyRepr(object) + - "."); + throw py::value_error( + std::format("Expected an instance of collections.deque, got {}.", object)); } } @@ -298,8 +322,8 @@ inline Py_ALWAYS_INLINE bool IsNamedTuple(const py::handle &object) { } inline Py_ALWAYS_INLINE void AssertExactNamedTuple(const py::handle &object) { if (!IsNamedTupleInstance(object)) [[unlikely]] { - throw py::value_error("Expected an instance of collections.namedtuple, got " + - PyRepr(object) + "."); + throw py::value_error( + std::format("Expected an instance of collections.namedtuple, got {}.", object)); } } inline py::tuple NamedTupleGetFields(const py::handle &object) { @@ -307,14 +331,15 @@ inline py::tuple NamedTupleGetFields(const py::handle &object) { if (PyType_Check(object.ptr())) [[unlikely]] { type = object; if (!IsNamedTupleClass(type)) [[unlikely]] { - throw py::type_error("Expected a collections.namedtuple type, got " + PyRepr(object) + - "."); + throw py::type_error( + std::format("Expected a collections.namedtuple type, got {}.", object)); } } else [[likely]] { type = py::type::handle_of(object); if (!IsNamedTupleClass(type)) [[unlikely]] { - throw py::type_error("Expected an instance of collections.namedtuple type, got " + - PyRepr(object) + "."); + throw py::type_error( + std::format("Expected an instance of collections.namedtuple type, got {}.", + object)); } } return EVALUATE_WITH_LOCK_HELD(py::getattr(type, Py_Get_ID(_fields)), type); @@ -401,8 +426,8 @@ inline Py_ALWAYS_INLINE bool IsStructSequence(const py::handle &object) { } inline Py_ALWAYS_INLINE void AssertExactStructSequence(const py::handle &object) { if (!IsStructSequenceInstance(object)) [[unlikely]] { - throw py::value_error("Expected an instance of PyStructSequence type, got " + - PyRepr(object) + "."); + throw py::value_error( + std::format("Expected an instance of PyStructSequence type, got {}.", object)); } } inline py::tuple StructSequenceGetFieldsImpl(const py::handle &type) { @@ -439,13 +464,13 @@ inline py::tuple StructSequenceGetFields(const py::handle &object) { if (PyType_Check(object.ptr())) [[unlikely]] { type = object; if (!IsStructSequenceClass(type)) [[unlikely]] { - throw py::type_error("Expected a PyStructSequence type, got " + PyRepr(object) + "."); + throw py::type_error(std::format("Expected a PyStructSequence type, got {}.", object)); } } else [[likely]] { type = py::type::handle_of(object); if (!IsStructSequenceClass(type)) [[unlikely]] { - throw py::type_error("Expected an instance of PyStructSequence type, got " + - PyRepr(object) + "."); + throw py::type_error( + std::format("Expected an instance of PyStructSequence type, got {}.", object)); } } @@ -496,8 +521,9 @@ inline void TotalOrderSort(py::list &list) { // NOLINT[runtime/references] const auto sort_key_fn = py::cpp_function([](const py::object &obj) -> py::tuple { const py::handle cls = py::type::handle_of(obj); const py::str qualname{EVALUATE_WITH_LOCK_HELD( - PyStr(py::getattr(cls, Py_Get_ID(__module__))) + "." + - PyStr(py::getattr(cls, Py_Get_ID(__qualname__))), + std::format("{}.{}", + PyStr(py::getattr(cls, Py_Get_ID(__module__))), + PyStr(py::getattr(cls, Py_Get_ID(__qualname__)))), cls)}; return py::make_tuple(qualname, obj); }); diff --git a/src/optree.cpp b/src/optree.cpp index 25c831cd..4a9b0265 100644 --- a/src/optree.cpp +++ b/src/optree.cpp @@ -17,6 +17,7 @@ limitations under the License. #include "optree/optree.h" +#include // std::format #include // std::{not_,}equal_to, std::less{,_equal}, std::greater{,_equal} #include // std::unique_ptr #include // std::optional, std::nullopt @@ -48,8 +49,8 @@ void BuildModule(py::module_ &mod) { // NOLINT[runtime/references] GetCxxModule(mod); - mod.doc() = "Optimized PyTree Utilities. (C extension module built from " + - std::string(RelpathFromProjectRoot()) + ")"; + mod.doc() = std::format("Optimized PyTree Utilities. (C extension module built from {})", + RelpathFromProjectRoot()); mod.attr("Py_TPFLAGS_BASETYPE") = py::int_(Py_TPFLAGS_BASETYPE); // NOLINTNEXTLINE[bugprone-macro-parentheses] diff --git a/src/registry.cpp b/src/registry.cpp index f996a60f..d7a39140 100644 --- a/src/registry.cpp +++ b/src/registry.cpp @@ -15,6 +15,7 @@ limitations under the License. ================================================================================ */ +#include // std::format #include // std::make_shared #include // std::ostringstream #include // std::string @@ -41,8 +42,9 @@ template registration->kind = kind; registration->type = py::reinterpret_borrow(cls); EXPECT_TRUE(registry.m_registrations.emplace(cls, std::move(registration)).second, - "PyTree type " + PyRepr(cls) + - " is already registered in the global namespace."); + std::format("PyTree type {} is already registered " + "in the global namespace.", + cls)); if (sm_builtins_types.emplace(cls).second) [[likely]] { cls.inc_ref(); } @@ -71,8 +73,8 @@ template const py::object &path_entry_type, const std::string ®istry_namespace) { if (sm_builtins_types.find(cls) != sm_builtins_types.end()) [[unlikely]] { - throw py::value_error("PyTree type " + PyRepr(cls) + - " is a built-in type and cannot be re-registered."); + throw py::value_error( + std::format("PyTree type {} is a built-in type and cannot be re-registered.", cls)); } auto ®istry = GetSingleton(); @@ -84,23 +86,23 @@ template registration->path_entry_type = py::reinterpret_borrow(path_entry_type); if (registry_namespace.empty()) [[unlikely]] { if (!registry.m_registrations.emplace(cls, std::move(registration)).second) [[unlikely]] { - throw py::value_error("PyTree type " + PyRepr(cls) + - " is already registered in the global namespace."); + throw py::value_error( + std::format("PyTree type {} is already registered in the global namespace.", cls)); } if (IsStructSequenceClass(cls)) [[unlikely]] { PyErr_WarnEx(PyExc_UserWarning, - ("PyTree type " + PyRepr(cls) + - " is a class of `PyStructSequence`, " - "which is already registered in the global namespace. " - "Override it with custom flatten/unflatten functions.") + std::format("PyTree type {} is a class of `PyStructSequence`, " + "which is already registered in the global namespace. " + "Override it with custom flatten/unflatten functions.", + cls) .c_str(), /*stack_level=*/2); } else if (IsNamedTupleClass(cls)) [[unlikely]] { PyErr_WarnEx(PyExc_UserWarning, - ("PyTree type " + PyRepr(cls) + - " is a subclass of `collections.namedtuple`, " - "which is already registered in the global namespace. " - "Override it with custom flatten/unflatten functions.") + std::format("PyTree type {} is a subclass of `collections.namedtuple`, " + "which is already registered in the global namespace. " + "Override it with custom flatten/unflatten functions.", + cls) .c_str(), /*stack_level=*/2); } @@ -108,31 +110,32 @@ template if (!registry.m_named_registrations .emplace(std::make_pair(registry_namespace, cls), std::move(registration)) .second) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTree type " << PyRepr(cls) << " is already registered in namespace " - << PyRepr(registry_namespace) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("PyTree type {} is already registered in namespace {}.", + cls, + PyRepr(registry_namespace))); } if (IsStructSequenceClass(cls)) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTree type " << PyRepr(cls) - << " is a class of `PyStructSequence`, " - "which is already registered in the global namespace. " - "Override it with custom flatten/unflatten functions in namespace " - << PyRepr(registry_namespace) << "."; - PyErr_WarnEx(PyExc_UserWarning, - oss.str().c_str(), - /*stack_level=*/2); + PyErr_WarnEx( + PyExc_UserWarning, + + std::format("PyTree type {} is a class of `PyStructSequence`, " + "which is already registered in the global namespace. " + "Override it with custom flatten/unflatten functions in namespace {}.", + cls, + PyRepr(registry_namespace)) + .c_str(), + /*stack_level=*/2); } else if (IsNamedTupleClass(cls)) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTree type " << PyRepr(cls) - << " is a subclass of `collections.namedtuple`, " - "which is already registered in the global namespace. " - "Override it with custom flatten/unflatten functions in namespace " - << PyRepr(registry_namespace) << "."; - PyErr_WarnEx(PyExc_UserWarning, - oss.str().c_str(), - /*stack_level=*/2); + PyErr_WarnEx( + PyExc_UserWarning, + std::format("PyTree type {} is a subclass of `collections.namedtuple`, " + "which is already registered in the global namespace. " + "Override it with custom flatten/unflatten functions in namespace {}.", + cls, + PyRepr(registry_namespace)) + .c_str(), + /*stack_level=*/2); } } } @@ -165,8 +168,8 @@ template const py::object &cls, const std::string ®istry_namespace) { if (sm_builtins_types.find(cls) != sm_builtins_types.end()) [[unlikely]] { - throw py::value_error("PyTree type " + PyRepr(cls) + - " is a built-in type and cannot be unregistered."); + throw py::value_error( + std::format("PyTree type {} is a built-in type and cannot be unregistered.", cls)); } auto ®istry = GetSingleton(); diff --git a/src/treespec/constructors.cpp b/src/treespec/constructors.cpp index acd42e88..2ecff623 100644 --- a/src/treespec/constructors.cpp +++ b/src/treespec/constructors.cpp @@ -16,9 +16,9 @@ limitations under the License. */ #include // std::ranges::copy +#include // std::format #include // std::back_inserter #include // std::unique_ptr, std::make_unique -#include // std::ostringstream #include // std::runtime_error #include // std::string #include // std::move @@ -77,10 +77,9 @@ template std::vector &treespecs) -> void { for (const py::object &child : children) { if (!py::isinstance(child)) [[unlikely]] { - std::ostringstream oss{}; - oss << "Expected a(n) " << NodeKindToString(node) << " of PyTreeSpec(s), got " - << PyRepr(handle) << "."; - throw py::value_error(oss.str()); + throw py::value_error(std::format("Expected a(n) {} of PyTreeSpec(s), got {}.", + NodeKindToString(node), + handle)); } treespecs.emplace_back(thread_safe_cast(child)); } @@ -97,11 +96,10 @@ template if (common_registry_namespace.empty()) [[likely]] { common_registry_namespace = treespec.m_namespace; } else if (common_registry_namespace != treespec.m_namespace) [[unlikely]] { - std::ostringstream oss{}; - oss << "Expected treespecs with the same namespace, got " - << PyRepr(common_registry_namespace) << " vs. " - << PyRepr(treespec.m_namespace) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("Expected treespecs with the same namespace, got {} vs. {}.", + PyRepr(common_registry_namespace), + PyRepr(treespec.m_namespace))); } } } @@ -109,10 +107,9 @@ template if (registry_namespace.empty()) [[likely]] { registry_namespace = common_registry_namespace; } else if (registry_namespace != common_registry_namespace) [[unlikely]] { - std::ostringstream oss{}; - oss << "Expected treespec(s) with namespace " << PyRepr(registry_namespace) - << ", got " << PyRepr(common_registry_namespace) << "."; - throw py::value_error(oss.str()); + throw py::value_error(std::format("Expected treespec(s) with namespace {}, got {}.", + PyRepr(registry_namespace), + PyRepr(common_registry_namespace))); } } else if (node.kind != PyTreeKind::Custom) [[likely]] { registry_namespace = ""; @@ -220,10 +217,11 @@ template node.custom->flatten_func); const ssize_t num_out = TupleGetSize(out); if (num_out != 2 && num_out != 3) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTree custom flatten function for type " << PyRepr(node.custom->type) - << " should return a 2- or 3-tuple, got " << num_out << "."; - throw std::runtime_error(oss.str()); + throw std::runtime_error( + std::format("PyTree custom flatten function for type {} should return a 2- or " + "3-tuple, got {}.", + node.custom->type, + num_out)); } node.arity = 0; node.node_data = TupleGetItem(out, 1); @@ -242,12 +240,13 @@ template node.node_entries = thread_safe_cast(node_entries); const ssize_t num_entries = TupleGetSize(node.node_entries); if (num_entries != node.arity) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTree custom flatten function for type " - << PyRepr(node.custom->type) - << " returned inconsistent number of children (" << node.arity - << ") and number of entries (" << num_entries << ")."; - throw std::runtime_error(oss.str()); + throw std::runtime_error( + std::format("PyTree custom flatten function for type {} returned " + "inconsistent number of children ({}) " + "and number of entries ({}).", + node.custom->type, + node.arity, + num_entries)); } } } diff --git a/src/treespec/flatten.cpp b/src/treespec/flatten.cpp index 2271f96e..672ec0fe 100644 --- a/src/treespec/flatten.cpp +++ b/src/treespec/flatten.cpp @@ -15,6 +15,7 @@ limitations under the License. ================================================================================ */ +#include // std::format #include // std::unique_ptr, std::make_unique #include // std::optional #include // std::ostringstream @@ -155,10 +156,11 @@ bool PyTreeSpec::FlattenIntoImpl(const py::handle &handle, node.custom->flatten_func); const ssize_t num_out = TupleGetSize(out); if (num_out != 2 && num_out != 3) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTree custom flatten function for type " << PyRepr(node.custom->type) - << " should return a 2- or 3-tuple, got " << num_out << "."; - throw std::runtime_error(oss.str()); + throw std::runtime_error( + std::format("PyTree custom flatten function for type {} should " + "return a 2- or 3-tuple, got {}.", + node.custom->type, + num_out)); } node.arity = 0; node.node_data = TupleGetItem(out, 1); @@ -176,12 +178,13 @@ bool PyTreeSpec::FlattenIntoImpl(const py::handle &handle, node.node_entries = thread_safe_cast(node_entries); const ssize_t num_entries = TupleGetSize(node.node_entries); if (num_entries != node.arity) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTree custom flatten function for type " - << PyRepr(node.custom->type) - << " returned inconsistent number of children (" << node.arity - << ") and number of entries (" << num_entries << ")."; - throw std::runtime_error(oss.str()); + throw std::runtime_error( + std::format("PyTree custom flatten function for type {} returned " + "inconsistent number of children ({}) " + "and number of entries ({}).", + node.custom->type, + node.arity, + num_entries)); } } } @@ -418,10 +421,11 @@ bool PyTreeSpec::FlattenIntoWithPathImpl(const py::handle &handle, node.custom->flatten_func); const ssize_t num_out = TupleGetSize(out); if (num_out != 2 && num_out != 3) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTree custom flatten function for type " << PyRepr(node.custom->type) - << " should return a 2- or 3-tuple, got " << num_out << "."; - throw std::runtime_error(oss.str()); + throw std::runtime_error( + std::format("PyTree custom flatten function for type {} should " + "return a 2- or 3-tuple, got {}.", + node.custom->type, + num_out)); } node.arity = 0; node.node_data = TupleGetItem(out, 1); @@ -446,19 +450,20 @@ bool PyTreeSpec::FlattenIntoWithPathImpl(const py::handle &handle, for (const py::handle &child : children) { if (num_children >= node.arity) [[unlikely]] { throw std::runtime_error( - "PyTree custom flatten function for type " + - PyRepr(node.custom->type) + - " returned inconsistent number of children and number of entries."); + std::format("PyTree custom flatten function for type {} returned " + "inconsistent number of children and number of entries", + node.custom->type)); } recurse(child, TupleGetItem(node.node_entries, num_children++)); } if (num_children != node.arity) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTree custom flatten function for type " - << PyRepr(node.custom->type) - << " returned inconsistent number of children (" << num_children - << ") and number of entries (" << node.arity << ")."; - throw std::runtime_error(oss.str()); + throw std::runtime_error( + std::format("PyTree custom flatten function for type {} returned " + "inconsistent number of children ({}) " + "and number of entries ({}).", + node.custom->type, + num_children, + node.arity)); } } break; @@ -573,10 +578,10 @@ py::list PyTreeSpec::FlattenUpTo(const py::object &tree) const { ssize_t leaf = num_leaves - 1; while (!agenda.empty()) { if (it == m_traversal.crend()) [[unlikely]] { - std::ostringstream oss{}; - oss << "Tree structures did not match; expected: " << ToString() - << ", got: " << PyRepr(tree) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("Tree structures did not match; expected: {}, got: {}.", + ToString(), + tree)); } const Node &node = *it; const py::object object = std::move(agenda.back()); @@ -598,9 +603,7 @@ py::list PyTreeSpec::FlattenUpTo(const py::object &tree) const { "`PyTreeKind::None`."); } if (!object.is_none()) [[likely]] { - std::ostringstream oss{}; - oss << "Expected None, got " << PyRepr(object) << "."; - throw py::value_error(oss.str()); + throw py::value_error(std::format("Expected None, got {}.", object)); } break; } @@ -609,10 +612,11 @@ py::list PyTreeSpec::FlattenUpTo(const py::object &tree) const { AssertExactTuple(object); const auto tuple = py::reinterpret_borrow(object); if (TupleGetSize(tuple) != node.arity) [[unlikely]] { - std::ostringstream oss{}; - oss << "tuple arity mismatch; expected: " << node.arity - << ", got: " << TupleGetSize(tuple) << "; tuple: " << PyRepr(object) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("tuple arity mismatch; expected: {}, got: {}; tuple: {}.", + node.arity, + TupleGetSize(tuple), + object)); } for (ssize_t i = 0; i < node.arity; ++i) { agenda.emplace_back(TupleGetItem(tuple, i)); @@ -625,10 +629,11 @@ py::list PyTreeSpec::FlattenUpTo(const py::object &tree) const { const scoped_critical_section cs{object}; const auto list = py::reinterpret_borrow(object); if (ListGetSize(list) != node.arity) [[unlikely]] { - std::ostringstream oss{}; - oss << "list arity mismatch; expected: " << node.arity - << ", got: " << ListGetSize(list) << "; list: " << PyRepr(object) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("list arity mismatch; expected: {}, got: {}; list: {}.", + node.arity, + ListGetSize(list), + object)); } for (ssize_t i = 0; i < node.arity; ++i) { agenda.emplace_back(ListGetItem(list, i)); @@ -679,17 +684,19 @@ py::list PyTreeSpec::FlattenUpTo(const py::object &tree) const { AssertExactNamedTuple(object); const auto tuple = py::reinterpret_borrow(object); if (TupleGetSize(tuple) != node.arity) [[unlikely]] { - std::ostringstream oss{}; - oss << "namedtuple arity mismatch; expected: " << node.arity - << ", got: " << TupleGetSize(tuple) << "; tuple: " << PyRepr(object) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("namedtuple arity mismatch; expected: {}, got: {}; tuple: {}.", + node.arity, + TupleGetSize(tuple), + object)); } if (py::type::handle_of(object).not_equal(node.node_data)) [[unlikely]] { - std::ostringstream oss{}; - oss << "namedtuple type mismatch; expected type: " << PyRepr(node.node_data) - << ", got type: " << PyRepr(py::type::handle_of(object)) - << "; tuple: " << PyRepr(object) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("namedtuple type mismatch; expected type: {}, got type: {}; " + "tuple: {}.", + node.node_data, + py::type::handle_of(object), + object)); } for (ssize_t i = 0; i < node.arity; ++i) { agenda.emplace_back(TupleGetItem(tuple, i)); @@ -701,10 +708,11 @@ py::list PyTreeSpec::FlattenUpTo(const py::object &tree) const { AssertExactDeque(object); const auto list = thread_safe_cast(object); if (ListGetSize(list) != node.arity) [[unlikely]] { - std::ostringstream oss{}; - oss << "deque arity mismatch; expected: " << node.arity - << ", got: " << ListGetSize(list) << "; deque: " << PyRepr(object) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("deque arity mismatch; expected: {}, got: {}; deque: {}.", + node.arity, + ListGetSize(list), + object)); } for (ssize_t i = 0; i < node.arity; ++i) { agenda.emplace_back(ListGetItem(list, i)); @@ -716,18 +724,20 @@ py::list PyTreeSpec::FlattenUpTo(const py::object &tree) const { AssertExactStructSequence(object); const auto tuple = py::reinterpret_borrow(object); if (TupleGetSize(tuple) != node.arity) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyStructSequence arity mismatch; expected: " << node.arity - << ", got: " << TupleGetSize(tuple) << "; tuple: " << PyRepr(object) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("PyStructSequence arity mismatch; expected: {}, got: {}; " + "tuple: {}.", + node.arity, + TupleGetSize(tuple), + object)); } if (py::type::handle_of(object).not_equal(node.node_data)) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyStructSequence type mismatch; expected type: " - << PyRepr(node.node_data) - << ", got type: " << PyRepr(py::type::handle_of(object)) - << "; tuple: " << PyRepr(object) << "."; - throw py::value_error(oss.str()); + throw py::value_error(std::format( + "PyStructSequence type mismatch; expected type: {}, got type: {}; " + "tuple: {}.", + node.node_data, + py::type::handle_of(object), + object)); } for (ssize_t i = 0; i < node.arity; ++i) { agenda.emplace_back(TupleGetItem(tuple, i)); @@ -745,11 +755,12 @@ py::list PyTreeSpec::FlattenUpTo(const py::object &tree) const { PyTreeTypeRegistry::Lookup(py::type::of(object), m_namespace); } if (registration != node.custom) [[unlikely]] { - std::ostringstream oss{}; - oss << "Custom node type mismatch; expected type: " << PyRepr(node.custom->type) - << ", got type: " << PyRepr(py::type::handle_of(object)) - << "; value: " << PyRepr(object) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("Custom node type mismatch; expected type: {}, got type: {}; " + "value: {}.", + node.custom->type, + py::type::handle_of(object), + object)); } const py::tuple out = EVALUATE_WITH_LOCK_HELD2( thread_safe_cast(node.custom->flatten_func(object)), @@ -757,20 +768,22 @@ py::list PyTreeSpec::FlattenUpTo(const py::object &tree) const { node.custom->flatten_func); const ssize_t num_out = TupleGetSize(out); if (num_out != 2 && num_out != 3) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTree custom flatten function for type " << PyRepr(node.custom->type) - << " should return a 2- or 3-tuple, got " << num_out << "."; - throw std::runtime_error(oss.str()); + throw std::runtime_error( + std::format("PyTree custom flatten function for type {} should " + "return a 2- or 3-tuple, got {}.", + node.custom->type, + num_out)); } { const py::object node_data = TupleGetItem(out, 1); const scoped_critical_section2 cs{node.node_data, node_data}; if (node.node_data.not_equal(node_data)) [[unlikely]] { - std::ostringstream oss{}; - oss << "Mismatch custom node data; expected: " << PyRepr(node.node_data) - << ", got: " << PyRepr(node_data) << "; value: " << PyRepr(object) - << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("Mismatch custom node data; expected: {}, got: {}; " + "value: {}.", + node.node_data, + node_data, + object)); } } ssize_t arity = 0; @@ -783,10 +796,11 @@ py::list PyTreeSpec::FlattenUpTo(const py::object &tree) const { } } if (arity != node.arity) [[unlikely]] { - std::ostringstream oss{}; - oss << "Custom type arity mismatch; expected: " << node.arity - << ", got: " << arity << "; value: " << PyRepr(object) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("Custom type arity mismatch; expected: {}, got: {}; value: {}.", + node.arity, + arity, + object)); } break; } @@ -797,10 +811,8 @@ py::list PyTreeSpec::FlattenUpTo(const py::object &tree) const { } } if (it != m_traversal.crend() || leaf != -1) [[unlikely]] { - std::ostringstream oss{}; - oss << "Tree structures did not match; expected: " << ToString() - << ", got: " << PyRepr(tree) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("Tree structures did not match; expected: {}, got: {}.", ToString(), tree)); } return leaves; } diff --git a/src/treespec/traversal.cpp b/src/treespec/traversal.cpp index 0a48e180..1164e1ee 100644 --- a/src/treespec/traversal.cpp +++ b/src/treespec/traversal.cpp @@ -15,9 +15,9 @@ limitations under the License. ================================================================================ */ +#include // std::format #include // std::optional #include // std::span -#include // std::ostringstream #include // std::runtime_error #include // std::move @@ -125,10 +125,11 @@ py::object PyTreeIter::NextImpl() { custom->flatten_func); const ssize_t num_out = TupleGetSize(out); if (num_out != 2 && num_out != 3) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTree custom flatten function for type " << PyRepr(custom->type) - << " should return a 2- or 3-tuple, got " << num_out << "."; - throw std::runtime_error(oss.str()); + throw std::runtime_error( + std::format("PyTree custom flatten function for type {} should " + "return a 2- or 3-tuple, got {}.", + custom->type, + num_out)); } auto children = thread_safe_cast(TupleGetItem(out, 0)); const ssize_t arity = TupleGetSize(children); @@ -138,12 +139,13 @@ py::object PyTreeIter::NextImpl() { const ssize_t num_entries = TupleGetSize(thread_safe_cast(node_entries)); if (num_entries != arity) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTree custom flatten function for type " - << PyRepr(custom->type) - << " returned inconsistent number of children (" << arity - << ") and number of entries (" << num_entries << ")."; - throw std::runtime_error(oss.str()); + throw std::runtime_error( + std::format("PyTree custom flatten function for type {} returned " + "inconsistent number of children ({}) and " + "number of entries ({}).", + custom->type, + arity, + num_entries)); } } } diff --git a/src/treespec/treespec.cpp b/src/treespec/treespec.cpp index 2c41da3b..4b01528f 100644 --- a/src/treespec/treespec.cpp +++ b/src/treespec/treespec.cpp @@ -16,6 +16,7 @@ limitations under the License. */ #include // std::ranges::copy, std::ranges::reverse +#include // std::format #include // std::back_inserter #include // std::unique_ptr, std::make_unique #include // std::optional @@ -219,10 +220,10 @@ namespace optree { } if (root.kind == PyTreeKind::None) [[unlikely]] { if (other_root.kind != PyTreeKind::None) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTreeSpecs have incompatible node types; expected type: " - << NodeKindToString(root) << ", got: " << NodeKindToString(other_root) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("PyTreeSpecs have incompatible node types; expected type: {}, got: {}.", + NodeKindToString(root), + NodeKindToString(other_root))); } nodes.emplace_back(root); @@ -243,16 +244,16 @@ namespace optree { case PyTreeKind::List: case PyTreeKind::Deque: { if (root.kind != other_root.kind) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTreeSpecs have incompatible node types; expected type: " - << NodeKindToString(root) << ", got: " << NodeKindToString(other_root) << "."; - throw py::value_error(oss.str()); + throw py::value_error(std::format( + "PyTreeSpecs have incompatible node types; expected type: {}, got: {}.", + NodeKindToString(root), + NodeKindToString(other_root))); } if (root.arity != other_root.arity) [[unlikely]] { - std::ostringstream oss{}; - oss << NodeKindToString(root) << " arity mismatch; expected: " << root.arity - << ", got: " << other_root.arity << "."; - throw py::value_error(oss.str()); + throw py::value_error(std::format("{} arity mismatch; expected: {}, got: {}.", + NodeKindToString(root), + root.arity, + other_root.arity)); } break; } @@ -262,10 +263,10 @@ namespace optree { case PyTreeKind::DefaultDict: { if (other_root.kind != PyTreeKind::Dict && other_root.kind != PyTreeKind::OrderedDict && other_root.kind != PyTreeKind::DefaultDict) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTreeSpecs have incompatible node types; expected type: " - << NodeKindToString(root) << ", got: " << NodeKindToString(other_root) << "."; - throw py::value_error(oss.str()); + throw py::value_error(std::format( + "PyTreeSpecs have incompatible node types; expected type: {}, got: {}.", + NodeKindToString(root), + NodeKindToString(other_root))); } const scoped_critical_section2 cs{root.node_data, other_root.node_data}; @@ -289,11 +290,11 @@ namespace optree { if (ListGetSize(extra_keys) != 0) [[likely]] { key_difference_sstream << ", extra key(s): " << PyRepr(extra_keys); } - std::ostringstream oss{}; - oss << "dictionary key mismatch; expected key(s): " << PyRepr(expected_keys) - << ", got key(s): " << PyRepr(other_keys) << key_difference_sstream.str() - << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("dictionary key mismatch; expected key(s): {}, got key(s): {}; {}.", + py::handle{expected_keys}, + py::handle{other_keys}, + key_difference_sstream.str())); } const size_t start_num_nodes = nodes.size(); @@ -324,54 +325,54 @@ namespace optree { case PyTreeKind::NamedTuple: case PyTreeKind::StructSequence: { if (root.kind != other_root.kind) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTreeSpecs have incompatible node types; expected type: " - << NodeKindToString(root) << ", got: " << NodeKindToString(other_root) << "."; - throw py::value_error(oss.str()); + throw py::value_error(std::format( + "PyTreeSpecs have incompatible node types; expected type: {}, got: {}.", + NodeKindToString(root), + NodeKindToString(other_root))); } if (root.arity != other_root.arity) [[unlikely]] { - std::ostringstream oss{}; - oss << (root.kind == PyTreeKind::NamedTuple ? "namedtuple" : "PyStructSequence") - << " arity mismatch; expected: " << root.arity << ", got: " << other_root.arity - << "."; - throw py::value_error(oss.str()); + throw py::value_error(std::format( + "{} arity mismatch; expected: {}, got: {}.", + root.kind == PyTreeKind::NamedTuple ? "namedtuple" : "PyStructSequence", + root.arity, + other_root.arity)); } if (root.node_data.not_equal(other_root.node_data)) [[unlikely]] { - std::ostringstream oss{}; - oss << (root.kind == PyTreeKind::NamedTuple ? "namedtuple" : "PyStructSequence") - << " type mismatch; expected type: " << NodeKindToString(root) - << ", got type: " << NodeKindToString(other_root) << "."; - throw py::value_error(oss.str()); + throw py::value_error(std::format( + "{} type mismatch; expected type: {}, got type: {}.", + root.kind == PyTreeKind::NamedTuple ? "namedtuple" : "PyStructSequence", + NodeKindToString(root), + NodeKindToString(other_root))); } break; } case PyTreeKind::Custom: { if (root.kind != other_root.kind) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTreeSpecs have incompatible node types; expected type: " - << NodeKindToString(root) << ", got: " << NodeKindToString(other_root) << "."; - throw py::value_error(oss.str()); + throw py::value_error(std::format( + "PyTreeSpecs have incompatible node types; expected type: {}, got: {}.", + NodeKindToString(root), + NodeKindToString(other_root))); } if (!root.custom->type.is(other_root.custom->type)) [[unlikely]] { - std::ostringstream oss{}; - oss << "Custom node type mismatch; expected type: " << NodeKindToString(root) - << ", got type: " << NodeKindToString(other_root) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("Custom node type mismatch; expected type: {}, got type: {}.", + NodeKindToString(root), + NodeKindToString(other_root))); } if (root.arity != other_root.arity) [[unlikely]] { - std::ostringstream oss{}; - oss << "Custom type arity mismatch; expected: " << root.arity - << ", got: " << other_root.arity << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("Custom type arity mismatch; expected: {}, got: {}.", + root.arity, + other_root.arity)); } { const scoped_critical_section2 cs{root.node_data, other_root.node_data}; if (root.node_data.not_equal(other_root.node_data)) [[unlikely]] { - std::ostringstream oss{}; - oss << "Mismatch custom node data; expected: " << PyRepr(root.node_data) - << ", got: " << PyRepr(other_root.node_data) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("Mismatch custom node data; expected: {}, got: {}.", + root.node_data, + other_root.node_data)); } } break; @@ -410,10 +411,10 @@ std::unique_ptr PyTreeSpec::BroadcastToCommonSuffix(const PyTreeSpec } if (!m_namespace.empty() && !other.m_namespace.empty() && m_namespace != other.m_namespace) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTreeSpecs must have the same namespace, got " << PyRepr(m_namespace) << " vs. " - << PyRepr(other.m_namespace) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("PyTreeSpecs must have the same namespace, got {} vs. {}.", + PyRepr(m_namespace), + PyRepr(other.m_namespace))); } auto treespec = std::make_unique(); @@ -473,10 +474,11 @@ std::unique_ptr PyTreeSpec::Transform(const std::optional(out)) [[unlikely]] { - std::ostringstream oss{}; - oss << "Expected the PyTreeSpec transform function returns a PyTreeSpec, got " - << PyRepr(out) << " (input: " << GetOneLevel(node)->ToString() << ")."; - throw py::type_error(oss.str()); + throw py::type_error( + std::format("Expected the PyTreeSpec transform function returns a PyTreeSpec, " + "got {} (input: {}).", + out, + GetOneLevel(node)->ToString())); } return std::make_unique(thread_safe_cast(out)); }; @@ -489,42 +491,39 @@ std::unique_ptr PyTreeSpec::Transform(const std::optionalm_none_is_leaf != m_none_is_leaf) [[unlikely]] { - std::ostringstream oss{}; - oss << "Expected the PyTreeSpec transform function returns " - "a PyTreeSpec with the same value of " - << (m_none_is_leaf ? "`none_is_leaf=True`" : "`none_is_leaf=False`") - << " as the input, got " << transformed->ToString() - << " (input: " << GetOneLevel(node)->ToString() << ")."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("Expected the PyTreeSpec transform function returns a PyTreeSpec " + "with the same value of {} as the input, got {} (input: {}).", + m_none_is_leaf ? "`none_is_leaf=True`" : "`none_is_leaf=False`", + transformed->ToString(), + GetOneLevel(node)->ToString())); } if (!transformed->m_namespace.empty()) [[unlikely]] { if (common_registry_namespace.empty()) [[likely]] { common_registry_namespace = transformed->m_namespace; } else if (transformed->m_namespace != common_registry_namespace) [[unlikely]] { - std::ostringstream oss{}; - oss << "Expected the PyTreeSpec transform function returns " - "a PyTreeSpec with namespace " - << PyRepr(common_registry_namespace) << ", got " - << PyRepr(transformed->m_namespace) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("Expected the PyTreeSpec transform function returns a PyTreeSpec " + "with namespace {}, got {}.", + PyRepr(common_registry_namespace), + PyRepr(transformed->m_namespace))); } } if (node.kind != PyTreeKind::Leaf) [[likely]] { if (transformed->GetNumLeaves() != node.arity) [[unlikely]] { - std::ostringstream oss{}; - oss << "Expected the PyTreeSpec transform function returns " - "a PyTreeSpec with the same number of arity as the input (" - << node.arity << "), got " << transformed->ToString() - << " (input: " << GetOneLevel(node)->ToString() << ")."; - throw py::value_error(oss.str()); + throw py::value_error(std::format( + "Expected the PyTreeSpec transform function returns a PyTreeSpec " + "with the same number of arity as the input ({}), got {} (input: {}).", + node.arity, + transformed->ToString(), + GetOneLevel(node)->ToString())); } if (transformed->GetNumNodes() != node.arity + 1) [[unlikely]] { - std::ostringstream oss{}; - oss << "Expected the PyTreeSpec transform function returns a one-level PyTreeSpec " - "as the input, got " - << transformed->ToString() << " (input: " << GetOneLevel(node)->ToString() - << ")."; - throw py::value_error(oss.str()); + throw py::value_error(std::format( + "Expected the PyTreeSpec transform function returns a one-level PyTreeSpec " + "as the input, got {} (input: {}).", + transformed->ToString(), + GetOneLevel(node)->ToString())); } auto &subroot = treespec->m_traversal.emplace_back(transformed->m_traversal.back()); EXPECT_GE(py::ssize_t_cast(pending_num_leaves_nodes.size()), @@ -581,10 +580,10 @@ std::unique_ptr PyTreeSpec::Compose(const PyTreeSpec &inner) const { } if (!m_namespace.empty() && !inner.m_namespace.empty() && m_namespace != inner.m_namespace) [[unlikely]] { - std::ostringstream oss{}; - oss << "PyTreeSpecs must have the same namespace, got " << PyRepr(m_namespace) << " vs. " - << PyRepr(inner.m_namespace) << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("PyTreeSpecs must have the same namespace, got {} vs. {}.", + PyRepr(m_namespace), + PyRepr(inner.m_namespace))); } auto treespec = std::make_unique(); diff --git a/src/treespec/unflatten.cpp b/src/treespec/unflatten.cpp index e5389857..8b18cdca 100644 --- a/src/treespec/unflatten.cpp +++ b/src/treespec/unflatten.cpp @@ -15,8 +15,8 @@ limitations under the License. ================================================================================ */ +#include // std::format #include // std::span -#include // std::ostringstream #include // std::move #include "optree/optree.h" @@ -36,10 +36,10 @@ py::object PyTreeSpec::UnflattenImpl(const Span &leaves) const { switch (node.kind) { case PyTreeKind::Leaf: { if (it == leaves.end()) [[unlikely]] { - std::ostringstream oss{}; - oss << "Too few leaves for PyTreeSpec; expected: " << GetNumLeaves() - << ", got: " << num_leaves << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("Too few leaves for PyTreeSpec; expected: {}, got: {}.", + GetNumLeaves(), + num_leaves)); } agenda.emplace_back(py::reinterpret_borrow(*it)); ++it; @@ -73,9 +73,8 @@ py::object PyTreeSpec::UnflattenImpl(const Span &leaves) const { } } if (it != leaves.end()) [[unlikely]] { - std::ostringstream oss{}; - oss << "Too many leaves for PyTreeSpec; expected: " << GetNumLeaves() << "."; - throw py::value_error(oss.str()); + throw py::value_error( + std::format("Too many leaves for PyTreeSpec; expected: {}.", GetNumLeaves())); } EXPECT_EQ(agenda.size(), 1, "PyTreeSpec traversal did not yield a singleton."); return agenda.back(); From 0b0899189c67642d799bf82582c40ecfe27519a0 Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Tue, 5 Aug 2025 04:00:22 +0800 Subject: [PATCH 7/7] chore(workflows): pass `{MACOSX,IPHONEOS}_DEPLOYMENT_TARGET` to `cibuildwheel` --- .github/workflows/build.yml | 2 ++ .github/workflows/tests-with-pydebug.yml | 1 + .github/workflows/tests.yml | 1 + 3 files changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 89605871..9f2ffd8c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,6 +44,8 @@ concurrency: env: OPTREE_CXX_WERROR: "OFF" _GLIBCXX_USE_CXX11_ABI: "1" + MACOSX_DEPLOYMENT_TARGET: "15.0" + IPHONEOS_DEPLOYMENT_TARGET: "16.3" PYTHONUNBUFFERED: "1" PYTHON_TAG: "py3" # to be updated PYTHON_VERSION: "3" # to be updated diff --git a/.github/workflows/tests-with-pydebug.yml b/.github/workflows/tests-with-pydebug.yml index 1d86e36a..ec88b418 100644 --- a/.github/workflows/tests-with-pydebug.yml +++ b/.github/workflows/tests-with-pydebug.yml @@ -36,6 +36,7 @@ env: CMAKE_BUILD_TYPE: "Debug" OPTREE_CXX_WERROR: "ON" _GLIBCXX_USE_CXX11_ABI: "1" + MACOSX_DEPLOYMENT_TARGET: "15.0" PYTHONUNBUFFERED: "1" PYTHON: "python" # to be updated PYTHON_TAG: "py3" # to be updated diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 52b254ac..36bcbffc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,6 +37,7 @@ env: CMAKE_BUILD_TYPE: "Debug" OPTREE_CXX_WERROR: "ON" _GLIBCXX_USE_CXX11_ABI: "1" + MACOSX_DEPLOYMENT_TARGET: "15.0" FULL_TEST_PYTHON_VERSIONS: "3.12;3.13" PYTHONUNBUFFERED: "1" PYTHON: "python" # to be updated