blob: 0c4b066a45ff9b052e3a75b1c372f0f50b515a91 [file]
/*
* 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/cast.h>
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/container/dict.h>
#include <tvm/ffi/container/list.h>
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/container/tuple.h>
#include <tvm/ffi/container/variant.h>
#include <tvm/ffi/expected.h>
#include <tvm/ffi/memory.h>
#include <tvm/ffi/object.h>
#include <tvm/ffi/optional.h>
#include <stdexcept>
#include <type_traits>
#include "./testing_object.h"
namespace tvm {
namespace ffi {
namespace testing {
class TIntOrFloatRef : public ObjectRef {
public:
TIntOrFloatRef() = default;
explicit TIntOrFloatRef(UnsafeInit tag) : ObjectRef(tag) {}
static constexpr bool _type_is_nullable = true;
static constexpr bool _type_container_is_exact = false;
using ContainerType = Object;
};
} // namespace testing
template <>
struct TypeTraits<testing::TIntOrFloatRef>
: public ObjectRefTypeTraitsBase<testing::TIntOrFloatRef> {
TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) {
return TypeTraits<testing::TInt>::CheckAnyStrict(src) ||
TypeTraits<testing::TFloat>::CheckAnyStrict(src);
}
};
template <typename ObjectType>
inline constexpr bool object_ref_contains_v<testing::TIntOrFloatRef, ObjectType> =
std::is_base_of_v<testing::TIntObj, ObjectType> ||
std::is_base_of_v<testing::TFloatObj, ObjectType>;
} // namespace ffi
} // namespace tvm
namespace {
using namespace tvm::ffi;
using namespace tvm::ffi::testing;
template <typename RefType, typename ObjectType, typename = void>
inline constexpr bool object_ref_contains_is_enabled_v = false;
template <typename RefType, typename ObjectType>
inline constexpr bool object_ref_contains_is_enabled_v<
RefType, ObjectType, std::void_t<decltype(object_ref_contains_v<RefType, ObjectType>)>> = true;
static_assert(ObjectRef::_type_container_is_exact);
static_assert(TNumber::_type_container_is_exact);
static_assert(TInt::_type_container_is_exact);
// ObjectRef optionals retain the original ObjectRef/ObjectPtr representation
// and continue to participate in the ObjectRef container concept.
static_assert(std::is_base_of_v<ObjectRef, Optional<TInt>>);
static_assert(!TIntOrFloatRef::_type_container_is_exact);
static_assert(!Array<TInt>::_type_container_is_exact);
static_assert(!List<TInt>::_type_container_is_exact);
static_assert(!Map<TInt, TFloat>::_type_container_is_exact);
static_assert(!Dict<TInt, TFloat>::_type_container_is_exact);
static_assert(!Tuple<TInt, TFloat>::_type_container_is_exact);
static_assert(!Variant<TInt, TFloat>::_type_container_is_exact);
static_assert(object_ref_contains_v<TNumber, TIntObj>);
static_assert(object_ref_contains_v<TInt, TIntObj>);
static_assert(object_ref_contains_v<Optional<TInt>, TIntObj>);
static_assert(!object_ref_contains_v<TInt, TFloatObj>);
static_assert(!object_ref_contains_v<Array<TInt>, ArrayObj>);
static_assert(object_ref_contains_v<TIntOrFloatRef, TIntObj>);
static_assert(object_ref_contains_v<TIntOrFloatRef, TFloatObj>);
static_assert(!object_ref_contains_v<TIntOrFloatRef, TNumberObj>);
static_assert(object_ref_contains_is_enabled_v<TInt, TIntObj>);
static_assert(object_ref_contains_is_enabled_v<Optional<TInt>, TIntObj>);
static_assert(object_ref_contains_is_enabled_v<TIntOrFloatRef, TIntObj>);
static_assert(!object_ref_contains_is_enabled_v<int, TIntObj>);
static_assert(!object_ref_contains_is_enabled_v<TIntObj, TIntObj>);
static_assert(!object_ref_contains_is_enabled_v<TInt, int>);
static_assert(!object_ref_contains_is_enabled_v<TIntOrFloatRef, int>);
template <typename T>
class CRTPObject : public Object {
public:
static constexpr int _type_child_slots [[maybe_unused]] = 0;
static constexpr bool _type_final [[maybe_unused]] = true;
TVM_FFI_DECLARE_OBJECT_INFO_PREDEFINED_TYPE_KEY(T, Object);
private:
friend T;
CRTPObject() = default;
};
class LeafObject : public CRTPObject<LeafObject> {
public:
static constexpr const char* _type_key = "test.CRTPLeaf";
};
class ThrowingConstructorObject : public Object {
public:
ThrowingConstructorObject() { throw std::runtime_error("constructor failed"); }
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.ThrowingConstructorObject", ThrowingConstructorObject,
Object);
};
TEST(Object, RefCounter) {
ObjectPtr<TIntObj> a = make_object<TIntObj>(11);
ObjectPtr<TIntObj> b = a;
EXPECT_EQ(a->value, 11);
EXPECT_EQ(a.use_count(), 2);
ObjectPtr<TIntObj> aa = make_object<TIntObj>(*a);
EXPECT_EQ(aa.use_count(), 1);
EXPECT_EQ(aa->value, 11);
b.reset();
EXPECT_EQ(a.use_count(), 1);
EXPECT_TRUE(b == nullptr);
EXPECT_EQ(b.use_count(), 0);
ObjectPtr<TIntObj> c = std::move(a);
EXPECT_EQ(c.use_count(), 1);
EXPECT_TRUE(a == nullptr); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
EXPECT_EQ(c->value, 11);
}
TEST(Object, MakeObjectPropagatesConstructorException) {
EXPECT_THROW(make_object<ThrowingConstructorObject>(), std::runtime_error);
}
TEST(Object, TypeInfo) {
const TypeInfo* info = TVMFFIGetTypeInfo(TIntObj::RuntimeTypeIndex());
EXPECT_TRUE(info != nullptr);
EXPECT_EQ(info->type_index, TIntObj::RuntimeTypeIndex());
EXPECT_EQ(info->type_depth, 2);
EXPECT_EQ(info->type_ancestors[0]->type_index, Object::RuntimeTypeIndex());
EXPECT_EQ(info->type_ancestors[1]->type_index, TNumberObj::RuntimeTypeIndex());
EXPECT_GE(info->type_index, TypeIndex::kTVMFFIDynObjectBegin);
}
TEST(Object, CRTPObjectInfo) {
const TypeInfo* info = TVMFFIGetTypeInfo(LeafObject::RuntimeTypeIndex());
ASSERT_TRUE(info != nullptr);
EXPECT_EQ(info->type_index, LeafObject::RuntimeTypeIndex());
EXPECT_EQ(info->type_depth, 1);
EXPECT_EQ(info->type_ancestors[0]->type_index, Object::RuntimeTypeIndex());
EXPECT_GE(info->type_index, TypeIndex::kTVMFFIDynObjectBegin);
}
TEST(Object, TypeGetOrAllocIndexQueryRegistered) {
TVMFFIByteArray type_key{TIntObj::_type_key, std::char_traits<char>::length(TIntObj::_type_key)};
EXPECT_EQ(TVMFFITypeGetOrAllocIndex(&type_key, -1, 0, 0, 0, -2), TIntObj::RuntimeTypeIndex());
}
TEST(Object, TypeGetOrAllocIndexQueryMissDoesNotRegister) {
const char* type_key_data = "test.TypeGetOrAllocIndexQueryMiss";
TVMFFIByteArray type_key{type_key_data, std::char_traits<char>::length(type_key_data)};
EXPECT_EQ(TVMFFITypeGetOrAllocIndex(&type_key, -1, 0, 0, 0, -2), -2);
int32_t type_index = -1;
EXPECT_NE(TVMFFITypeKeyToIndex(&type_key, &type_index), 0);
EXPECT_EQ(type_index, -1);
}
TEST(Object, InstanceCheck) {
ObjectPtr<Object> a = make_object<TIntObj>(11);
ObjectPtr<Object> b = make_object<TFloatObj>(11);
EXPECT_TRUE(a->IsInstance<Object>());
EXPECT_TRUE(a->IsInstance<TNumberObj>());
EXPECT_TRUE(a->IsInstance<TIntObj>());
EXPECT_TRUE(!a->IsInstance<TFloatObj>());
EXPECT_TRUE(a->IsInstance<Object>());
EXPECT_TRUE(b->IsInstance<TNumberObj>());
EXPECT_TRUE(!b->IsInstance<TIntObj>());
EXPECT_TRUE(b->IsInstance<TFloatObj>());
}
TEST(ObjectRef, as) {
ObjectRef a = TInt(10);
ObjectRef b = TFloat(20);
// nullable object
ObjectRef c(nullptr);
EXPECT_TRUE(a.as<TIntObj>() != nullptr);
EXPECT_TRUE(a.as<TFloatObj>() == nullptr);
EXPECT_TRUE(a.as<TNumberObj>() != nullptr);
EXPECT_TRUE(b.as<TIntObj>() == nullptr);
EXPECT_TRUE(b.as<TFloatObj>() != nullptr);
EXPECT_TRUE(b.as<TNumberObj>() != nullptr);
EXPECT_TRUE(c.as<TIntObj>() == nullptr);
EXPECT_TRUE(c.as<TFloatObj>() == nullptr);
EXPECT_TRUE(c.as<TNumberObj>() == nullptr);
auto null_number = c.as<TNumber>();
ASSERT_TRUE(null_number.has_value()) << "Expected nullable null ObjectRef cast to succeed";
EXPECT_TRUE(!(*null_number).defined()); // NOLINT(bugprone-unchecked-optional-access)
EXPECT_TRUE(!c.as<TInt>().has_value());
EXPECT_EQ(a.as<TIntObj>()->value, 10);
EXPECT_EQ(b.as<TFloatObj>()->value, 20);
}
TEST(ObjectRef, AsUsesTypeTraitsCheckAnyStrict) {
ObjectRef a = TInt(10);
ObjectRef b = TFloat(20);
auto int_like = a.as<TIntOrFloatRef>();
ASSERT_TRUE(int_like.has_value()) << "Expected TIntOrFloatRef cast from TInt to succeed";
EXPECT_TRUE((*int_like).as<TIntObj>() != nullptr); // NOLINT(bugprone-unchecked-optional-access)
auto float_like = b.as<TIntOrFloatRef>();
ASSERT_TRUE(float_like.has_value()) << "Expected TIntOrFloatRef cast from TFloat to succeed";
EXPECT_NE((*float_like).as<TFloatObj>(), nullptr); // NOLINT(bugprone-unchecked-optional-access)
}
TEST(ObjectRef, GetRefUsesObjectRefContainment) {
ObjectPtr<TIntObj> int_object = make_object<TIntObj>(10);
TIntOrFloatRef int_or_float = GetRef<TIntOrFloatRef>(int_object.get());
ASSERT_NE(int_or_float.as<TIntObj>(), nullptr);
EXPECT_EQ(int_or_float.as<TIntObj>()->value, 10);
}
TEST(ObjectRef, AsOrThrow) {
ObjectRef a = TInt(10);
ObjectRef b = TFloat(20);
ObjectRef c(nullptr);
const ObjectRef const_a = TInt(30);
ObjectRef movable_as = TInt(40);
ObjectRef movable_as_or_throw = TInt(50);
EXPECT_EQ(a.as<TIntObj>()->value, 10);
EXPECT_EQ(a.as_or_throw<TInt>()->value, 10);
EXPECT_EQ(b.as<TFloatObj>()->value, 20);
EXPECT_TRUE(!c.as_or_throw<TNumber>().defined());
auto const_as = const_a.as<TInt>();
ASSERT_TRUE(const_as.has_value()) << "Expected const ObjectRef as<TInt>() to succeed";
EXPECT_EQ((*const_as).get()->value, 30); // NOLINT(bugprone-unchecked-optional-access)
EXPECT_EQ(const_a.as_or_throw<TInt>()->value, 30);
auto moved_as = std::move(movable_as).as<TInt>();
ASSERT_TRUE(moved_as.has_value()) << "Expected rvalue ObjectRef as<TInt>() to succeed";
EXPECT_EQ((*moved_as).get()->value, 40); // NOLINT(bugprone-unchecked-optional-access)
// NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
EXPECT_FALSE(movable_as.defined());
EXPECT_EQ(std::move(movable_as_or_throw).as_or_throw<TInt>()->value, 50);
// NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
EXPECT_FALSE(movable_as_or_throw.defined());
EXPECT_THROW(
{
try {
[[maybe_unused]] auto value = a.as_or_throw<TFloat>();
} catch (const Error& error) {
EXPECT_EQ(error.kind(), "TypeError");
std::string what = error.what();
EXPECT_NE(what.find("Cannot treat type `test.Int` as type `test.Float`"),
std::string::npos);
throw;
}
},
::tvm::ffi::Error);
EXPECT_THROW(
{
try {
[[maybe_unused]] auto value = c.as_or_throw<TInt>();
} catch (const Error& error) {
EXPECT_EQ(error.kind(), "TypeError");
std::string what = error.what();
EXPECT_NE(what.find("Cannot treat type `None` as type `test.Int`"), std::string::npos);
throw;
}
},
::tvm::ffi::Error);
}
TEST(ObjectRef, UnsafeInit) {
ObjectRef a(UnsafeInit{});
EXPECT_TRUE(a.get() == nullptr);
TInt b(UnsafeInit{});
EXPECT_TRUE(b.get() == nullptr);
}
TEST(Object, CAPIAccessor) {
ObjectRef a = TInt(10);
TVMFFIObjectHandle obj = details::ObjectUnsafe::RawObjectPtrFromObjectRef(a);
int32_t type_index = TVMFFIObjectGetTypeIndex(obj);
EXPECT_EQ(type_index, TIntObj::RuntimeTypeIndex());
}
TEST(Object, WeakObjectPtr) {
// Test basic construction from ObjectPtr
ObjectPtr<TIntObj> strong_ptr = make_object<TIntObj>(42);
WeakObjectPtr<TIntObj> weak_ptr(strong_ptr);
EXPECT_EQ(strong_ptr.use_count(), 1);
EXPECT_FALSE(weak_ptr.expired());
EXPECT_EQ(weak_ptr.use_count(), 1);
// Test lock() when object is still alive
ObjectPtr<TIntObj> locked_ptr = weak_ptr.lock();
EXPECT_TRUE(locked_ptr != nullptr);
EXPECT_EQ(locked_ptr->value, 42);
EXPECT_EQ(strong_ptr.use_count(), 2);
EXPECT_EQ(weak_ptr.use_count(), 2);
// Test lock() when object is expired
strong_ptr.reset();
locked_ptr.reset();
EXPECT_TRUE(weak_ptr.expired());
EXPECT_EQ(weak_ptr.use_count(), 0);
ObjectPtr<TIntObj> expired_lock = weak_ptr.lock();
EXPECT_TRUE(expired_lock == nullptr);
}
TEST(Object, WeakObjectPtrAssignment) {
// Test copy construction
ObjectPtr<TIntObj> new_strong = make_object<TIntObj>(100);
WeakObjectPtr<TIntObj> weak1(new_strong);
WeakObjectPtr<TIntObj> weak2(weak1);
EXPECT_EQ(new_strong.use_count(), 1);
EXPECT_FALSE(weak1.expired());
EXPECT_FALSE(weak2.expired());
EXPECT_EQ(weak1.use_count(), 1);
EXPECT_EQ(weak2.use_count(), 1);
// Test move construction
WeakObjectPtr<TIntObj> weak3(std::move(weak1));
// NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
EXPECT_TRUE(weak1.expired()); // weak1 should be moved from
EXPECT_FALSE(weak3.expired());
EXPECT_EQ(weak3.use_count(), 1);
// Test assignment
WeakObjectPtr<TIntObj> weak4;
weak4 = weak2;
EXPECT_FALSE(weak2.expired());
EXPECT_FALSE(weak4.expired());
EXPECT_EQ(weak2.use_count(), 1);
EXPECT_EQ(weak4.use_count(), 1);
// Test move assignment
WeakObjectPtr<TIntObj> weak5;
weak5 = std::move(weak2);
// NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
EXPECT_TRUE(weak2.expired()); // weak2 should be moved from
EXPECT_FALSE(weak5.expired());
EXPECT_EQ(weak5.use_count(), 1);
// Test reset()
weak3.reset();
EXPECT_TRUE(weak3.expired());
EXPECT_EQ(weak3.use_count(), 0);
// Test swap()
ObjectPtr<TIntObj> strong_a = make_object<TIntObj>(200);
ObjectPtr<TIntObj> strong_b = make_object<TIntObj>(300);
WeakObjectPtr<TIntObj> weak_a(strong_a);
WeakObjectPtr<TIntObj> weak_b(strong_b);
weak_a.swap(weak_b);
EXPECT_EQ(weak_a.lock()->value, 300);
EXPECT_EQ(weak_b.lock()->value, 200);
// Test construction from nullptr
WeakObjectPtr<TIntObj> null_weak(nullptr);
EXPECT_TRUE(null_weak.expired());
EXPECT_EQ(null_weak.use_count(), 0);
EXPECT_TRUE(null_weak.lock() == nullptr);
// Test inheritance compatibility
ObjectPtr<TNumberObj> number_ptr = make_object<TIntObj>(500);
WeakObjectPtr<TNumberObj> number_weak(number_ptr);
EXPECT_FALSE(number_weak.expired());
EXPECT_EQ(number_weak.use_count(), 1);
// Test that weak references don't prevent object deletion
ObjectPtr<TIntObj> temp_strong = make_object<TIntObj>(999);
WeakObjectPtr<TIntObj> temp_weak(temp_strong);
EXPECT_FALSE(temp_weak.expired());
temp_strong.reset();
EXPECT_TRUE(temp_weak.expired());
EXPECT_TRUE(temp_weak.lock() == nullptr);
// Test multiple weak references
ObjectPtr<TIntObj> multi_strong = make_object<TIntObj>(777);
WeakObjectPtr<TIntObj> multi_weak1(multi_strong);
WeakObjectPtr<TIntObj> multi_weak2(multi_strong);
WeakObjectPtr<TIntObj> multi_weak3(multi_strong);
EXPECT_EQ(multi_strong.use_count(), 1);
EXPECT_FALSE(multi_weak1.expired());
EXPECT_FALSE(multi_weak2.expired());
EXPECT_FALSE(multi_weak3.expired());
// All weak references should be able to lock
ObjectPtr<TIntObj> lock1 = multi_weak1.lock();
ObjectPtr<TIntObj> lock2 = multi_weak2.lock();
ObjectPtr<TIntObj> lock3 = multi_weak3.lock();
EXPECT_EQ(multi_strong.use_count(), 4);
EXPECT_EQ(lock1->value, 777);
EXPECT_EQ(lock2->value, 777);
EXPECT_EQ(lock3->value, 777);
}
TEST(Object, OpaqueObject) {
thread_local int deleter_trigger_counter = 0;
struct DummyOpaqueObject {
int value;
explicit DummyOpaqueObject(int value) : value(value) {}
static void Deleter(void* handle) {
deleter_trigger_counter++;
delete static_cast<DummyOpaqueObject*>(handle);
}
};
TVMFFIObjectHandle handle = nullptr;
TVM_FFI_CHECK_SAFE_CALL(TVMFFIObjectCreateOpaque(new DummyOpaqueObject(10), kTVMFFIOpaquePyObject,
DummyOpaqueObject::Deleter, &handle));
ObjectPtr<Object> a =
details::ObjectUnsafe::ObjectPtrFromOwned<Object>(static_cast<Object*>(handle));
EXPECT_EQ(a->type_index(), kTVMFFIOpaquePyObject);
EXPECT_EQ(static_cast<DummyOpaqueObject*>(TVMFFIOpaqueObjectGetCellPtr(a.get())->handle)->value,
10);
EXPECT_EQ(a.use_count(), 1);
EXPECT_EQ(deleter_trigger_counter, 0);
a.reset();
EXPECT_EQ(deleter_trigger_counter, 1);
}
using NumberPtr = ObjectPtr<TNumberObj>;
NumberPtr MakeInt(int64_t value) { return make_object<TIntObj>(value); }
NumberPtr MakeFloat(double value) { return make_object<TFloatObj>(value); }
TEST(ObjectPtrStorage, Array) {
NumberPtr first = MakeInt(1);
NumberPtr second = MakeFloat(2.0);
NumberPtr null;
Array<NumberPtr> array{first, null};
EXPECT_EQ(array.size(), 2U);
EXPECT_EQ(array[0], first);
EXPECT_EQ(array[1], nullptr);
array.Set(1, second);
array.push_back(null);
EXPECT_EQ(array[1], second);
EXPECT_EQ(array[2], nullptr);
size_t defined = 0;
for (NumberPtr item : array) {
defined += item != nullptr;
}
EXPECT_EQ(defined, 2U);
Array<NumberPtr> roundtrip = Any(array).cast<Array<NumberPtr>>();
EXPECT_TRUE(roundtrip.same_as(array));
EXPECT_EQ(roundtrip[0], first);
EXPECT_EQ(roundtrip[1], second);
EXPECT_EQ(roundtrip[2], nullptr);
ObjectPtr<TIntObj> derived = make_object<TIntObj>(3);
Array<ObjectPtr<TIntObj>> derived_array{derived};
Array<NumberPtr> upcast_array = derived_array;
EXPECT_TRUE(upcast_array.same_as(derived_array));
EXPECT_EQ(upcast_array[0].get(), static_cast<TNumberObj*>(derived.get()));
}
TEST(ObjectPtrStorage, List) {
NumberPtr first = MakeInt(1);
NumberPtr second = MakeFloat(2.0);
NumberPtr null;
List<NumberPtr> list{first, null};
list.Set(1, second);
list.insert(list.begin() + 1, null);
list.push_back(first);
EXPECT_EQ(list.size(), 4U);
EXPECT_EQ(list[0], first);
EXPECT_EQ(list[1], nullptr);
EXPECT_EQ(list[2], second);
EXPECT_EQ(list[3], first);
List<NumberPtr> roundtrip = Any(list).cast<List<NumberPtr>>();
EXPECT_TRUE(roundtrip.same_as(list));
roundtrip.Set(1, second);
EXPECT_EQ(list[1], second);
}
TEST(ObjectPtrStorage, Map) {
NumberPtr key = MakeInt(1);
NumberPtr value = MakeFloat(2.0);
NumberPtr null;
Map<NumberPtr, NumberPtr> map{{key, value}};
EXPECT_EQ(map.count(key), 1U);
EXPECT_EQ(map[key], value);
map.Set(key, null);
map.Set(null, key);
EXPECT_EQ(map[key], nullptr);
EXPECT_EQ(map[null], key);
size_t entries = 0;
for (const auto& [stored_key, stored_value] : map) {
if (stored_key == key) {
EXPECT_EQ(stored_value, nullptr);
}
if (stored_key == nullptr) {
EXPECT_EQ(stored_value, key);
}
++entries;
}
EXPECT_EQ(entries, 2U);
Map<NumberPtr, NumberPtr> roundtrip = Any(map).cast<Map<NumberPtr, NumberPtr>>();
EXPECT_TRUE(roundtrip.same_as(map));
EXPECT_EQ(roundtrip[null], key);
}
TEST(ObjectPtrStorage, Dict) {
NumberPtr key = MakeInt(1);
NumberPtr value = MakeFloat(2.0);
NumberPtr null;
Dict<NumberPtr, NumberPtr> dict{{key, value}, {null, key}};
Dict<NumberPtr, NumberPtr> alias = dict;
EXPECT_EQ(dict[key], value);
EXPECT_EQ(dict[null], key);
dict.Set(key, null);
EXPECT_EQ(alias[key], nullptr);
auto found = dict.Get(null);
ASSERT_TRUE(found.has_value());
EXPECT_EQ(found.value(), key); // NOLINT(bugprone-unchecked-optional-access)
Dict<NumberPtr, NumberPtr> roundtrip = Any(dict).cast<Dict<NumberPtr, NumberPtr>>();
EXPECT_TRUE(roundtrip.same_as(dict));
roundtrip.erase(null);
EXPECT_EQ(dict.count(null), 0U);
}
TEST(ObjectPtrStorage, Tuple) {
NumberPtr first = MakeInt(1);
NumberPtr second = MakeFloat(2.0);
NumberPtr null;
Tuple<NumberPtr, NumberPtr> tuple(first, null);
EXPECT_EQ(tuple.get<0>(), first);
EXPECT_EQ(tuple.get<1>(), nullptr);
tuple.Set<1>(second);
EXPECT_EQ(tuple.get<1>(), second);
Tuple<NumberPtr, NumberPtr> roundtrip = Any(tuple).cast<Tuple<NumberPtr, NumberPtr>>();
EXPECT_TRUE(roundtrip.same_as(tuple));
EXPECT_EQ(roundtrip.get<0>(), first);
EXPECT_EQ(roundtrip.get<1>(), second);
}
TEST(ObjectPtrStorage, Variant) {
using NumberOrInt = Variant<NumberPtr, int64_t>;
NumberPtr first = MakeInt(1);
NumberPtr null;
NumberOrInt variant = first;
EXPECT_EQ(variant.get<NumberPtr>(), first);
NumberOrInt roundtrip = Any(variant).cast<NumberOrInt>();
EXPECT_EQ(roundtrip.get<NumberPtr>(), first);
variant = int64_t{2};
EXPECT_EQ(variant.get<int64_t>(), 2);
variant = null;
EXPECT_EQ(variant.get<NumberPtr>(), nullptr);
}
TEST(ObjectPtrStorage, OptionalAndVariantComposition) {
using OptionalNumber = Optional<NumberPtr>;
using NestedOptionalNumber = Optional<OptionalNumber>;
using OptionalNumberOrInt = Variant<OptionalNumber, int64_t>;
using OptionalNumberOrIntValue = Optional<Variant<NumberPtr, int64_t>>;
NumberPtr number = MakeInt(1);
OptionalNumber optional_number = number;
Array<OptionalNumber> array{optional_number, std::nullopt};
EXPECT_TRUE(array[0].has_value());
EXPECT_EQ(array[0].value(), number);
EXPECT_FALSE(array[1].has_value());
OptionalNumberOrInt variant = optional_number;
OptionalNumber variant_value = variant.get<OptionalNumber>();
ASSERT_TRUE(variant_value.has_value());
EXPECT_EQ(variant_value.value(), number);
OptionalNumberOrIntValue optional_variant = Variant<NumberPtr, int64_t>(number);
Any encoded = optional_variant;
OptionalNumberOrIntValue decoded = encoded.cast<OptionalNumberOrIntValue>();
ASSERT_TRUE(decoded.has_value());
EXPECT_EQ(decoded.value().get<NumberPtr>(), number);
OptionalNumber absent = std::nullopt;
OptionalNumber present_null = NumberPtr();
EXPECT_FALSE(Any(absent).cast<OptionalNumber>().has_value());
EXPECT_FALSE(Any(present_null).cast<OptionalNumber>().has_value());
NestedOptionalNumber nested = optional_number;
NestedOptionalNumber nested_roundtrip = Any(nested).cast<NestedOptionalNumber>();
ASSERT_TRUE(nested_roundtrip.has_value());
ASSERT_TRUE(nested_roundtrip.value().has_value());
EXPECT_EQ(nested_roundtrip.value().value(), number);
NestedOptionalNumber present_absent = OptionalNumber(std::nullopt);
EXPECT_FALSE(Any(present_absent).cast<NestedOptionalNumber>().has_value());
EXPECT_EQ(TypeTraits<OptionalNumber>::TypeSchema(),
R"({"type":"Optional","args":[{"type":"Optional","args":[{"type":"test.Number"}]}]})");
EXPECT_EQ(
TypeTraits<OptionalNumberOrInt>::TypeSchema(),
R"({"type":"Variant","args":[{"type":"Optional","args":[{"type":"Optional","args":[{"type":"test.Number"}]}]},{"type":"int"}]})");
}
TEST(ObjectPtrStorage, Expected) {
NumberPtr number = MakeInt(1);
Expected<NumberPtr> success = number;
EXPECT_TRUE(success.is_ok());
EXPECT_EQ(success.value(), number);
Expected<NumberPtr> success_roundtrip = Any(success).cast<Expected<NumberPtr>>();
EXPECT_TRUE(success_roundtrip.is_ok());
EXPECT_EQ(success_roundtrip.value(), number);
Expected<NumberPtr> failure = Error("ValueError", "expected failure", "");
Expected<NumberPtr> failure_roundtrip = Any(failure).cast<Expected<NumberPtr>>();
EXPECT_TRUE(failure_roundtrip.is_err());
EXPECT_EQ(failure_roundtrip.error().kind(), "ValueError");
}
using NumberArc = tvm::ffi::Arc<TNumberObj>;
NumberArc MakeArcInt(int64_t value) { return make_arc<TIntObj>(value); }
NumberArc MakeArcFloat(double value) { return make_arc<TFloatObj>(value); }
TEST(ArcStorage, Containers) {
NumberArc first = MakeArcInt(1);
NumberArc second = MakeArcFloat(2.0);
Array<NumberArc> array{first};
array.push_back(second);
EXPECT_EQ(array[1], second);
List<NumberArc> list{first};
list.push_back(second);
EXPECT_EQ(list[1], second);
Map<String, NumberArc> map{{"value", first}};
map.Set("value", second);
EXPECT_EQ(map["value"], second);
Dict<String, NumberArc> dict{{"value", first}};
dict.Set("value", second);
EXPECT_EQ(dict["value"], second);
Tuple<NumberArc, NumberArc> tuple(first, first);
tuple.Set<1>(second);
EXPECT_EQ(tuple.get<1>(), second);
Variant<NumberArc, int64_t> variant = int64_t{3};
variant = first;
EXPECT_EQ(variant.get<NumberArc>(), first);
Expected<NumberArc> expected{first};
ASSERT_TRUE(expected.is_ok());
EXPECT_EQ(expected.value(), first);
EXPECT_EQ(TypeTraits<List<NumberArc>>::TypeSchema(),
R"({"type":"ffi.List","args":[{"type":"test.Number"}]})");
EXPECT_EQ((TypeTraits<Map<String, NumberArc>>::TypeSchema()),
R"({"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"test.Number"}]})");
EXPECT_EQ((TypeTraits<Tuple<NumberArc, int64_t>>::TypeSchema()),
R"({"type":"Tuple","args":[{"type":"test.Number"},{"type":"int"}]})");
}
} // namespace