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 98d0029 [FEAT][FFI] Add ostream operator<< for
Any/ObjectRef/Variant/Optional (#596)
98d0029 is described below
commit 98d0029dd4e002da1516d43f9b92e792f139e709
Author: Tianqi Chen <[email protected]>
AuthorDate: Wed May 27 09:54:06 2026 -0400
[FEAT][FFI] Add ostream operator<< for Any/ObjectRef/Variant/Optional (#596)
## Summary
The four type-erased FFI value containers (`ffi::Any`, `ffi::ObjectRef`,
`ffi::Variant<Ts...>`, `ffi::Optional<T>`) have no `operator<<` for
`std::ostream`, requiring manual `.ToString()` / `ReprPrint()` calls.
This PR adds four inline overloads in
`include/tvm/ffi/extra/dataclass.h` that delegate to the existing
`ReprPrint(Any)` machinery, so `std::cout << x` and `LOG(INFO) << x`
just work.
- `operator<<(ostream&, const Any&)` — delegates to `ReprPrint(value)`
- `operator<<(ostream&, const ObjectRef&)` — delegates to
`ReprPrint(Any(value))`; derived types still print their type-specific
repr via runtime `type_index` dispatch
- `operator<<(ostream&, const Variant<Ts...>&)` — delegates to
`ReprPrint(Any(value))`
- `operator<<(ostream&, const Optional<T>&)` — prints `"None"` for empty
(Python convention), otherwise `ReprPrint(Any(value))`
Pre-existing `String` and `Shape` `operator<<` overloads are unaffected:
`Shape` (a derived `ObjectRef`) wins over the generic `const ObjectRef&`
overload via overload resolution; `String` is not an `ObjectRef`
subclass and has no conflict.
---
include/tvm/ffi/extra/dataclass.h | 28 ++++++
tests/cpp/extra/test_dataclass.cc | 193 ++++++++++++++++++++++++++++++++++++++
2 files changed, 221 insertions(+)
diff --git a/include/tvm/ffi/extra/dataclass.h
b/include/tvm/ffi/extra/dataclass.h
index bfd95e5..2c3e566 100644
--- a/include/tvm/ffi/extra/dataclass.h
+++ b/include/tvm/ffi/extra/dataclass.h
@@ -24,9 +24,13 @@
#define TVM_FFI_EXTRA_DATACLASS_H_
#include <tvm/ffi/any.h>
+#include <tvm/ffi/container/variant.h>
#include <tvm/ffi/extra/base.h>
+#include <tvm/ffi/optional.h>
#include <tvm/ffi/string.h>
+#include <ostream>
+
namespace tvm {
namespace ffi {
@@ -108,6 +112,30 @@ TVM_FFI_EXTRA_CXX_API bool RecursiveGt(const Any& lhs,
const Any& rhs);
*/
TVM_FFI_EXTRA_CXX_API bool RecursiveGe(const Any& lhs, const Any& rhs);
+// std::ostream overloads
+
+/*! \brief Stream an ffi::Any using its canonical repr form. */
+inline std::ostream& operator<<(std::ostream& os, const Any& value) {
+ return os << ReprPrint(value);
+}
+
+/*! \brief Stream an ffi::ObjectRef using its canonical repr form. */
+inline std::ostream& operator<<(std::ostream& os, const ObjectRef& value) {
+ return os << ReprPrint(Any(value));
+}
+
+/*! \brief Stream an ffi::Variant<...> using its canonical repr form. */
+template <typename... V>
+inline std::ostream& operator<<(std::ostream& os, const Variant<V...>& value) {
+ return os << ReprPrint(Any(value));
+}
+
+/*! \brief Stream an ffi::Optional<T> using its canonical repr form. */
+template <typename T>
+inline std::ostream& operator<<(std::ostream& os, const Optional<T>& value) {
+ return os << ReprPrint(Any(value));
+}
+
} // namespace ffi
} // namespace tvm
#endif // TVM_FFI_EXTRA_DATACLASS_H_
diff --git a/tests/cpp/extra/test_dataclass.cc
b/tests/cpp/extra/test_dataclass.cc
new file mode 100644
index 0000000..4e7e21a
--- /dev/null
+++ b/tests/cpp/extra/test_dataclass.cc
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+#include <gtest/gtest.h>
+#include <tvm/ffi/any.h>
+#include <tvm/ffi/container/shape.h>
+#include <tvm/ffi/container/variant.h>
+#include <tvm/ffi/extra/dataclass.h>
+#include <tvm/ffi/object.h>
+#include <tvm/ffi/optional.h>
+#include <tvm/ffi/string.h>
+
+#include <sstream>
+
+#include "../testing_object.h"
+
+namespace {
+
+using namespace tvm::ffi;
+using namespace tvm::ffi::testing;
+
+// TIntObj::RegisterReflection() is called from test_reflection.cc (same
binary).
+
+// ---------------------------------------------------------------------------
+// Any << overload
+// ---------------------------------------------------------------------------
+
+TEST(OstreamAny, Primitives) {
+ {
+ std::ostringstream os;
+ os << Any(int64_t{42});
+ EXPECT_EQ(os.str(), "42");
+ }
+ {
+ std::ostringstream os;
+ os << Any(3.14);
+ EXPECT_EQ(os.str(), "3.14");
+ }
+ {
+ std::ostringstream os;
+ os << Any(true);
+ EXPECT_EQ(os.str(), "True");
+ }
+ {
+ std::ostringstream os;
+ os << Any(nullptr);
+ EXPECT_EQ(os.str(), "None");
+ }
+ {
+ std::ostringstream os;
+ os << Any(String("hello"));
+ EXPECT_EQ(os.str(), "\"hello\"");
+ }
+}
+
+TEST(OstreamAny, ObjectRef) {
+ std::ostringstream os;
+ os << Any(TInt(5));
+ EXPECT_EQ(os.str(), "test.Int(value=5)");
+}
+
+// ---------------------------------------------------------------------------
+// ObjectRef << overload
+// ---------------------------------------------------------------------------
+
+TEST(OstreamObjectRef, Direct) {
+ std::ostringstream os;
+ os << TInt(7);
+ EXPECT_EQ(os.str(), "test.Int(value=7)");
+}
+
+TEST(OstreamObjectRef, DerivedSlicing) {
+ // Slice derived TInt into ObjectRef base — runtime type must survive
+ TInt ti(7);
+ const ObjectRef& base = ti;
+ std::ostringstream os;
+ os << base;
+ EXPECT_EQ(os.str(), "test.Int(value=7)");
+}
+
+TEST(OstreamObjectRef, NullObjectRef) {
+ TNumber n; // default-constructed, null
+ std::ostringstream os;
+ os << n;
+ EXPECT_EQ(os.str(), "None");
+}
+
+TEST(OstreamObjectRef, ExistingShape) {
+ // Shape's own operator<< must still win — otherwise repr would include type
prefix
+ Shape s({1, 2, 3});
+ std::ostringstream os;
+ os << s;
+ // Shape's operator<< writes [1, 2, 3]
+ EXPECT_EQ(os.str(), "[1, 2, 3]");
+}
+
+TEST(OstreamObjectRef, ExistingString) {
+ // String's own operator<< writes raw content (no quotes), not repr-quoted
+ String str("abc");
+ std::ostringstream os;
+ os << str;
+ EXPECT_EQ(os.str(), "abc");
+}
+
+// ---------------------------------------------------------------------------
+// Variant << overload
+// ---------------------------------------------------------------------------
+
+TEST(OstreamVariant, IntAlternative) {
+ Variant<int64_t, String, TInt> v(int64_t{42});
+ std::ostringstream os;
+ os << v;
+ EXPECT_EQ(os.str(), "42");
+}
+
+TEST(OstreamVariant, StringAlternative) {
+ Variant<int64_t, String, TInt> v(String("hi"));
+ std::ostringstream os;
+ os << v;
+ // Goes through Any/ReprPrint so strings are repr-quoted
+ EXPECT_EQ(os.str(), "\"hi\"");
+}
+
+TEST(OstreamVariant, ObjectRefAlternative) {
+ Variant<int64_t, String, TInt> v(TInt(9));
+ std::ostringstream os;
+ os << v;
+ EXPECT_EQ(os.str(), "test.Int(value=9)");
+}
+
+// ---------------------------------------------------------------------------
+// Optional << overload
+// ---------------------------------------------------------------------------
+
+TEST(OstreamOptional, IntPresent) {
+ Optional<int64_t> o(int64_t{42});
+ std::ostringstream os;
+ os << o;
+ EXPECT_EQ(os.str(), "42");
+}
+
+TEST(OstreamOptional, IntEmpty) {
+ Optional<int64_t> o(std::nullopt);
+ std::ostringstream os;
+ os << o;
+ EXPECT_EQ(os.str(), "None");
+}
+
+TEST(OstreamOptional, StringPresent) {
+ Optional<String> o(String("x"));
+ std::ostringstream os;
+ os << o;
+ // Goes through Any/ReprPrint so strings are repr-quoted
+ EXPECT_EQ(os.str(), "\"x\"");
+}
+
+TEST(OstreamOptional, StringEmpty) {
+ Optional<String> o(std::nullopt);
+ std::ostringstream os;
+ os << o;
+ EXPECT_EQ(os.str(), "None");
+}
+
+TEST(OstreamOptional, ObjectRefPresent) {
+ Optional<TInt> o(TInt(3));
+ std::ostringstream os;
+ os << o;
+ EXPECT_EQ(os.str(), "test.Int(value=3)");
+}
+
+TEST(OstreamOptional, ObjectRefEmpty) {
+ Optional<TInt> o;
+ std::ostringstream os;
+ os << o;
+ EXPECT_EQ(os.str(), "None");
+}
+
+} // namespace