| /* |
| * 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/container/dict.h> |
| #include <tvm/ffi/tvm_ffi.h> |
| #include <tvm/ffi/type_traits.h> |
| |
| #include <type_traits> |
| #include <utility> |
| |
| namespace tvm { |
| namespace ffi { |
| namespace testing { |
| |
| class GeneratedBaseObj; |
| class GeneratedDerivedObj; |
| class GeneratedUnrelatedObj; |
| class MutualLeftObj; |
| class MutualRightObj; |
| |
| } // namespace testing |
| } // namespace ffi |
| } // namespace tvm |
| |
| template <> |
| inline constexpr bool tvm::ffi::is_object_subclass_v<::tvm::ffi::testing::MutualLeftObj> = true; |
| template <> |
| inline constexpr bool tvm::ffi::is_object_subclass_v<::tvm::ffi::testing::MutualRightObj> = true; |
| |
| static_assert(::tvm::ffi::details::storage_enabled_v< |
| ::tvm::ffi::ObjectPtr<::tvm::ffi::testing::MutualLeftObj>>); |
| static_assert(::tvm::ffi::details::storage_enabled_v< |
| ::tvm::ffi::ObjectPtr<::tvm::ffi::testing::MutualRightObj>>); |
| |
| namespace tvm { |
| namespace ffi { |
| namespace testing { |
| |
| class GeneratedBaseObj : public Object { |
| public: |
| TVM_FFI_DECLARE_OBJECT_INFO("testing.GeneratedBase", GeneratedBaseObj, Object); |
| }; |
| |
| class GeneratedDerivedObj : public GeneratedBaseObj { |
| public: |
| int64_t value; |
| |
| explicit GeneratedDerivedObj(int64_t value) : value(value) {} |
| |
| TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.GeneratedDerived", GeneratedDerivedObj, |
| GeneratedBaseObj); |
| }; |
| |
| class GeneratedUnrelatedObj : public Object { |
| public: |
| TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.GeneratedUnrelated", GeneratedUnrelatedObj, Object); |
| }; |
| |
| class MutualLeftObj : public Object { |
| public: |
| List<ObjectPtr<MutualRightObj>> right; |
| |
| TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.MutualLeft", MutualLeftObj, Object); |
| }; |
| |
| class MutualRightObj : public Object { |
| public: |
| List<ObjectPtr<MutualLeftObj>> left; |
| |
| TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.MutualRight", MutualRightObj, Object); |
| }; |
| |
| class CxxBaseObj : public Object { |
| public: |
| TVM_FFI_DECLARE_OBJECT_INFO("testing.CxxBase", CxxBaseObj, Object); |
| }; |
| |
| class CxxDerivedObj : public CxxBaseObj { |
| public: |
| TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.CxxDerived", CxxDerivedObj, CxxBaseObj); |
| }; |
| |
| class PointerAdjustmentPad { |
| public: |
| int64_t padding[4]; |
| }; |
| |
| class PointerAdjustedObj : public PointerAdjustmentPad, public CxxBaseObj { |
| public: |
| TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.PointerAdjusted", PointerAdjustedObj, CxxBaseObj); |
| }; |
| |
| } // namespace testing |
| } // namespace ffi |
| } // namespace tvm |
| |
| namespace { |
| |
| using tvm::ffi::Any; |
| using tvm::ffi::AnyView; |
| using tvm::ffi::Arc; |
| using tvm::ffi::Array; |
| using tvm::ffi::Dict; |
| using tvm::ffi::List; |
| using tvm::ffi::make_arc; |
| using tvm::ffi::make_object; |
| using tvm::ffi::Map; |
| using tvm::ffi::Object; |
| using tvm::ffi::ObjectPtr; |
| using tvm::ffi::ObjectRef; |
| using tvm::ffi::Optional; |
| using tvm::ffi::String; |
| using tvm::ffi::TypeTraits; |
| using tvm::ffi::UnsafeInit; |
| using tvm::ffi::testing::CxxBaseObj; |
| using tvm::ffi::testing::CxxDerivedObj; |
| using tvm::ffi::testing::GeneratedBaseObj; |
| using tvm::ffi::testing::GeneratedDerivedObj; |
| using tvm::ffi::testing::GeneratedUnrelatedObj; |
| using tvm::ffi::testing::MutualLeftObj; |
| using tvm::ffi::testing::MutualRightObj; |
| using tvm::ffi::testing::PointerAdjustedObj; |
| |
| template <typename T, typename = void> |
| struct HasPublicReset : std::false_type {}; |
| |
| template <typename T> |
| struct HasPublicReset<T, std::void_t<decltype(std::declval<T&>().reset())>> : std::true_type {}; |
| |
| template <typename T, typename = void> |
| struct HasPublicSwap : std::false_type {}; |
| |
| template <typename T> |
| struct HasPublicSwap<T, std::void_t<decltype(std::declval<T&>().swap(std::declval<T&>()))>> |
| : std::true_type {}; |
| |
| static_assert(tvm::ffi::is_object_subclass_v<GeneratedBaseObj>); |
| static_assert(tvm::ffi::is_object_subclass_v<GeneratedDerivedObj>); |
| static_assert(std::is_convertible_v<GeneratedDerivedObj*, GeneratedBaseObj*>); |
| static_assert(std::is_constructible_v<ObjectPtr<GeneratedBaseObj>, ObjectPtr<GeneratedDerivedObj>>); |
| static_assert(std::is_assignable_v<ObjectPtr<GeneratedBaseObj>&, ObjectPtr<GeneratedDerivedObj>>); |
| static_assert(tvm::ffi::is_object_subclass_v<CxxBaseObj>); |
| static_assert(tvm::ffi::is_object_subclass_v<CxxDerivedObj>); |
| static_assert(std::is_convertible_v<CxxDerivedObj*, CxxBaseObj*>); |
| static_assert(std::is_constructible_v<ObjectPtr<CxxBaseObj>, ObjectPtr<CxxDerivedObj>>); |
| static_assert(std::is_assignable_v<ObjectPtr<CxxBaseObj>&, ObjectPtr<CxxDerivedObj>>); |
| static_assert(!TypeTraits<ObjectPtr<int>>::storage_enabled); |
| static_assert(!tvm::ffi::details::storage_enabled_v<ObjectPtr<int>>); |
| static_assert( |
| tvm::ffi::type_subsumes_v<ObjectPtr<GeneratedBaseObj>, ObjectPtr<GeneratedDerivedObj>>); |
| static_assert( |
| !tvm::ffi::type_subsumes_v<ObjectPtr<GeneratedDerivedObj>, ObjectPtr<GeneratedBaseObj>>); |
| static_assert(sizeof(Arc<GeneratedDerivedObj>) == sizeof(ObjectPtr<GeneratedDerivedObj>)); |
| static_assert(alignof(Arc<GeneratedDerivedObj>) == alignof(ObjectPtr<GeneratedDerivedObj>)); |
| static_assert(std::is_standard_layout_v<Arc<GeneratedDerivedObj>>); |
| static_assert(std::is_base_of_v<ObjectPtr<GeneratedDerivedObj>, Arc<GeneratedDerivedObj>>); |
| static_assert(!std::is_default_constructible_v<Arc<GeneratedDerivedObj>>); |
| static_assert(!std::is_constructible_v<Arc<GeneratedDerivedObj>, std::nullptr_t>); |
| static_assert(std::is_constructible_v<Arc<GeneratedDerivedObj>, UnsafeInit>); |
| static_assert(!std::is_constructible_v<Arc<GeneratedDerivedObj>, ObjectPtr<GeneratedDerivedObj>>); |
| static_assert(!HasPublicReset<Arc<GeneratedDerivedObj>>::value); |
| static_assert(!HasPublicSwap<Arc<GeneratedDerivedObj>>::value); |
| static_assert(std::is_constructible_v<Arc<GeneratedBaseObj>, Arc<GeneratedDerivedObj>>); |
| static_assert(std::is_assignable_v<Arc<GeneratedBaseObj>&, Arc<GeneratedDerivedObj>>); |
| static_assert(std::is_constructible_v<ObjectPtr<GeneratedBaseObj>, Arc<GeneratedDerivedObj>>); |
| static_assert(!TypeTraits<Arc<int>>::storage_enabled); |
| static_assert(tvm::ffi::type_subsumes_v<Arc<GeneratedBaseObj>, Arc<GeneratedDerivedObj>>); |
| static_assert(tvm::ffi::type_subsumes_v<ObjectPtr<GeneratedBaseObj>, Arc<GeneratedDerivedObj>>); |
| static_assert(!tvm::ffi::type_subsumes_v<Arc<GeneratedBaseObj>, ObjectPtr<GeneratedDerivedObj>>); |
| static_assert( |
| !std::is_constructible_v<Array<Arc<GeneratedBaseObj>>, Array<ObjectPtr<GeneratedDerivedObj>>>); |
| |
| static_assert(std::is_same_v<Array<ObjectPtr<GeneratedDerivedObj>>::value_type, |
| ObjectPtr<GeneratedDerivedObj>>); |
| static_assert(std::is_same_v<List<ObjectPtr<GeneratedDerivedObj>>::value_type, |
| ObjectPtr<GeneratedDerivedObj>>); |
| static_assert(std::is_same_v<Map<String, ObjectPtr<GeneratedDerivedObj>>::mapped_type, |
| ObjectPtr<GeneratedDerivedObj>>); |
| static_assert(std::is_same_v<Dict<String, ObjectPtr<GeneratedDerivedObj>>::mapped_type, |
| ObjectPtr<GeneratedDerivedObj>>); |
| static_assert(std::is_same_v<Map<ObjectPtr<GeneratedDerivedObj>, String>::key_type, |
| ObjectPtr<GeneratedDerivedObj>>); |
| static_assert(std::is_same_v<Dict<ObjectPtr<GeneratedDerivedObj>, String>::key_type, |
| ObjectPtr<GeneratedDerivedObj>>); |
| static_assert( |
| std::is_same_v<List<ObjectPtr<MutualRightObj>>::value_type, ObjectPtr<MutualRightObj>>); |
| static_assert(std::is_same_v<List<ObjectPtr<MutualLeftObj>>::value_type, ObjectPtr<MutualLeftObj>>); |
| static_assert(std::is_constructible_v<Array<ObjectPtr<GeneratedBaseObj>>, |
| Array<ObjectPtr<GeneratedDerivedObj>>>); |
| static_assert(std::is_constructible_v<List<ObjectPtr<GeneratedBaseObj>>, |
| List<ObjectPtr<GeneratedDerivedObj>>>); |
| static_assert(std::is_constructible_v<Map<String, ObjectPtr<GeneratedBaseObj>>, |
| Map<String, ObjectPtr<GeneratedDerivedObj>>>); |
| static_assert(std::is_constructible_v<Dict<String, ObjectPtr<GeneratedBaseObj>>, |
| Dict<String, ObjectPtr<GeneratedDerivedObj>>>); |
| static_assert(!std::is_constructible_v<Array<ObjectPtr<GeneratedDerivedObj>>, |
| Array<ObjectPtr<GeneratedBaseObj>>>); |
| static_assert(sizeof(Array<ObjectPtr<GeneratedDerivedObj>>) == sizeof(ObjectPtr<Object>)); |
| static_assert(sizeof(List<ObjectPtr<GeneratedDerivedObj>>) == sizeof(ObjectPtr<Object>)); |
| static_assert(sizeof(Map<String, ObjectPtr<GeneratedDerivedObj>>) == sizeof(ObjectPtr<Object>)); |
| static_assert(sizeof(Dict<String, ObjectPtr<GeneratedDerivedObj>>) == sizeof(ObjectPtr<Object>)); |
| |
| TEST(ObjectPtr, NativeUpcastPreservesOwnershipAndPointer) { |
| ObjectPtr<GeneratedDerivedObj> derived = make_object<GeneratedDerivedObj>(42); |
| EXPECT_EQ(derived.use_count(), 1); |
| |
| ObjectPtr<GeneratedBaseObj> base = derived; |
| EXPECT_EQ(derived.use_count(), 2); |
| EXPECT_EQ(reinterpret_cast<const void*>(derived.get()), |
| reinterpret_cast<const void*>(base.get())); |
| |
| ObjectPtr<GeneratedDerivedObj> move_source = make_object<GeneratedDerivedObj>(43); |
| const void* move_source_address = move_source.get(); |
| ObjectPtr<GeneratedBaseObj> moved = std::move(move_source); |
| // ObjectPtr documents a null moved-from state, so inspecting it here is intentional. |
| EXPECT_TRUE(move_source == // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move) |
| nullptr); |
| EXPECT_EQ(moved.use_count(), 1); |
| EXPECT_EQ(reinterpret_cast<const void*>(moved.get()), move_source_address); |
| |
| ObjectPtr<GeneratedBaseObj> assigned; |
| assigned = derived; |
| EXPECT_EQ(derived.use_count(), 3); |
| EXPECT_EQ(reinterpret_cast<const void*>(derived.get()), |
| reinterpret_cast<const void*>(assigned.get())); |
| |
| EXPECT_EQ(static_cast<GeneratedBaseObj*>(derived.get()), base.get()); |
| const GeneratedDerivedObj* const_derived = derived.get(); |
| EXPECT_EQ(static_cast<const GeneratedBaseObj*>(const_derived), base.get()); |
| EXPECT_EQ(static_cast<GeneratedBaseObj*>(static_cast<GeneratedDerivedObj*>(nullptr)), nullptr); |
| EXPECT_EQ(derived.use_count(), 3); |
| } |
| |
| TEST(ObjectPtr, PhysicalUpcastConstructorsPreserveOwnership) { |
| ObjectPtr<CxxDerivedObj> derived = make_object<CxxDerivedObj>(); |
| ObjectPtr<CxxBaseObj> copied = derived; |
| EXPECT_EQ(derived.use_count(), 2); |
| |
| ObjectPtr<CxxBaseObj> moved = std::move(derived); |
| EXPECT_TRUE(derived == // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move) |
| nullptr); |
| EXPECT_EQ(copied.use_count(), 2); |
| EXPECT_EQ(moved.use_count(), 2); |
| |
| ObjectPtr<PointerAdjustedObj> adjusted = make_object<PointerAdjustedObj>(); |
| PointerAdjustedObj* adjusted_raw = adjusted.get(); |
| CxxBaseObj* adjusted_base_raw = adjusted_raw; |
| EXPECT_NE(static_cast<const void*>(adjusted_raw), static_cast<const void*>(adjusted_base_raw)); |
| |
| ObjectPtr<CxxBaseObj> adjusted_base = adjusted; |
| EXPECT_EQ(adjusted_base.get(), adjusted_base_raw); |
| EXPECT_EQ(adjusted.use_count(), 2); |
| } |
| |
| TEST(ObjectPtr, AnyRoundTripUsesRuntimeAncestry) { |
| ObjectPtr<GeneratedDerivedObj> derived = make_object<GeneratedDerivedObj>(7); |
| Any value = derived; |
| |
| ObjectPtr<GeneratedBaseObj> base = value.cast<ObjectPtr<GeneratedBaseObj>>(); |
| EXPECT_EQ(reinterpret_cast<const void*>(derived.get()), |
| reinterpret_cast<const void*>(base.get())); |
| EXPECT_FALSE(value.try_cast<ObjectPtr<GeneratedUnrelatedObj>>().has_value()); |
| |
| Any null_value = ObjectPtr<GeneratedDerivedObj>(nullptr); |
| EXPECT_EQ(null_value.type_index(), tvm::ffi::TypeIndex::kTVMFFINone); |
| EXPECT_EQ(null_value.cast<ObjectPtr<GeneratedBaseObj>>(), nullptr); |
| } |
| |
| TEST(Arc, ConstructionOwnershipAndUpcast) { |
| Arc<GeneratedDerivedObj> derived = make_arc<GeneratedDerivedObj>(42); |
| EXPECT_EQ(derived->value, 42); |
| EXPECT_EQ(derived.use_count(), 1); |
| |
| Arc<GeneratedDerivedObj> copied = derived; |
| EXPECT_EQ(derived.use_count(), 2); |
| EXPECT_EQ(copied.get(), derived.get()); |
| |
| Arc<GeneratedBaseObj> upcast = derived; |
| EXPECT_EQ(derived.use_count(), 3); |
| EXPECT_EQ(upcast.get(), static_cast<GeneratedBaseObj*>(derived.get())); |
| |
| copied = make_arc<GeneratedDerivedObj>(45); |
| EXPECT_EQ(derived.use_count(), 2); |
| EXPECT_EQ(copied->value, 45); |
| |
| Arc<GeneratedDerivedObj> move_source = make_arc<GeneratedDerivedObj>(43); |
| const void* move_source_address = move_source.get(); |
| Arc<GeneratedBaseObj> moved = std::move(move_source); |
| EXPECT_EQ(move_source, // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move) |
| nullptr); |
| EXPECT_EQ(moved.use_count(), 1); |
| EXPECT_EQ(static_cast<const void*>(moved.get()), move_source_address); |
| |
| Arc<GeneratedBaseObj> assigned = make_arc<GeneratedDerivedObj>(44); |
| assigned = derived; |
| EXPECT_EQ(assigned.get(), static_cast<GeneratedBaseObj*>(derived.get())); |
| EXPECT_EQ(derived.use_count(), 3); |
| } |
| |
| TEST(Arc, AnyRoundTripAndSchemas) { |
| Arc<GeneratedDerivedObj> derived = make_arc<GeneratedDerivedObj>(7); |
| using BasePtr = ObjectPtr<GeneratedBaseObj>; |
| using BaseArc = Arc<GeneratedBaseObj>; |
| static_assert(sizeof(Optional<BasePtr>) == sizeof(BasePtr)); |
| static_assert(alignof(Optional<BasePtr>) == alignof(BasePtr)); |
| static_assert(sizeof(Optional<BaseArc>) == sizeof(BasePtr)); |
| static_assert(alignof(Optional<BaseArc>) == alignof(BasePtr)); |
| |
| Any value = derived; |
| EXPECT_EQ(derived.use_count(), 2); |
| |
| Arc<GeneratedBaseObj> base = value.cast<Arc<GeneratedBaseObj>>(); |
| EXPECT_EQ(base.get(), static_cast<GeneratedBaseObj*>(derived.get())); |
| EXPECT_EQ(derived.use_count(), 3); |
| EXPECT_FALSE(value.try_cast<Arc<GeneratedUnrelatedObj>>().has_value()); |
| |
| Any none; |
| EXPECT_FALSE(none.as<Arc<GeneratedBaseObj>>().has_value()); |
| EXPECT_FALSE(none.try_cast<Arc<GeneratedBaseObj>>().has_value()); |
| EXPECT_THROW(none.cast<Arc<GeneratedBaseObj>>(), tvm::ffi::Error); |
| |
| EXPECT_EQ(tvm::ffi::TypeToRuntimeTypeIndex<Arc<GeneratedDerivedObj>>::v(), |
| GeneratedDerivedObj::RuntimeTypeIndex()); |
| EXPECT_EQ(TypeTraits<Arc<GeneratedBaseObj>>::TypeSchema(), R"({"type":"testing.GeneratedBase"})"); |
| EXPECT_EQ(TypeTraits<ObjectPtr<GeneratedBaseObj>>::TypeSchema(), |
| R"({"type":"Optional","args":[{"type":"testing.GeneratedBase"}]})"); |
| EXPECT_EQ(TypeTraits<Optional<Arc<GeneratedBaseObj>>>::TypeSchema(), |
| R"({"type":"Optional","args":[{"type":"testing.GeneratedBase"}]})"); |
| |
| BasePtr ptr = derived; |
| Optional<BasePtr> optional_ptr = ptr; |
| ASSERT_TRUE(optional_ptr.has_value()); |
| EXPECT_EQ(optional_ptr.get(), ptr.get()); |
| Optional<BasePtr> ptr_roundtrip = Any(optional_ptr).cast<Optional<BasePtr>>(); |
| ASSERT_TRUE(ptr_roundtrip.has_value()); |
| EXPECT_EQ(ptr_roundtrip.get(), ptr.get()); |
| BasePtr moved_ptr = std::move(optional_ptr).value(); |
| EXPECT_EQ(moved_ptr.get(), ptr.get()); |
| // NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move) |
| EXPECT_FALSE(optional_ptr.has_value()); |
| |
| Optional<Arc<GeneratedBaseObj>> present = Arc<GeneratedBaseObj>(derived); |
| ASSERT_TRUE(present.has_value()); |
| EXPECT_EQ(present.get(), static_cast<GeneratedBaseObj*>(derived.get())); |
| EXPECT_EQ(present.value().get(), static_cast<GeneratedBaseObj*>(derived.get())); |
| Arc<GeneratedBaseObj> moved_arc = std::move(present).value(); |
| EXPECT_EQ(moved_arc.get(), static_cast<GeneratedBaseObj*>(derived.get())); |
| // NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move) |
| EXPECT_FALSE(present.has_value()); |
| Optional<Arc<GeneratedBaseObj>> absent = std::nullopt; |
| EXPECT_FALSE(Any(absent).cast<Optional<Arc<GeneratedBaseObj>>>().has_value()); |
| } |
| |
| TEST(Arc, ContainerStorageAndValidation) { |
| Arc<GeneratedDerivedObj> first = make_arc<GeneratedDerivedObj>(1); |
| Arc<GeneratedDerivedObj> second = make_arc<GeneratedDerivedObj>(2); |
| |
| Array<Arc<GeneratedDerivedObj>> derived_array{first}; |
| Array<Arc<GeneratedBaseObj>> base_array = derived_array; |
| EXPECT_TRUE(base_array.same_as(derived_array)); |
| Array<ObjectPtr<GeneratedBaseObj>> nullable_base_array = derived_array; |
| EXPECT_TRUE(nullable_base_array.same_as(derived_array)); |
| |
| derived_array.push_back(second); |
| EXPECT_EQ(derived_array.size(), 2U); |
| |
| Array<ObjectPtr<GeneratedDerivedObj>> checked_source{first}; |
| Array<Arc<GeneratedBaseObj>> checked = Any(checked_source).cast<Array<Arc<GeneratedBaseObj>>>(); |
| // Runtime casting validates every nullable source element before reusing the storage. |
| EXPECT_TRUE(checked.same_as(checked_source)); |
| EXPECT_EQ(checked[0].get(), static_cast<GeneratedBaseObj*>(first.get())); |
| |
| Array<ObjectPtr<GeneratedDerivedObj>> nullable_source{nullptr, first}; |
| Any nullable_value = nullable_source; |
| EXPECT_FALSE(nullable_value.try_cast<Array<Arc<GeneratedBaseObj>>>().has_value()); |
| EXPECT_THROW(nullable_value.cast<Array<Arc<GeneratedBaseObj>>>(), tvm::ffi::Error); |
| |
| EXPECT_EQ(TypeTraits<Array<Arc<GeneratedDerivedObj>>>::TypeSchema(), |
| R"({"type":"ffi.Array","args":[{"type":"testing.GeneratedDerived"}]})"); |
| EXPECT_EQ( |
| TypeTraits<Array<ObjectPtr<GeneratedDerivedObj>>>::TypeSchema(), |
| R"({"type":"ffi.Array","args":[{"type":"Optional","args":[{"type":"testing.GeneratedDerived"}]}]})"); |
| } |
| |
| TEST(ObjectPtr, ContainerCovariance) { |
| ObjectPtr<GeneratedDerivedObj> first = make_object<GeneratedDerivedObj>(1); |
| ObjectPtr<GeneratedDerivedObj> second = make_object<GeneratedDerivedObj>(2); |
| |
| Array<ObjectPtr<GeneratedDerivedObj>> derived_array{first}; |
| Array<ObjectPtr<GeneratedBaseObj>> base_array = derived_array; |
| EXPECT_TRUE(base_array.same_as(derived_array)); |
| EXPECT_EQ(reinterpret_cast<const void*>(base_array[0].get()), |
| reinterpret_cast<const void*>(first.get())); |
| base_array.push_back(second); |
| EXPECT_FALSE(base_array.same_as(derived_array)); |
| EXPECT_EQ(derived_array.size(), 1); |
| |
| Map<String, ObjectPtr<GeneratedDerivedObj>> derived_map{{"first", first}}; |
| Map<String, ObjectPtr<GeneratedBaseObj>> base_map = derived_map; |
| EXPECT_TRUE(base_map.same_as(derived_map)); |
| base_map.Set("second", second); |
| EXPECT_FALSE(base_map.same_as(derived_map)); |
| EXPECT_EQ(derived_map.count("second"), 0); |
| |
| List<ObjectPtr<GeneratedDerivedObj>> derived_list{first}; |
| List<ObjectPtr<GeneratedBaseObj>> base_list = derived_list; |
| EXPECT_TRUE(base_list.same_as(derived_list)); |
| base_list.push_back(second); |
| EXPECT_EQ(derived_list.size(), 2); |
| |
| Dict<String, ObjectPtr<GeneratedDerivedObj>> derived_dict{{"first", first}}; |
| Dict<String, ObjectPtr<GeneratedBaseObj>> base_dict = derived_dict; |
| EXPECT_TRUE(base_dict.same_as(derived_dict)); |
| base_dict.Set("second", second); |
| EXPECT_EQ(derived_dict.count("second"), 1); |
| |
| List<ObjectPtr<GeneratedDerivedObj>> move_list_source{first}; |
| ObjectRef move_list_storage = move_list_source; |
| List<ObjectPtr<GeneratedBaseObj>> moved_list = std::move(move_list_source); |
| EXPECT_TRUE(moved_list.same_as(move_list_storage)); |
| EXPECT_FALSE(move_list_source.defined()); // NOLINT(bugprone-use-after-move) |
| EXPECT_EQ(moved_list.size(), 1); |
| |
| Dict<String, ObjectPtr<GeneratedDerivedObj>> move_dict_source{{"first", first}}; |
| ObjectRef move_dict_storage = move_dict_source; |
| Dict<String, ObjectPtr<GeneratedBaseObj>> moved_dict = std::move(move_dict_source); |
| EXPECT_TRUE(moved_dict.same_as(move_dict_storage)); |
| EXPECT_FALSE(move_dict_source.defined()); // NOLINT(bugprone-use-after-move) |
| EXPECT_EQ(moved_dict.size(), 1); |
| |
| Map<ObjectPtr<GeneratedDerivedObj>, String> derived_key_map{{first, "first"}}; |
| Map<ObjectPtr<GeneratedBaseObj>, String> base_key_map = derived_key_map; |
| EXPECT_TRUE(base_key_map.same_as(derived_key_map)); |
| EXPECT_EQ(base_key_map.at(first), "first"); |
| |
| Dict<ObjectPtr<GeneratedDerivedObj>, String> derived_key_dict{{first, "first"}}; |
| Dict<ObjectPtr<GeneratedBaseObj>, String> base_key_dict = derived_key_dict; |
| EXPECT_TRUE(base_key_dict.same_as(derived_key_dict)); |
| EXPECT_EQ(base_key_dict.at(first), "first"); |
| |
| Array<ObjectPtr<GeneratedDerivedObj>> nullable_array{nullptr, first}; |
| auto iterator = nullable_array.begin(); |
| EXPECT_EQ(*iterator, nullptr); |
| ++iterator; |
| EXPECT_EQ(*iterator, first); |
| } |
| |
| TEST(ObjectPtr, ExplicitPointerContainerSchemas) { |
| ObjectPtr<GeneratedDerivedObj> value = make_object<GeneratedDerivedObj>(3); |
| Array<ObjectPtr<GeneratedDerivedObj>> derived_array{value}; |
| Array<ObjectPtr<GeneratedBaseObj>> base_array = derived_array; |
| EXPECT_TRUE(derived_array.same_as(base_array)); |
| |
| EXPECT_EQ( |
| TypeTraits<Array<ObjectPtr<GeneratedDerivedObj>>>::TypeSchema(), |
| R"({"type":"ffi.Array","args":[{"type":"Optional","args":[{"type":"testing.GeneratedDerived"}]}]})"); |
| EXPECT_EQ( |
| TypeTraits<List<ObjectPtr<GeneratedDerivedObj>>>::TypeSchema(), |
| R"({"type":"ffi.List","args":[{"type":"Optional","args":[{"type":"testing.GeneratedDerived"}]}]})"); |
| |
| Array<Any> left{1}; |
| Array<Any> right{2}; |
| Array<Any> concatenated = tvm::ffi::Concat(left, right); |
| ASSERT_EQ(concatenated.size(), 2); |
| EXPECT_EQ(concatenated[0].cast<int64_t>(), 1); |
| EXPECT_EQ(concatenated[1].cast<int64_t>(), 2); |
| } |
| |
| TEST(ObjectPtr, NullMutableContainerConversionsStayNull) { |
| List<int64_t> list_copy_source(UnsafeInit{}); |
| List<Any> list_copy(list_copy_source); |
| EXPECT_FALSE(list_copy.defined()); |
| |
| List<int64_t> list_move_source(UnsafeInit{}); |
| List<Any> list_move(std::move(list_move_source)); |
| EXPECT_FALSE(list_move.defined()); |
| |
| List<Any> list_copy_assignment; |
| list_copy_assignment = list_copy_source; |
| EXPECT_FALSE(list_copy_assignment.defined()); |
| |
| List<int64_t> list_move_assignment_source(UnsafeInit{}); |
| List<Any> list_move_assignment; |
| list_move_assignment = std::move(list_move_assignment_source); |
| EXPECT_FALSE(list_move_assignment.defined()); |
| |
| Dict<String, int64_t> dict_copy_source(UnsafeInit{}); |
| Dict<String, Any> dict_copy(dict_copy_source); |
| EXPECT_FALSE(dict_copy.defined()); |
| |
| Dict<String, int64_t> dict_move_source(UnsafeInit{}); |
| Dict<String, Any> dict_move(std::move(dict_move_source)); |
| EXPECT_FALSE(dict_move.defined()); |
| |
| Dict<String, Any> dict_copy_assignment; |
| dict_copy_assignment = dict_copy_source; |
| EXPECT_FALSE(dict_copy_assignment.defined()); |
| |
| Dict<String, int64_t> dict_move_assignment_source(UnsafeInit{}); |
| Dict<String, Any> dict_move_assignment; |
| dict_move_assignment = std::move(dict_move_assignment_source); |
| EXPECT_FALSE(dict_move_assignment.defined()); |
| } |
| |
| TEST(ObjectPtr, ErasedContainerUpcastsShareBacking) { |
| ObjectPtr<GeneratedDerivedObj> derived = make_object<GeneratedDerivedObj>(1); |
| |
| Array<ObjectPtr<GeneratedDerivedObj>> narrow_array{derived}; |
| Any erased_array = narrow_array; |
| Array<ObjectPtr<GeneratedBaseObj>> wide_array = |
| erased_array.cast<Array<ObjectPtr<GeneratedBaseObj>>>(); |
| EXPECT_TRUE(wide_array.same_as(narrow_array)); |
| |
| Map<String, ObjectPtr<GeneratedDerivedObj>> narrow_map{{"derived", derived}}; |
| Any erased_map = narrow_map; |
| Map<String, ObjectPtr<GeneratedBaseObj>> wide_map = |
| erased_map.cast<Map<String, ObjectPtr<GeneratedBaseObj>>>(); |
| EXPECT_TRUE(wide_map.same_as(narrow_map)); |
| |
| List<ObjectPtr<GeneratedDerivedObj>> narrow_list{derived}; |
| Any erased_list = narrow_list; |
| List<ObjectPtr<GeneratedBaseObj>> wide_list = |
| erased_list.cast<List<ObjectPtr<GeneratedBaseObj>>>(); |
| EXPECT_TRUE(wide_list.same_as(narrow_list)); |
| wide_list.push_back(derived); |
| EXPECT_EQ(narrow_list.size(), 2); |
| |
| Any moved_list = narrow_list; |
| List<ObjectPtr<GeneratedBaseObj>> moved_wide_list = |
| std::move(moved_list).cast<List<ObjectPtr<GeneratedBaseObj>>>(); |
| EXPECT_TRUE(moved_wide_list.same_as(narrow_list)); |
| ASSERT_EQ(moved_wide_list.size(), 2); |
| moved_wide_list.push_back(derived); |
| EXPECT_EQ(narrow_list.size(), 3); |
| |
| const ObjectRef& erased_list_ref = narrow_list; |
| std::optional<List<ObjectPtr<GeneratedBaseObj>>> list_from_ref = |
| erased_list_ref.as<List<ObjectPtr<GeneratedBaseObj>>>(); |
| ASSERT_TRUE(list_from_ref.has_value()); |
| EXPECT_TRUE(list_from_ref->same_as( // NOLINT(bugprone-unchecked-optional-access) |
| narrow_list)); |
| list_from_ref->push_back(derived); // NOLINT(bugprone-unchecked-optional-access) |
| EXPECT_EQ(narrow_list.size(), 4); |
| |
| const ObjectRef& throwing_list_ref = narrow_list; |
| List<ObjectPtr<GeneratedBaseObj>> throwing_list = |
| throwing_list_ref.as_or_throw<List<ObjectPtr<GeneratedBaseObj>>>(); |
| EXPECT_TRUE(throwing_list.same_as(narrow_list)); |
| throwing_list.push_back(derived); |
| EXPECT_EQ(narrow_list.size(), 5); |
| |
| Dict<String, ObjectPtr<GeneratedDerivedObj>> narrow_dict{{"derived", derived}}; |
| Any erased_dict = narrow_dict; |
| Dict<String, ObjectPtr<GeneratedBaseObj>> wide_dict = |
| erased_dict.cast<Dict<String, ObjectPtr<GeneratedBaseObj>>>(); |
| EXPECT_TRUE(wide_dict.same_as(narrow_dict)); |
| wide_dict.Set("wide", derived); |
| EXPECT_EQ(narrow_dict.count("wide"), 1); |
| |
| const ObjectRef& erased_dict_ref = narrow_dict; |
| std::optional<Dict<String, ObjectPtr<GeneratedBaseObj>>> dict_from_ref = |
| erased_dict_ref.as<Dict<String, ObjectPtr<GeneratedBaseObj>>>(); |
| ASSERT_TRUE(dict_from_ref.has_value()); |
| EXPECT_TRUE(dict_from_ref->same_as( // NOLINT(bugprone-unchecked-optional-access) |
| narrow_dict)); |
| dict_from_ref->Set("ref", derived); // NOLINT(bugprone-unchecked-optional-access) |
| EXPECT_EQ(narrow_dict.count("ref"), 1); |
| |
| ObjectRef throwing_dict_ref = narrow_dict; |
| Dict<String, ObjectPtr<GeneratedBaseObj>> throwing_dict = |
| std::move(throwing_dict_ref).as_or_throw<Dict<String, ObjectPtr<GeneratedBaseObj>>>(); |
| EXPECT_TRUE(throwing_dict.same_as(narrow_dict)); |
| throwing_dict.Set("throwing", derived); |
| EXPECT_EQ(narrow_dict.count("throwing"), 1); |
| } |
| |
| } // namespace |