This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git
The following commit(s) were added to refs/heads/main by this push:
new a54d3f67 [FIX] Forward implicitly convertible values into UnchangedOr
(#782)
a54d3f67 is described below
commit a54d3f67fb1f9dd640c0c6f34bb4e513a9637459
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Sep 13 11:54:36 2026 -0400
[FIX] Forward implicitly convertible values into UnchangedOr (#782)
Allow values implicitly convertible to the replacement type to
initialize UnchangedOr directly, enabling bare object returns through
Expected<UnchangedOr<Any>>. The constrained C++17 forwarding constructor
materializes the replacement type and preserves dedicated unchanged,
error, and wrapper conversions.
---
include/tvm/ffi/extra/structural_mutate.h | 22 ++++++++++++++++++++++
tests/cpp/extra/test_structural_mutate.cc | 9 +++++++++
2 files changed, 31 insertions(+)
diff --git a/include/tvm/ffi/extra/structural_mutate.h
b/include/tvm/ffi/extra/structural_mutate.h
index 358866c2..601d1bc6 100644
--- a/include/tvm/ffi/extra/structural_mutate.h
+++ b/include/tvm/ffi/extra/structural_mutate.h
@@ -156,6 +156,12 @@ template <typename Parent>
class StructuralMutateDynEngine;
struct UnchangedOrUnsafe;
+
+template <typename T>
+inline constexpr bool is_unchanged_or_v = false;
+
+template <typename T>
+inline constexpr bool is_unchanged_or_v<UnchangedOr<T>> = true;
} // namespace details
/*! \brief Tag for a mutation result that produced no new value. */
@@ -220,6 +226,22 @@ class UnchangedOr {
// NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
TVM_FFI_INLINE UnchangedOr(T value) : data_(Any(std::move(value))) {}
+ /*!
+ * \brief Construct a changed result from an implicitly convertible
replacement value.
+ * \tparam U Source value type, implicitly convertible to T.
+ * \param value The replacement value to copy or move.
+ */
+ // Preserve the dedicated tag and wrapper routes. Subsumption applies only
to materialized
+ // wrapper storage; a bare value must first be implicitly convertible to T.
+ template <typename U, typename =
std::enable_if_t<!std::is_same_v<std::decay_t<U>, Unchanged> &&
+
!details::is_unchanged_or_v<std::decay_t<U>> &&
+
!details::is_expected_v<std::decay_t<U>> &&
+
!details::is_unexpected_v<std::decay_t<U>> &&
+ !std::is_base_of_v<Error,
std::decay_t<U>> &&
+ std::is_convertible_v<U,
T>>>
+ // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
+ TVM_FFI_INLINE UnchangedOr(U&& value) :
data_(Any(T(std::forward<U>(value)))) {}
+
/*!
* \brief Implicit converting constructor from another replacement type.
* \tparam U Source replacement type whose storage is subsumed by or
implicitly convertible to T.
diff --git a/tests/cpp/extra/test_structural_mutate.cc
b/tests/cpp/extra/test_structural_mutate.cc
index ad9724d4..2866d1ca 100644
--- a/tests/cpp/extra/test_structural_mutate.cc
+++ b/tests/cpp/extra/test_structural_mutate.cc
@@ -54,6 +54,15 @@ static_assert(
Expected<UnchangedOr<String>> ReturnTypedUnchangedExpected() noexcept { return
Unchanged(); }
+TEST(UnchangedOr, BareValueForwarding) {
+ TInt value(42);
+ auto convert = [](TInt& value) -> Expected<UnchangedOr<Any>> { return value;
};
+
EXPECT_TRUE(std::move(convert(value)).value().ValueUnchecked().same_as(value));
+ UnchangedOr<double> numeric = 42;
+ EXPECT_EQ(AnyView(numeric).type_index(), TypeIndex::kTVMFFIFloat);
+ EXPECT_DOUBLE_EQ(std::move(numeric).ValueUnchecked(), 42.0);
+}
+
TEST(UnchangedOr, ConversionsAndAssignmentMacro) {
static_assert(!std::is_convertible_v<UnchangedOr<Any>, UnchangedOr<int>>);
static_assert(type_subsumes_v<Expected<UnchangedOr<TNumber>>,
Expected<UnchangedOr<TInt>>>);