blob: 556700fbb61cf80c19935a8c408c905a9b45dde2 [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/container/array.h>
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/object.h>
#include <tvm/ffi/reflection/access_path.h>
#include <tvm/ffi/reflection/accessor.h>
#include <tvm/ffi/reflection/creator.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include "./testing_object.h"
/*! \brief Look up __ffi_init__ from the TypeAttrColumn (not the method table). */
static tvm::ffi::Function GetInitAttr(const char* type_key) {
static tvm::ffi::reflection::TypeAttrColumn col(tvm::ffi::reflection::type_attr::kInit);
return col[tvm::ffi::TypeKeyToIndex(type_key)].cast<tvm::ffi::Function>();
}
namespace {
using namespace tvm::ffi;
using namespace tvm::ffi::testing;
struct TestObjA : public Object {
int64_t x;
int64_t y;
TestObjA(int64_t x, int64_t y) : x(x), y(y) {}
static constexpr bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("test.TestObjA", TestObjA, Object);
};
struct TestObjADerived : public TestObjA {
int64_t z;
TestObjADerived(int64_t x, int64_t y, int64_t z) : TestObjA(x, y), z(z) {}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.TestObjADerived", TestObjADerived, TestObjA);
};
struct TestObjRefADerived : public ObjectRef {
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TestObjRefADerived, ObjectRef, TestObjADerived);
};
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
TIntObj::RegisterReflection();
TFloatObj::RegisterReflection();
TPrimExprObj::RegisterReflection();
TVarObj::RegisterReflection();
TPairObj::RegisterReflection();
TVarWithDepObj::RegisterReflection();
TDefHolderObj::RegisterReflection();
TFuncObj::RegisterReflection();
TCustomFuncObj::RegisterReflection();
TAllFieldsObj::RegisterReflection();
TWithDefaultsObj::RegisterReflection();
refl::ObjectDef<TestObjA>()
.def(refl::init<int64_t, int64_t>())
.def_ro("x", &TestObjA::x)
.def_rw("y", &TestObjA::y);
refl::ObjectDef<TestObjADerived>()
.def(refl::init<int64_t, int64_t, int64_t>())
.def_ro("z", &TestObjADerived::z);
}
TEST(Reflection, GetFieldByteOffset) {
EXPECT_EQ(reflection::GetFieldByteOffsetToObject(&TestObjA::x), sizeof(TVMFFIObject));
EXPECT_EQ(reflection::GetFieldByteOffsetToObject(&TestObjA::y), 8 + sizeof(TVMFFIObject));
EXPECT_EQ(reflection::GetFieldByteOffsetToObject(&TIntObj::value), sizeof(TVMFFIObject));
}
TEST(Reflection, FieldGetter) {
ObjectRef a = TInt(10);
reflection::FieldGetter getter("test.Int", "value");
EXPECT_EQ(getter(a).cast<int>(), 10);
ObjectRef b = TFloat(10.0);
reflection::FieldGetter getter_float("test.Float", "value");
EXPECT_EQ(getter_float(b).cast<double>(), 10.0);
}
TEST(Reflection, FieldSetter) {
ObjectRef a = TFloat(10.0);
reflection::FieldSetter setter("test.Float", "value");
setter(a, 20.0);
EXPECT_EQ(a.as<TFloatObj>()->value, 20.0);
}
TEST(Reflection, FieldInfo) {
const TVMFFIFieldInfo* info_int = reflection::GetFieldInfo("test.Int", "value");
EXPECT_FALSE(info_int->flags & kTVMFFIFieldFlagBitMaskHasDefault);
EXPECT_FALSE(info_int->flags & kTVMFFIFieldFlagBitMaskWritable);
EXPECT_EQ(Bytes(info_int->doc).operator std::string(), "");
const TVMFFIFieldInfo* info_float = reflection::GetFieldInfo("test.Float", "value");
EXPECT_EQ(info_float->default_value_or_factory.v_float64, 10.0);
EXPECT_TRUE(info_float->flags & kTVMFFIFieldFlagBitMaskHasDefault);
EXPECT_FALSE(info_float->flags & kTVMFFIFieldFlagBitMaskWritable);
EXPECT_EQ(Bytes(info_float->doc).operator std::string(), "float value field");
const TVMFFIFieldInfo* info_prim_expr_dtype = reflection::GetFieldInfo("test.PrimExpr", "dtype");
AnyView default_value =
AnyView::CopyFromTVMFFIAny(info_prim_expr_dtype->default_value_or_factory);
EXPECT_EQ(default_value.cast<String>(), "float");
EXPECT_TRUE(info_prim_expr_dtype->flags & kTVMFFIFieldFlagBitMaskHasDefault);
EXPECT_TRUE(info_prim_expr_dtype->flags & kTVMFFIFieldFlagBitMaskWritable);
EXPECT_EQ(Bytes(info_prim_expr_dtype->doc).operator std::string(), "dtype field");
}
TEST(Reflection, MethodInfo) {
const TVMFFIMethodInfo* info_int_static_add = reflection::GetMethodInfo("test.Int", "static_add");
EXPECT_TRUE(info_int_static_add->flags & kTVMFFIFieldFlagBitMaskIsStaticMethod);
EXPECT_EQ(Bytes(info_int_static_add->doc).operator std::string(), "static add method");
const TVMFFIMethodInfo* info_float_add = reflection::GetMethodInfo("test.Float", "add");
EXPECT_FALSE(info_float_add->flags & kTVMFFIFieldFlagBitMaskIsStaticMethod);
EXPECT_EQ(Bytes(info_float_add->doc).operator std::string(), "add method");
const TVMFFIMethodInfo* info_float_sub = reflection::GetMethodInfo("test.Float", "sub");
EXPECT_FALSE(info_float_sub->flags & kTVMFFIFieldFlagBitMaskIsStaticMethod);
EXPECT_EQ(Bytes(info_float_sub->doc).operator std::string(), "");
}
TEST(Reflection, CallMethod) {
Function static_int_add = reflection::GetMethod("test.Int", "static_add");
EXPECT_EQ(static_int_add(TInt(1), TInt(2)).cast<TInt>()->value, 3);
Function float_add = reflection::GetMethod("test.Float", "add");
EXPECT_EQ(float_add(TFloat(1), 2.0).cast<double>(), 3.0);
Function float_sub = reflection::GetMethod("test.Float", "sub");
EXPECT_EQ(float_sub(TFloat(1), 2.0).cast<double>(), -1.0);
Function prim_expr_sub = reflection::GetMethod("test.PrimExpr", "sub");
EXPECT_EQ(prim_expr_sub(TPrimExpr("float", 1), 2.0).cast<double>(), -1.0);
}
TEST(Reflection, InitFunctionBase) {
Function int_init = GetInitAttr("test.TestObjA");
Any obj_a = int_init(1, 2);
EXPECT_TRUE(obj_a.as<TestObjA>() != nullptr);
EXPECT_EQ(obj_a.as<TestObjA>()->x, 1);
EXPECT_EQ(obj_a.as<TestObjA>()->y, 2);
}
TEST(Reflection, InitFunctionDerived) {
Function derived_init = GetInitAttr("test.TestObjADerived");
Any obj_derived = derived_init(1, 2, 3);
EXPECT_TRUE(obj_derived.as<TestObjADerived>() != nullptr);
EXPECT_EQ(obj_derived.as<TestObjADerived>()->x, 1);
EXPECT_EQ(obj_derived.as<TestObjADerived>()->y, 2);
EXPECT_EQ(obj_derived.as<TestObjADerived>()->z, 3);
}
TEST(Reflection, ForEachFieldInfo) {
const TypeInfo* info = TVMFFIGetTypeInfo(TestObjADerived::RuntimeTypeIndex());
Map<String, int> field_name_to_offset;
reflection::ForEachFieldInfo(info, [&](const TVMFFIFieldInfo* field_info) {
field_name_to_offset.Set(String(field_info->name), static_cast<int>(field_info->offset));
});
EXPECT_EQ(field_name_to_offset["x"], sizeof(TVMFFIObject));
EXPECT_EQ(field_name_to_offset["y"], 8 + sizeof(TVMFFIObject));
EXPECT_EQ(field_name_to_offset["z"], 16 + sizeof(TVMFFIObject));
}
TEST(Reflection, TypeAttrColumn) {
reflection::TypeAttrColumn size_attr("test.size");
EXPECT_EQ(size_attr[TIntObj::RuntimeTypeIndex()].cast<int>(), sizeof(TIntObj));
}
TEST(Reflection, TypeAttrColumnBeginIndex) {
// Get the column and verify begin_index
TVMFFIByteArray attr_name = {"test.size", std::char_traits<char>::length("test.size")};
const TVMFFITypeAttrColumn* column = TVMFFIGetTypeAttrColumn(&attr_name);
ASSERT_NE(column, nullptr);
// begin_index should be >= 0
EXPECT_GE(column->begin_index, 0);
// size should cover the range from begin_index
EXPECT_GT(column->size, 0);
// verify that lookup of a type_index below begin_index returns None
reflection::TypeAttrColumn size_attr("test.size");
AnyView result = size_attr[0]; // index 0 is kTVMFFINone, unlikely to have this attr
(void)result; // suppress unused variable warning; we only verify no crash occurs
// The result may or may not be None depending on begin_index; the key is no crash.
// verify the known registered entry still works
EXPECT_EQ(size_attr[TIntObj::RuntimeTypeIndex()].cast<int>(), sizeof(TIntObj));
}
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::GlobalDef().def_method("testing.Int_GetValue", &TIntObj::GetValue);
}
TEST(Reflection, FuncRegister) {
Function fget_value = Function::GetGlobalRequired("testing.Int_GetValue");
TInt a(12);
EXPECT_EQ(fget_value(a).cast<int>(), 12);
}
TEST(Reflection, ObjectCreator) {
namespace refl = tvm::ffi::reflection;
refl::ObjectCreator creator("test.Int");
EXPECT_EQ(creator(Map<String, Any>({{"value", 1}})).cast<TInt>()->value, 1);
}
TEST(Reflection, AccessPath) {
namespace refl = tvm::ffi::reflection;
// Test basic path construction and ToSteps()
refl::AccessPath path = refl::AccessPath::Root()->Attr("body")->ArrayItem(1);
auto steps = path->ToSteps();
EXPECT_EQ(steps.size(), 2);
EXPECT_EQ(steps[0]->kind, refl::AccessKind::kAttr);
EXPECT_EQ(steps[1]->kind, refl::AccessKind::kArrayItem);
EXPECT_EQ(steps[0]->key.cast<String>(), "body");
EXPECT_EQ(steps[1]->key.cast<int64_t>(), 1);
// Test PathEqual with identical paths
refl::AccessPath path2 = refl::AccessPath::Root()->Attr("body")->ArrayItem(1);
EXPECT_TRUE(path->PathEqual(path2));
EXPECT_TRUE(path->IsPrefixOf(path2));
// Test PathEqual with different paths
refl::AccessPath path3 = refl::AccessPath::Root()->Attr("body")->ArrayItem(2);
EXPECT_FALSE(path->PathEqual(path3));
EXPECT_FALSE(path->IsPrefixOf(path3));
// Test prefix relationship - path4 extends path, so path should be prefix of path4
refl::AccessPath path4 = refl::AccessPath::Root()->Attr("body")->ArrayItem(1)->Attr("body");
EXPECT_FALSE(path->PathEqual(path4)); // Not equal (different lengths)
EXPECT_TRUE(path->IsPrefixOf(path4)); // But path is a prefix of path4
// Test completely different paths
refl::AccessPath path5 = refl::AccessPath::Root()->ArrayItem(0)->ArrayItem(1)->Attr("body");
EXPECT_FALSE(path->PathEqual(path5));
EXPECT_FALSE(path->IsPrefixOf(path5));
// Test Root path
refl::AccessPath root = refl::AccessPath::Root();
auto root_steps = root->ToSteps();
EXPECT_EQ(root_steps.size(), 0);
EXPECT_EQ(root->depth, 0);
EXPECT_TRUE(root->IsPrefixOf(path));
EXPECT_TRUE(root->IsPrefixOf(root));
EXPECT_TRUE(root->PathEqual(refl::AccessPath::Root()));
// Test depth calculations
EXPECT_EQ(path->depth, 2);
EXPECT_EQ(path4->depth, 3);
EXPECT_EQ(root->depth, 0);
// Test MapItem access
refl::AccessPath map_path = refl::AccessPath::Root()->Attr("data")->MapItem("key1");
auto map_steps = map_path->ToSteps();
EXPECT_EQ(map_steps.size(), 2);
EXPECT_EQ(map_steps[0]->kind, refl::AccessKind::kAttr);
EXPECT_EQ(map_steps[1]->kind, refl::AccessKind::kMapItem);
EXPECT_EQ(map_steps[0]->key.cast<String>(), "data");
EXPECT_EQ(map_steps[1]->key.cast<String>(), "key1");
// Test MapItemMissing access
refl::AccessPath map_missing_path = refl::AccessPath::Root()->MapItemMissing(42);
auto map_missing_steps = map_missing_path->ToSteps();
EXPECT_EQ(map_missing_steps.size(), 1);
EXPECT_EQ(map_missing_steps[0]->kind, refl::AccessKind::kMapItemMissing);
EXPECT_EQ(map_missing_steps[0]->key.cast<int64_t>(), 42);
// Test ArrayItemMissing access
refl::AccessPath array_missing_path = refl::AccessPath::Root()->ArrayItemMissing(5);
auto array_missing_steps = array_missing_path->ToSteps();
EXPECT_EQ(array_missing_steps.size(), 1);
EXPECT_EQ(array_missing_steps[0]->kind, refl::AccessKind::kArrayItemMissing);
EXPECT_EQ(array_missing_steps[0]->key.cast<int64_t>(), 5);
// Test FromSteps static method - round trip conversion
auto original_steps = path->ToSteps();
refl::AccessPath reconstructed = refl::AccessPath::FromSteps(original_steps);
EXPECT_TRUE(path->PathEqual(reconstructed));
EXPECT_EQ(path->depth, reconstructed->depth);
// Test complex prefix relationships
refl::AccessPath short_path = refl::AccessPath::Root()->Attr("x");
refl::AccessPath medium_path = refl::AccessPath::Root()->Attr("x")->ArrayItem(0);
refl::AccessPath long_path = refl::AccessPath::Root()->Attr("x")->ArrayItem(0)->MapItem("z");
EXPECT_TRUE(short_path->IsPrefixOf(medium_path));
EXPECT_TRUE(short_path->IsPrefixOf(long_path));
EXPECT_TRUE(medium_path->IsPrefixOf(long_path));
EXPECT_FALSE(medium_path->IsPrefixOf(short_path));
EXPECT_FALSE(long_path->IsPrefixOf(medium_path));
EXPECT_FALSE(long_path->IsPrefixOf(short_path));
// Test non-prefix relationships
refl::AccessPath branch1 = refl::AccessPath::Root()->Attr("x")->ArrayItem(0);
refl::AccessPath branch2 = refl::AccessPath::Root()->Attr("x")->ArrayItem(1);
EXPECT_FALSE(branch1->IsPrefixOf(branch2));
EXPECT_FALSE(branch2->IsPrefixOf(branch1));
EXPECT_FALSE(branch1->PathEqual(branch2));
// Test GetParent functionality
auto parent = path4->GetParent();
EXPECT_TRUE(parent.has_value());
EXPECT_TRUE(parent.value()->PathEqual(path));
auto root_parent = root->GetParent();
EXPECT_FALSE(root_parent.has_value());
}
struct TestObjWithFactory : public Object {
Array<ObjectRef> items;
int64_t count;
explicit TestObjWithFactory(UnsafeInit) {}
[[maybe_unused]] static constexpr bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.TestObjWithFactory", TestObjWithFactory, Object);
};
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TestObjWithFactory>()
.def_ro("items", &TestObjWithFactory::items,
refl::default_factory(
Function::FromTyped([]() -> Array<ObjectRef> { return Array<ObjectRef>(); })))
.def_ro("count", &TestObjWithFactory::count, refl::default_value(static_cast<int64_t>(0)));
}
struct TestObjWithAny : public Object {
Any value;
explicit TestObjWithAny(Any value) : value(std::move(value)) {}
[[maybe_unused]] static constexpr bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.TestObjWithAny", TestObjWithAny, Object);
};
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TestObjWithAny>().def(refl::init<Any>()).def_ro("value", &TestObjWithAny::value);
}
struct TestObjWithAnyView : public Object {
Any value;
explicit TestObjWithAnyView(AnyView value) : value(value) {}
[[maybe_unused]] static constexpr bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.TestObjWithAnyView", TestObjWithAnyView, Object);
};
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TestObjWithAnyView>()
.def(refl::init<AnyView>())
.def_ro("value", &TestObjWithAnyView::value);
}
TEST(Reflection, InitWithAny) {
Function init = GetInitAttr("test.TestObjWithAny");
Any obj1 = init(42);
ASSERT_TRUE(obj1.as<TestObjWithAny>() != nullptr);
EXPECT_EQ(obj1.as<TestObjWithAny>()->value.cast<int>(), 42);
Any obj2 = init(3.14);
ASSERT_TRUE(obj2.as<TestObjWithAny>() != nullptr);
EXPECT_EQ(obj2.as<TestObjWithAny>()->value.cast<double>(), 3.14);
Any obj3 = init(String("hello"));
ASSERT_TRUE(obj3.as<TestObjWithAny>() != nullptr);
EXPECT_EQ(obj3.as<TestObjWithAny>()->value.cast<String>(), "hello");
}
TEST(Reflection, InitWithAnyView) {
Function init = GetInitAttr("test.TestObjWithAnyView");
Any obj1 = init(42);
ASSERT_TRUE(obj1.as<TestObjWithAnyView>() != nullptr);
EXPECT_EQ(obj1.as<TestObjWithAnyView>()->value.cast<int>(), 42);
Any obj2 = init(3.14);
ASSERT_TRUE(obj2.as<TestObjWithAnyView>() != nullptr);
EXPECT_EQ(obj2.as<TestObjWithAnyView>()->value.cast<double>(), 3.14);
Any obj3 = init(String("hello"));
ASSERT_TRUE(obj3.as<TestObjWithAnyView>() != nullptr);
EXPECT_EQ(obj3.as<TestObjWithAnyView>()->value.cast<String>(), "hello");
}
TEST(Reflection, DefaultFactoryFlag) {
const TVMFFIFieldInfo* info_items = reflection::GetFieldInfo("test.TestObjWithFactory", "items");
EXPECT_TRUE(info_items->flags & kTVMFFIFieldFlagBitMaskHasDefault);
EXPECT_TRUE(info_items->flags & kTVMFFIFieldFlagBitMaskDefaultFromFactory);
const TVMFFIFieldInfo* info_count = reflection::GetFieldInfo("test.TestObjWithFactory", "count");
EXPECT_TRUE(info_count->flags & kTVMFFIFieldFlagBitMaskHasDefault);
EXPECT_FALSE(info_count->flags & kTVMFFIFieldFlagBitMaskDefaultFromFactory);
}
TEST(Reflection, DefaultFactoryCreation) {
namespace refl = tvm::ffi::reflection;
refl::ObjectCreator creator("test.TestObjWithFactory");
// Create two objects without providing "items" - each should get a fresh Array
Any obj1 = creator(Map<String, Any>({{"count", static_cast<int64_t>(42)}}));
Any obj2 = creator(Map<String, Any>({{"count", static_cast<int64_t>(99)}}));
auto* p1 = obj1.as<TestObjWithFactory>();
auto* p2 = obj2.as<TestObjWithFactory>();
ASSERT_NE(p1, nullptr);
ASSERT_NE(p2, nullptr);
EXPECT_EQ(p1->count, 42);
EXPECT_EQ(p2->count, 99);
// Both should have empty arrays
EXPECT_EQ(p1->items.size(), 0);
EXPECT_EQ(p2->items.size(), 0);
// Crucially, the arrays should be distinct objects (not aliased)
EXPECT_NE(p1->items.get(), p2->items.get());
}
TEST(Reflection, DefaultFactoryNotCalledWhenProvided) {
namespace refl = tvm::ffi::reflection;
refl::ObjectCreator creator("test.TestObjWithFactory");
Array<ObjectRef> custom_items;
custom_items.push_back(TInt(1));
Any obj =
creator(Map<String, Any>({{"items", custom_items}, {"count", static_cast<int64_t>(5)}}));
auto* p = obj.as<TestObjWithFactory>();
ASSERT_NE(p, nullptr);
EXPECT_EQ(p->items.size(), 1);
EXPECT_EQ(p->count, 5);
}
// ---------------------------------------------------------------------------
// Tests for auto-generated __ffi_init__ with init(false) / KwOnly(true)
// ---------------------------------------------------------------------------
struct TestAutoInitObj : public Object {
int64_t a;
int64_t b;
int64_t c;
int64_t d;
static constexpr bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("test.AutoInit", TestAutoInitObj, Object);
};
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
// No refl::init<>() — auto-generates __ffi_init__
refl::ObjectDef<TestAutoInitObj>()
.def_rw("a", &TestAutoInitObj::a)
.def_rw("b", &TestAutoInitObj::b, refl::init(false), refl::default_value(int64_t{42}))
.def_rw("c", &TestAutoInitObj::c, refl::kw_only(true))
.def_rw("d", &TestAutoInitObj::d, refl::default_value(int64_t{99}));
}
TEST(Reflection, AutoInitPositional) {
// Auto-generated init: positional args for non-kw-only init=True fields (a, d)
// c is kw_only so it cannot be passed positionally.
Function auto_init = GetInitAttr("test.AutoInit");
ObjectRef kwargs = Function::GetGlobalRequired("ffi.GetKwargsObject")().cast<ObjectRef>();
// Positional: a=1, d=3; keyword: c=2
Any obj = auto_init(int64_t{1}, int64_t{3}, kwargs, String("c"), int64_t{2});
auto* p = obj.as<TestAutoInitObj>();
ASSERT_NE(p, nullptr);
EXPECT_EQ(p->a, 1);
EXPECT_EQ(p->b, 42); // init=False, gets default
EXPECT_EQ(p->c, 2); // kw_only, passed via KWARGS
EXPECT_EQ(p->d, 3); // init=True, 2nd positional
}
TEST(Reflection, AutoInitPartialPositional) {
// Provide only a (position 0); c is required but missing → error
Function auto_init = GetInitAttr("test.AutoInit");
EXPECT_THROW(
{
try {
auto_init(int64_t{1});
} catch (const std::exception& e) {
EXPECT_NE(std::string(e.what()).find("missing required"), std::string::npos);
throw;
}
},
std::exception);
}
TEST(Reflection, AutoInitWithDefaults) {
// Provide a positionally and c via KWARGS; d should use default 99
Function auto_init = GetInitAttr("test.AutoInit");
ObjectRef kwargs = Function::GetGlobalRequired("ffi.GetKwargsObject")().cast<ObjectRef>();
Any obj = auto_init(int64_t{10}, kwargs, String("c"), int64_t{20});
auto* p = obj.as<TestAutoInitObj>();
ASSERT_NE(p, nullptr);
EXPECT_EQ(p->a, 10);
EXPECT_EQ(p->b, 42); // default
EXPECT_EQ(p->c, 20); // provided via KWARGS
EXPECT_EQ(p->d, 99); // default
}
TEST(Reflection, AutoInitKwargs) {
Function auto_init = GetInitAttr("test.AutoInit");
ObjectRef kwargs = Function::GetGlobalRequired("ffi.GetKwargsObject")().cast<ObjectRef>();
// Positional: a=1, then KWARGS: c=30, d=40
Any obj = auto_init(int64_t{1}, kwargs, String("c"), int64_t{30}, String("d"), int64_t{40});
auto* p = obj.as<TestAutoInitObj>();
ASSERT_NE(p, nullptr);
EXPECT_EQ(p->a, 1);
EXPECT_EQ(p->b, 42); // default
EXPECT_EQ(p->c, 30);
EXPECT_EQ(p->d, 40);
}
TEST(Reflection, AutoInitKwargsOnly) {
Function auto_init = GetInitAttr("test.AutoInit");
ObjectRef kwargs = Function::GetGlobalRequired("ffi.GetKwargsObject")().cast<ObjectRef>();
// No positional args, all via KWARGS
Any obj = auto_init(kwargs, String("a"), int64_t{5}, String("c"), int64_t{15}, String("d"),
int64_t{25});
auto* p = obj.as<TestAutoInitObj>();
ASSERT_NE(p, nullptr);
EXPECT_EQ(p->a, 5);
EXPECT_EQ(p->b, 42);
EXPECT_EQ(p->c, 15);
EXPECT_EQ(p->d, 25);
}
TEST(Reflection, AutoInitKwargsDuplicate) {
Function auto_init = GetInitAttr("test.AutoInit");
ObjectRef kwargs = Function::GetGlobalRequired("ffi.GetKwargsObject")().cast<ObjectRef>();
// a is provided positionally AND as kwarg → error
EXPECT_THROW(
{
try {
auto_init(int64_t{1}, kwargs, String("a"), int64_t{2}, String("c"), int64_t{3});
} catch (const std::exception& e) {
EXPECT_NE(std::string(e.what()).find("multiple values"), std::string::npos);
throw;
}
},
std::exception);
}
TEST(Reflection, AutoInitKwargsUnknown) {
Function auto_init = GetInitAttr("test.AutoInit");
ObjectRef kwargs = Function::GetGlobalRequired("ffi.GetKwargsObject")().cast<ObjectRef>();
EXPECT_THROW(
{
try {
auto_init(kwargs, String("a"), int64_t{1}, String("z"), int64_t{2}, String("c"),
int64_t{3});
} catch (const std::exception& e) {
EXPECT_NE(std::string(e.what()).find("unexpected keyword"), std::string::npos);
throw;
}
},
std::exception);
}
TEST(Reflection, AutoInitFlagBits) {
// Verify the flag bits are set correctly on the field info.
const TVMFFIFieldInfo* fi_a = reflection::GetFieldInfo("test.AutoInit", "a");
EXPECT_FALSE(fi_a->flags & kTVMFFIFieldFlagBitMaskInitOff);
EXPECT_FALSE(fi_a->flags & kTVMFFIFieldFlagBitMaskKwOnly);
const TVMFFIFieldInfo* fi_b = reflection::GetFieldInfo("test.AutoInit", "b");
EXPECT_TRUE(fi_b->flags & kTVMFFIFieldFlagBitMaskInitOff);
EXPECT_FALSE(fi_b->flags & kTVMFFIFieldFlagBitMaskKwOnly);
EXPECT_TRUE(fi_b->flags & kTVMFFIFieldFlagBitMaskHasDefault);
const TVMFFIFieldInfo* fi_c = reflection::GetFieldInfo("test.AutoInit", "c");
EXPECT_FALSE(fi_c->flags & kTVMFFIFieldFlagBitMaskInitOff);
EXPECT_TRUE(fi_c->flags & kTVMFFIFieldFlagBitMaskKwOnly);
const TVMFFIFieldInfo* fi_d = reflection::GetFieldInfo("test.AutoInit", "d");
EXPECT_FALSE(fi_d->flags & kTVMFFIFieldFlagBitMaskInitOff);
EXPECT_FALSE(fi_d->flags & kTVMFFIFieldFlagBitMaskKwOnly);
EXPECT_TRUE(fi_d->flags & kTVMFFIFieldFlagBitMaskHasDefault);
}
// Simple auto-init test: all fields init=True, no Init/KwOnly traits
struct TestAutoInitSimpleObj : public Object {
int64_t x;
int64_t y;
static constexpr bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("test.AutoInitSimple", TestAutoInitSimpleObj, Object);
};
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TestAutoInitSimpleObj>()
.def_rw("x", &TestAutoInitSimpleObj::x)
.def_rw("y", &TestAutoInitSimpleObj::y);
}
TEST(Reflection, AutoInitSimple) {
Function auto_init = GetInitAttr("test.AutoInitSimple");
Any obj = auto_init(int64_t{10}, int64_t{20});
auto* p = obj.as<TestAutoInitSimpleObj>();
ASSERT_NE(p, nullptr);
EXPECT_EQ(p->x, 10);
EXPECT_EQ(p->y, 20);
}
TEST(Reflection, AutoInitSimpleTooManyArgs) {
Function auto_init = GetInitAttr("test.AutoInitSimple");
EXPECT_THROW(auto_init(int64_t{1}, int64_t{2}, int64_t{3}), std::exception);
}
// ---------------------------------------------------------------------------
// overload_cast — pick an overload by prefix-matching its parameter types.
// ---------------------------------------------------------------------------
namespace overload_cast_test {
struct Cat {};
struct Dog {};
// Pet: each Feed overload has a unique first arg plus a trailing context
// (`int amount`) that the caller doesn't have to spell out. Get is const
// vs non-const overloaded with identical params (selected via const_ tag).
struct Pet {
int Feed(const Cat*, int amount) { return 100 + amount; }
int Feed(const Dog*, int amount) { return 200 + amount; }
int Get(int x) { return 4000 + x; }
int Get(int x) const { return 5000 + x; }
};
// Mix: overloads share a leading prefix — spelling more parameters
// disambiguates against the longer variant.
struct Mix {
int Run(int, int) { return 1000; }
int Run(int, double) { return 2000; }
int Run(int, int, int) { return 3000; }
};
int FreeFeed(const Cat*, int x) { return 6000 + x; }
int FreeFeed(const Dog*, int x) { return 7000 + x; }
template <auto Method>
struct CallVia {
template <typename Self, typename... Args>
static auto Run(Self&& self, Args&&... args) {
return (std::forward<Self>(self).*Method)(std::forward<Args>(args)...);
}
};
} // namespace overload_cast_test
TEST(OverloadCast, PrefixMatch) {
using namespace overload_cast_test;
namespace refl = tvm::ffi::reflection;
Pet p;
Cat cat;
Dog dog;
// (a) Member with unique first arg per overload: spelling only the
// disambiguating prefix picks the overload and deduces the
// trailing `int amount` from the picked signature.
auto p_cat = refl::overload_cast<const Cat*>(&Pet::Feed);
static_assert(std::is_same_v<decltype(p_cat), int (Pet::*)(const Cat*, int)>,
"prefix match must deduce trailing arg types");
EXPECT_EQ((p.*p_cat)(&cat, 7), 107);
auto p_dog = refl::overload_cast<const Dog*>(&Pet::Feed);
EXPECT_EQ((p.*p_dog)(&dog, 12), 212);
// (b) Free function with the same shape — trailing arg deduced.
auto p_free_cat = refl::overload_cast<const Cat*>(&FreeFeed);
EXPECT_EQ(p_free_cat(&cat, 7), 6007);
auto p_free_dog = refl::overload_cast<const Dog*>(&FreeFeed);
EXPECT_EQ(p_free_dog(&dog, 7), 7007);
}
TEST(OverloadCast, AmbiguousPrefixRequiresMoreSpelling) {
using namespace overload_cast_test;
namespace refl = tvm::ffi::reflection;
Mix m;
// Mix::Run has three overloads:
// Run(int, int), Run(int, double), Run(int, int, int)
// Spelling only <int> would be ambiguous (all three start with int).
// Spelling enough parameters to identify exactly one overload picks it.
EXPECT_EQ((m.*refl::overload_cast<int, int>(&Mix::Run))(0, 0), 1000);
EXPECT_EQ((m.*refl::overload_cast<int, double>(&Mix::Run))(0, 0.0), 2000);
EXPECT_EQ((m.*refl::overload_cast<int, int, int>(&Mix::Run))(0, 0, 0), 3000);
}
TEST(OverloadCast, ConstQualifiedMember) {
using namespace overload_cast_test;
namespace refl = tvm::ffi::reflection;
Pet p;
const Pet& cp = p;
// Non-const overload — no tag.
EXPECT_EQ((p.*refl::overload_cast<int>(&Pet::Get))(7), 4007);
// Const overload — const_ tag required (even when the const overload
// is the only one with that signature, address-of-overload alone
// cannot select it from the operator() overload set).
EXPECT_EQ((cp.*refl::overload_cast<int>(&Pet::Get, refl::const_))(7), 5007);
}
TEST(OverloadCast, NonTypeTemplateArgument) {
using namespace overload_cast_test;
namespace refl = tvm::ffi::reflection;
Pet p;
Mix m;
Cat cat;
// Prefix match composed as a non-type template argument.
EXPECT_EQ((CallVia<refl::overload_cast<const Cat*>(&Pet::Feed)>::Run(p, &cat, 7)), 107);
// Disambiguated 3-arg overload as a non-type template argument.
EXPECT_EQ((CallVia<refl::overload_cast<int, int, int>(&Mix::Run)>::Run(m, 0, 0, 0)), 3000);
}
} // namespace