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 d711f44f [Rust] EnumRef for FFI enums (#953)
d711f44f is described below

commit d711f44f0be3e21f6e3858a2af25c155ead3ff55
Author: Tianqi Chen <[email protected]>
AuthorDate: Fri Nov 6 21:44:43 2026 -0500

    [Rust] EnumRef for FFI enums (#953)
    
    Generic support for FFI enums in the Rust crate, plus the registration
    builders it needs.
    
    - `reflection` is split into files like the C++ headers (`access_path`,
    `accessor`, `registry`, `enum_def`); public paths are unchanged.
    - `#[derive(Object)]` registers through one hidden entry point,
    `reflection::register_object`, so `ObjectDef::finish` and the other
    registration steps are private.
    - `EnumObj` (the `ffi.Enum` object) and the `EnumRef` trait: `get(name)`
    looks a value up in the enum type's `__ffi_enum__` registry, `name()`
    borrows its name, and `attr_map::<V>(name)` returns an `EnumAttrMap` to
    read an attribute. Values are singletons, compared with `same_as`; the
    integer index stays private.
    - `reflection::EnumDef<E>` registers a value eagerly (`new(name)`), sets
    its attributes (`set_attr`) and returns it (`value()`), for enum types
    without fields beyond `EnumObj`, as C++ `EnumDef` does. It updates the
    shared registry through the existing `ffi.Dict*` globals.
    - `reflection::TypeAttrDef<T>` registers type attributes, as C++
    `TypeAttrDef` does.
    - Stubgen: a type under `ffi.Enum` embeds `tvm_ffi::EnumObj` (complete,
    no allocator) and implements `tvm_ffi::EnumRef`;
    `ffi.IntEnum`/`ffi.StrEnum` stand-ins now sit on `EnumObj`.
    - Reflection items (`FieldFlags`, `FieldGetter`, `ObjectDef`,
    `SEqHashKind`, `ShallowCopy`, `TypeAttrColumn`, `UnsafeInit`) are no
    longer re-exported at the crate root; use `tvm_ffi::reflection::`.
---
 docs/guides/rust_lang_guide.rst                    |   2 +-
 python/tvm_ffi/stub/rust_generator/codegen.py      |  50 ++-
 python/tvm_ffi/stub/rust_generator/consts.py       |   7 +
 python/tvm_ffi/stub/rust_generator/utils.py        |   6 +-
 rust/tvm-ffi-macros/src/object_macros.rs           |  27 +-
 rust/tvm-ffi-macros/src/rs_object.rs               |   4 +-
 rust/tvm-ffi/src/enum_ref.rs                       | 164 +++++++++
 rust/tvm-ffi/src/extra/structural_eq_hash.rs       |   2 +-
 rust/tvm-ffi/src/lib.rs                            |   9 +-
 rust/tvm-ffi/src/reflection.rs                     | 379 +--------------------
 rust/tvm-ffi/src/reflection/access_path.rs         |  88 +++++
 .../src/{reflection.rs => reflection/accessor.rs}  |  85 +----
 rust/tvm-ffi/src/reflection/enum_def.rs            | 282 +++++++++++++++
 rust/tvm-ffi/src/reflection/registry.rs            |  91 ++++-
 rust/tvm-ffi/tests/test_enum.rs                    |  76 +++++
 rust/tvm-ffi/tests/test_object_reflection.rs       |   2 +-
 rust/tvm-ffi/tests/test_rs_object.rs               |   4 +-
 rust/tvm-ffi/tests/test_structural_mutate.rs       |   5 +-
 rust/tvm-ffi/tests/test_structural_visit.rs        |   7 +-
 tests/python/test_stubgen_rust.py                  |  70 ++--
 20 files changed, 814 insertions(+), 546 deletions(-)

diff --git a/docs/guides/rust_lang_guide.rst b/docs/guides/rust_lang_guide.rst
index c32e44f0..903aa497 100644
--- a/docs/guides/rust_lang_guide.rst
+++ b/docs/guides/rust_lang_guide.rst
@@ -347,7 +347,7 @@ first ``type_index()``, which allocating an instance calls:
 Each reflected field gets a C ABI getter and setter, its doc string and its
 type schema, so structural equality and hashing, ``ffi.ReprPrint``,
 ``ffi.DeepCopy`` and Python see it. The flags in ``#[reflect(...)]`` name
-``tvm_ffi::FieldFlags`` constants; ``name = "..."`` registers the field under
+``tvm_ffi::reflection::FieldFlags`` constants; ``name = "..."`` registers the 
field under
 another name, and ``doc = "..."`` sets its doc string, otherwise the field's
 ``///`` doc comment. Without ``#[type_s_eq_hash_kind]``, a type keeps its
 nearest reflected ancestor's kind. The derive also implements ``ShallowCopy``
diff --git a/python/tvm_ffi/stub/rust_generator/codegen.py 
b/python/tvm_ffi/stub/rust_generator/codegen.py
index 24281716..9f5917a8 100644
--- a/python/tvm_ffi/stub/rust_generator/codegen.py
+++ b/python/tvm_ffi/stub/rust_generator/codegen.py
@@ -38,10 +38,13 @@ A field without a Rust mirror (``Optional<Any>``, 
``Union``, ``void*``, an
 ``ffi.*`` type the default ``ty-map`` does not list, ...) makes the type opaque
 and is read as ``Any``; an ``opaque`` directive vetoes a reproducible layout; a
 scalar width named by a directive is checked against the reflected field size.
-A builtin parent (``ffi.IntEnum``, say) has no
+A builtin parent the crate defines (``ffi.Enum``: ``tvm_ffi::EnumObj``) is
+embedded as it is. Any other builtin parent (``ffi.IntEnum``, say) has no
 ``<Leaf>Obj`` in the crate: the import section defines a header-only stand-in
-per builtin ancestor so ``derive(Object)`` computes the registry's
+per such ancestor so ``derive(Object)`` computes the registry's
 ``TYPE_DEPTH``, and everything under such a parent stays opaque 
(``no-mirror``).
+An enum type (under ``ffi.Enum``) has no allocator, since its registry
+allocates the values, and its reference implements ``tvm_ffi::EnumRef``.
 
 Every type key an object refers to (parent, ancestors, field types) must be
 provided in the same run: ``ffi.*`` by the crate, a ``ty-map`` by a 
hand-written
@@ -223,8 +226,9 @@ class _ObjectRenderer:
     def _base_type(self) -> tuple[str, bool]:
         """Resolve the ``base_`` struct and whether it is a generated or 
``ty-map``'d parent.
 
-        A builtin parent below ``ffi.Object`` is embedded as its header-only
-        stand-in (see :meth:`RustImports.record_builtin_base`).
+        A builtin parent below ``ffi.Object`` is embedded as the crate's object
+        struct, or as its header-only stand-in over the deepest builtin 
ancestor the
+        crate defines (see :meth:`RustImports.record_builtin_base`).
         """
         parent = self.info.parent_type_key
         if parent is not None:
@@ -237,7 +241,12 @@ class _ObjectRenderer:
         if parent not in (None, C_RUST.RUST_ROOT_TYPE_KEY, *chain):
             chain.append(parent)
         assert all(self._provider(key) == "crate" for key in chain), 
(self.type_key, chain)
-        return self.imports.record_builtin_base(chain), False
+        defined = [i for i, key in enumerate(chain) if key in 
C_RUST.RUST_CRATE_OBJECTS]
+        if not defined:
+            return self.imports.record_builtin_base(chain), False
+        deepest = defined[-1]
+        root = C_RUST.RUST_CRATE_OBJECTS[chain[deepest]]
+        return self.imports.record_builtin_base(chain[deepest + 1 :], root), 
False
 
     # --- classification ----------------------------------------------------
 
@@ -251,11 +260,14 @@ class _ObjectRenderer:
         def renderable(field: NamedTypeSchema) -> bool:
             return self._field_mirror(owner_of[id(field)], field, scratch) is 
not None
 
-        # Builtin ancestors are header-only stand-ins (`_base_type`): none 
below is complete.
+        # Builtin ancestors the crate does not define are header-only stand-ins
+        # (`_base_type`): none below is complete.
         unmirrored = {
             key
             for key in self.info.ancestors
-            if key != C_RUST.RUST_ROOT_TYPE_KEY and self._provider(key) == 
"crate"
+            if key != C_RUST.RUST_ROOT_TYPE_KEY
+            and key not in C_RUST.RUST_CRATE_OBJECTS
+            and self._provider(key) == "crate"
         }
         verdicts = classify(
             infos,
@@ -669,7 +681,7 @@ class _ObjectRenderer:
         ancestors = [key for key in self.info.ancestors if key != 
C_RUST.RUST_ROOT_TYPE_KEY]
         if not all(self._provider(key) == "generated" for key in ancestors):
             return None
-        shallow_copy = self.imports.record("tvm_ffi::ShallowCopy")
+        shallow_copy = self.imports.record("tvm_ffi::reflection::ShallowCopy")
         own = [rust_ident(f.name) for f in sorted(self.info.fields, key=lambda 
f: f.offset or 0)]
         init = [
             f"base_: {shallow_copy}::shallow_copy(&self.base_)",
@@ -693,7 +705,7 @@ class _ObjectRenderer:
         anything else goes through ``drop_if_defined``, which skips an 
undefined field.
         Emitted wherever ``ShallowCopy`` is.
         """
-        unsafe_init = self.imports.record("tvm_ffi::UnsafeInit")
+        unsafe_init = self.imports.record("tvm_ffi::reflection::UnsafeInit")
         addr = self.imports.record("std::ptr::addr_of_mut")
         directives = self.imports.directives
         drops = 
[f"{unsafe_init}::drop_defined_fields({addr}!((*this).base_));"]
@@ -721,6 +733,14 @@ class _ObjectRenderer:
             "}",
         ]
 
+    def _impl_sections(self, is_enum: bool) -> list[list[str]]:
+        """``ImmutableObject``, ``EnumRef`` and the upcasts, each that 
applies."""
+        sections = [self._immutable_object_lines()]
+        if is_enum:
+            sections.append([f"impl tvm_ffi::EnumRef for {self.leaf} {{}}"])
+        sections.append(self._upcast_lines())
+        return [section for section in sections if section]
+
     def body(self) -> list[str]:
         """Build the Rust source lines for the object."""
         self.imports.reserved.update((self.leaf, self.obj_struct))
@@ -731,10 +751,11 @@ class _ObjectRenderer:
         if has_parent:
             self.imports.record("std::ops::Deref")
         fields = self.info.fields
+        is_enum = C_RUST.RUST_ENUM_TYPE_KEY in self.info.ancestors
         has_accessors = bool(fields) and not verdict.is_complete
         if has_accessors:
             self.imports.record("tvm_ffi::ObjectCore")  # `Self::type_index()`
-            self.imports.record("tvm_ffi::FieldGetter")
+            self.imports.record("tvm_ffi::reflection::FieldGetter")
             self.imports.record("tvm_ffi::Result")
 
         # The reference comes first, so a `///` written above the block 
documents it.
@@ -771,18 +792,13 @@ class _ObjectRenderer:
                     "}",
                 ]
             )
-        elif verdict.is_complete:
+        elif verdict.is_complete and not is_enum:
             sections += self._allocator_sections(base, has_parent)
             shallow_copy = self._shallow_copy_lines()
             if shallow_copy:
                 sections.append(shallow_copy)
                 sections.append(self._unsafe_init_lines())
-        thread_safe = self._immutable_object_lines()
-        if thread_safe:
-            sections.append(thread_safe)
-        upcasts = self._upcast_lines()
-        if upcasts:
-            sections.append(upcasts)
+        sections += self._impl_sections(is_enum)
 
         lines: list[str] = []
         for i, section in enumerate(sections):
diff --git a/python/tvm_ffi/stub/rust_generator/consts.py 
b/python/tvm_ffi/stub/rust_generator/consts.py
index 2ae25f92..99631218 100644
--- a/python/tvm_ffi/stub/rust_generator/consts.py
+++ b/python/tvm_ffi/stub/rust_generator/consts.py
@@ -123,6 +123,13 @@ RUST_MOD_MAP = {
 #: Root of the object hierarchy.
 RUST_ROOT_TYPE_KEY = "ffi.Object"
 
+#: Builtin types whose object struct the crate defines; a subtype embeds it as 
``base_``.
+RUST_CRATE_OBJECTS = {"ffi.Enum": "tvm_ffi::EnumObj"}
+
+#: Root of the enum types: their registry allocates the values, and their 
references
+#: implement ``tvm_ffi::EnumRef``.
+RUST_ENUM_TYPE_KEY = "ffi.Enum"
+
 #: Keywords a field name may collide with: spelled ``r#name``, or ``name_`` 
for the four
 #: that cannot be raw identifiers.
 RUST_KEYWORDS = frozenset(
diff --git a/python/tvm_ffi/stub/rust_generator/utils.py 
b/python/tvm_ffi/stub/rust_generator/utils.py
index 8f99d480..7a5e0667 100644
--- a/python/tvm_ffi/stub/rust_generator/utils.py
+++ b/python/tvm_ffi/stub/rust_generator/utils.py
@@ -111,12 +111,12 @@ class RustImports:
     reserved: set[str] = dataclasses.field(default_factory=set)
     """Names the file's ``object/`` blocks define; an import may not take 
one."""
 
-    def record_builtin_base(self, chain: list[str]) -> str:
+    def record_builtin_base(self, chain: list[str], root: str = 
"tvm_ffi::Object") -> str:
         """Record the stand-ins for the builtin ``chain`` (root side first); 
return the last name.
 
-        An empty chain (parent ``ffi.Object``) yields the crate's ``Object``.
+        The first stand-in embeds the crate's ``root`` object; an empty chain 
yields ``root``.
         """
-        base = self.record("tvm_ffi::Object")
+        base = self.record(root)
         for key in chain:
             self.builtin_mirrors.setdefault(key, base)
             base = builtin_mirror_name(key)
diff --git a/rust/tvm-ffi-macros/src/object_macros.rs 
b/rust/tvm-ffi-macros/src/object_macros.rs
index ed500c2e..2a7cdacf 100644
--- a/rust/tvm-ffi-macros/src/object_macros.rs
+++ b/rust/tvm-ffi-macros/src/object_macros.rs
@@ -442,12 +442,12 @@ impl Reflection {
                 format_ident!("{}", flag.to_string().to_uppercase(), span = 
flag.span())
             });
             quote! {
-                .field::<#ty>(
+                let def = def.field::<#ty>(
                     #name,
                     #doc,
                     ::core::mem::offset_of!(#struct_name, #id),
                     #reflection::FieldFlags::NONE #(| 
#reflection::FieldFlags::#flags)*,
-                )?
+                )?;
             }
         });
         // The `#[type_reflection]` function registers the methods after the
@@ -461,26 +461,25 @@ impl Reflection {
                     first.ident = struct_name.clone();
                 }
             }
-            quote! {
-                let mut def = def;
-                #path(&mut def);
-            }
+            quote!(#path)
         });
+        let methods = methods.unwrap_or_else(|| quote!(|_| {}));
         let creator = if self.unsafe_init {
             quote!(Some(#reflection::unsafe_init_creator::<#struct_name>))
         } else {
             quote!(None)
         };
         quote! {
-            let registered = (|| -> #tvm_ffi_crate::Result<()> {
-                let def = #reflection::ObjectDef::<#struct_name>::registering(
-                    tindex, #kind, #creator,
-                )?
+            let registered = #reflection::register_object::<#struct_name>(
+                tindex,
+                #kind,
+                #creator,
+                |def| {
                     #(#fields)*
-                    .shallow_copy()?;
-                #methods
-                def.finish()
-            })();
+                    Ok(def)
+                },
+                #methods,
+            );
             if let Err(error) = registered {
                 panic!("Failed to register the reflection of type key {}: {}", 
#type_key, error);
             }
diff --git a/rust/tvm-ffi-macros/src/rs_object.rs 
b/rust/tvm-ffi-macros/src/rs_object.rs
index 90bc2cf3..506442de 100644
--- a/rust/tvm-ffi-macros/src/rs_object.rs
+++ b/rust/tvm-ffi-macros/src/rs_object.rs
@@ -220,10 +220,10 @@ fn expand(args: &RsObjectArgs, item: &DeriveInput) -> 
syn::Result<TokenStream2>
     // them; only through raw pointers, since some may be undefined.
     let field_ids = fields.iter().map(|field| &field.ident);
     let unsafe_init_impl = quote! {
-        unsafe impl #tvm_ffi::UnsafeInit for #obj {
+        unsafe impl #tvm_ffi::reflection::UnsafeInit for #obj {
             unsafe fn drop_defined_fields(this: *mut Self) {
                 unsafe {
-                    <#base_ty as #tvm_ffi::UnsafeInit>::drop_defined_fields(
+                    <#base_ty as 
#tvm_ffi::reflection::UnsafeInit>::drop_defined_fields(
                         ::core::ptr::addr_of_mut!((*this).#base_id),
                     );
                     #(#tvm_ffi::reflection::drop_if_defined(
diff --git a/rust/tvm-ffi/src/enum_ref.rs b/rust/tvm-ffi/src/enum_ref.rs
new file mode 100644
index 00000000..79a0b8e9
--- /dev/null
+++ b/rust/tvm-ffi/src/enum_ref.rs
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+//! Enums shared across languages: each value is a named singleton of an
+//! object type whose parent is [`EnumObj`]. [`EnumRef`] looks values up;
+//! [`reflection::EnumDef`](crate::reflection::EnumDef) registers them.
+
+use crate::derive::Object;
+use crate::object::{ImmutableObject, Object, ObjectCore, ObjectRefCore};
+use crate::reflection::enum_def::{any_into, dict_get, enum_state};
+use crate::reflection::EnumAttrMap;
+use crate::type_traits::AnyCompatible;
+use crate::{AnyView, Error, Result, String, VALUE_ERROR};
+
+/// The object behind every enum value: its name and the index the registry
+/// assigned it. An enum type's object embeds it as its parent,
+/// `base_: EnumObj`.
+#[repr(C)]
+#[derive(Object)]
+#[type_key = "ffi.Enum"]
+pub struct EnumObj {
+    base_: Object,
+    // The registration order, which can differ between processes, so values
+    // are identified by name only.
+    #[allow(dead_code)]
+    index: i64,
+    name: String,
+}
+
+unsafe impl ImmutableObject for EnumObj {}
+
+impl EnumObj {
+    pub(crate) fn new(index: i64, name: &str) -> Self {
+        Self {
+            base_: Object::new(),
+            index,
+            name: String::from(name),
+        }
+    }
+}
+
+/// A reference to an enum value: one named singleton of an enum type,
+/// registered once and shared with every language.
+///
+/// [`get`](Self::get) looks a value up by name. Each name has one value, so
+/// equality is identity: compare values with
+/// [`same_as`](ObjectRefCore::same_as).
+/// [`EnumDef`](crate::reflection::EnumDef) registers values and their
+/// attributes, and [`attr_map`](Self::attr_map) reads an attribute.
+///
+/// The stub generator implements it for the enum types it binds. A
+/// hand-written reference implements it when its object's parent is
+/// [`EnumObj`].
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::derive::{Object, ObjectRef};
+/// use tvm_ffi::reflection::EnumDef;
+/// use tvm_ffi::{EnumObj, EnumRef, ObjectArc, ObjectRefCore};
+///
+/// #[repr(C)]
+/// #[derive(Object)]
+/// #[type_key = "my_ext.Color"]
+/// #[type_register]
+/// #[type_final]
+/// pub struct ColorObj {
+///     base_: EnumObj,
+/// }
+///
+/// #[derive(Clone, ObjectRef)]
+/// pub struct Color {
+///     data: ObjectArc<ColorObj>,
+/// }
+///
+/// impl EnumRef for Color {}
+///
+/// EnumDef::<Color>::new("red")?;
+///
+/// let red = Color::get("red")?;
+/// assert_eq!(red.name(), "red");
+/// assert!(red.same_as(&Color::get("red")?));
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
+pub trait EnumRef: ObjectRefCore {
+    /// Look up the value registered as `name`.
+    ///
+    /// # Arguments
+    ///
+    /// * `name` - The name of the value.
+    ///
+    /// # Returns
+    ///
+    /// The value, the same object on every call.
+    ///
+    /// # Errors
+    ///
+    /// Returns a `ValueError` if the enum type has no value named `name`.
+    fn get(name: &str) -> Result<Self> {
+        let key = String::from(name);
+        let found = match enum_state(Self::ContainerType::type_index())? {
+            Some(state) => dict_get(AnyView::from(&state.indexes), 
AnyView::from(&key))?,
+            None => None,
+        };
+        let Some(value) = found else {
+            return Err(Error::new(
+                VALUE_ERROR,
+                &format!(
+                    "Enum `{}` has no value named `{name}`",
+                    Self::ContainerType::TYPE_KEY
+                ),
+                "",
+            ));
+        };
+        any_into(value)
+    }
+
+    /// The name of the value, unique within its enum type.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the object of `Self` does not have [`EnumObj`] as an
+    /// ancestor, which only an incorrect `impl EnumRef` allows.
+    fn name(&self) -> &str {
+        let object = self
+            .downcast_ref::<EnumObj>()
+            .expect("an `EnumRef` object has `EnumObj` as an ancestor");
+        object.name.as_str()
+    }
+
+    /// The attribute `name` of the values of the enum type, or `None` when no
+    /// value has it.
+    ///
+    /// The map holds the registry's column, so it also sees the attributes set
+    /// after it was read. See also
+    /// [`EnumDef::set_attr`](crate::reflection::EnumDef::set_attr).
+    ///
+    /// # Arguments
+    ///
+    /// * `name` - The name of the attribute.
+    ///
+    /// # Returns
+    ///
+    /// The attribute of every value that has it, read as `V`.
+    fn attr_map<V: AnyCompatible>(name: &str) -> 
Result<Option<EnumAttrMap<Self, V>>> {
+        EnumAttrMap::lookup(name)
+    }
+}
diff --git a/rust/tvm-ffi/src/extra/structural_eq_hash.rs 
b/rust/tvm-ffi/src/extra/structural_eq_hash.rs
index d388236d..cbf90b88 100644
--- a/rust/tvm-ffi/src/extra/structural_eq_hash.rs
+++ b/rust/tvm-ffi/src/extra/structural_eq_hash.rs
@@ -38,7 +38,7 @@ pub struct StructuralOptions {
 /// and content, compared recursively through containers and reflected
 /// fields, not by identity.
 ///
-/// Objects compare as their [`SEqHashKind`](crate::SEqHashKind) declares.
+/// Objects compare as their [`SEqHashKind`](crate::reflection::SEqHashKind) 
declares.
 ///
 /// # Arguments
 ///
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index 102f53ad..7012023d 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -49,6 +49,7 @@ pub mod collections;
 pub mod derive;
 pub mod device;
 pub mod dtype;
+pub mod enum_ref;
 pub mod error;
 pub mod extra;
 pub mod function;
@@ -76,6 +77,7 @@ pub use crate::collections::shape::Shape;
 pub use crate::collections::tensor::{CPUNDAlloc, NDAllocator, Tensor, 
TensorView};
 pub use crate::device::{current_stream, with_stream};
 pub use crate::dtype::DLDataTypeExt;
+pub use crate::enum_ref::{EnumObj, EnumRef};
 pub use crate::error::{Error, ErrorKind, Result};
 pub use crate::error::{
     ATTRIBUTE_ERROR, INDEX_ERROR, INTERNAL_ERROR, KEY_ERROR, OVERFLOW_ERROR, 
RUNTIME_ERROR,
@@ -105,9 +107,6 @@ pub use crate::object::{
     ObjectIdentity, ObjectRefAnyCompatible, ObjectRefCore,
 };
 pub use crate::optional::{Optional, OptionalCompatible};
-pub use crate::reflection::{
-    FieldFlags, FieldGetter, ObjectDef, SEqHashKind, ShallowCopy, 
TypeAttrColumn, UnsafeInit,
-};
 pub use crate::rvalue_ref::RValueRef;
 pub use crate::static_init::run_static_init_blocks;
 pub use crate::string::{Bytes, String};
@@ -250,7 +249,7 @@ pub use tvm_ffi_macros::match_any;
 ///
 /// * `"type.Key"` - The type key.
 /// * `final` - The type has no subtypes.
-/// * `s_eq_hash_kind = "TreeNode"` - The [`SEqHashKind`] of structural
+/// * `s_eq_hash_kind = "TreeNode"` - The 
[`SEqHashKind`](crate::reflection::SEqHashKind) of structural
 ///   equality; by default the parent's.
 /// * `child_slots = N`, `child_slots_can_overflow = bool` - Type indices kept
 ///   for subtypes; 0 and `true` by default.
@@ -260,7 +259,7 @@ pub use tvm_ffi_macros::match_any;
 ///   depends on it only through a re-export; by default `tvm_ffi`.
 ///
 /// A field takes `#[reflect(writable, seq_hash_ignore, ...)]` for the
-/// [`FieldFlags`] of those names, and `name = "..."` or `doc = "..."` to
+/// [`FieldFlags`](crate::reflection::FieldFlags) of those names, and `name = 
"..."` or `doc = "..."` to
 /// register it under another name or with a doc string, by default its `///`
 /// comment.
 ///
diff --git a/rust/tvm-ffi/src/reflection.rs b/rust/tvm-ffi/src/reflection.rs
index f8ae5fd8..1483a577 100644
--- a/rust/tvm-ffi/src/reflection.rs
+++ b/rust/tvm-ffi/src/reflection.rs
@@ -18,377 +18,20 @@
  */
 
 //! Object reflection: look up the methods, fields and type attributes of
-//! registered types, and register Rust object types with [`ObjectDef`].
-
-use std::ffi::c_void;
-use std::ptr::NonNull;
-
-use crate::derive::{Object, ObjectRef};
-use crate::object::{Object, ObjectArc, ObjectRef};
-use crate::tvm_ffi_sys::{
-    TVMFFIAny, TVMFFIByteArray, TVMFFIFieldGetter, TVMFFIFieldInfo, 
TVMFFIGetTypeAttrColumn,
-    TVMFFIGetTypeInfo, TVMFFIObject, TVMFFITypeAttrColumn, TVMFFITypeIndex, 
TVMFFITypeKeyToIndex,
-};
-use crate::type_traits::AnyCompatible;
-use crate::{Any, AnyView, Error, Function, ObjectCore, Result, TYPE_ERROR};
+//! registered types; register Rust object types with [`ObjectDef`], type
+//! attributes with [`TypeAttrDef`] and enum values with [`EnumDef`].
 
+mod access_path;
+mod accessor;
+pub(crate) mod enum_def;
 mod registry;
 mod unsafe_init;
-pub use registry::{FieldFlags, ObjectDef, SEqHashKind, ShallowCopy};
+pub use access_path::{AccessKind, AccessPath, AccessPathObj, AccessStep, 
AccessStepObj};
+pub use accessor::{get_method, get_method_by_index, FieldGetter, 
TypeAttrColumn};
+pub use enum_def::{EnumAttrMap, EnumDef};
+#[doc(hidden)]
+pub use registry::register_object;
+pub use registry::{FieldFlags, ObjectDef, SEqHashKind, ShallowCopy, 
TypeAttrDef};
 #[doc(hidden)]
 pub use unsafe_init::{drop_if_defined, unsafe_init_creator};
 pub use unsafe_init::{unsafe_init, UnsafeInit};
-
-/// A registry-owned type-attribute column indexed by runtime type.
-///
-/// [`TypeAttrColumn::get`] returns owning copies. Registration must not race
-/// with reads.
-#[derive(Clone, Copy)]
-pub struct TypeAttrColumn(NonNull<TVMFFITypeAttrColumn>);
-
-// Type-attribute columns and their cells are registry-owned process-lifetime
-// data. Once registration is complete, reading a cell does not mutate the
-// registry and is safe from any thread.
-unsafe impl Send for TypeAttrColumn {}
-unsafe impl Sync for TypeAttrColumn {}
-
-impl TypeAttrColumn {
-    /// Look up a registered type-attribute column by name.
-    pub fn new(name: &str) -> Option<Self> {
-        unsafe {
-            let name = TVMFFIByteArray::from_str(name);
-            NonNull::new(TVMFFIGetTypeAttrColumn(&name).cast_mut()).map(Self)
-        }
-    }
-
-    /// The attribute of `type_index`, as an owned value, or `None` if unset.
-    pub fn get(self, type_index: i32) -> Option<Any> {
-        let raw = self.get_raw_ffi_any(type_index)?;
-        if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
-            return None;
-        }
-        Some(Any::from(unsafe { AnyView::from_raw_ffi_any(raw) }))
-    }
-
-    /// The raw cell of `type_index`'s attribute, borrowed from the registry.
-    ///
-    /// The cell does not retain its value: the value must stay registered 
while
-    /// the cell is used, and registration must not race with reads. Use
-    /// [`Self::get`] for an owned [`Any`].
-    ///
-    /// Returns `None` when the index is outside the column or the column has 
no
-    /// data. Unlike [`Self::get`], it returns a `None`-valued cell as `Some`.
-    #[inline(always)]
-    pub fn get_raw_ffi_any(self, type_index: i32) -> Option<TVMFFIAny> {
-        unsafe {
-            let column = self.0.as_ref();
-            let index = type_index - column.begin_index;
-            if index < 0 || index >= column.size || column.data.is_null() {
-                None
-            } else {
-                Some(*column.data.offset(index as isize))
-            }
-        }
-    }
-}
-
-/// Look up a reflected method of a type by type key and method name.
-///
-/// Same as [`get_method_by_index`], but resolves `type_key` to a type index
-/// first.
-///
-/// # Arguments
-///
-/// * `type_key` - The type key of the type that owns the method.
-/// * `method_name` - The name of the method.
-///
-/// # Returns
-///
-/// The reflected method.
-///
-/// # Errors
-///
-/// Returns an error if the type or the method does not exist.
-pub fn get_method(type_key: &str, method_name: &str) -> Result<Function> {
-    unsafe {
-        let type_key_arg = TVMFFIByteArray::from_str(type_key);
-        let mut type_index: i32 = 0;
-        crate::check_safe_call!(TVMFFITypeKeyToIndex(&type_key_arg, &mut 
type_index))?;
-        get_method_by_index(type_index, method_name)
-    }
-}
-
-/// Look up a reflected method of a type by type index and method name, as
-/// [`get_method`] does by type key.
-///
-/// Reflected methods, registered in any language (such as with
-/// [`ObjectDef::def`]), live in the type's method table rather than the global
-/// function table. The constructor is reachable under the reserved name
-/// `__ffi_init__`. For instance methods, the first argument is the object
-/// itself.
-///
-/// `type_index` must be a registered type index, obtained for example from a
-/// live object via `Any::type_index` or from a type key; the C API treats an
-/// unregistered index as a fatal error.
-///
-/// # Arguments
-///
-/// * `type_index` - The type index of the type that owns the method.
-/// * `method_name` - The name of the method.
-///
-/// # Returns
-///
-/// The reflected method.
-///
-/// # Errors
-///
-/// Returns an error if the type has no method `method_name`.
-pub fn get_method_by_index(type_index: i32, method_name: &str) -> 
Result<Function> {
-    unsafe {
-        let type_info = TVMFFIGetTypeInfo(type_index);
-        if type_info.is_null() {
-            crate::bail!(
-                crate::error::TYPE_ERROR,
-                "Cannot find type info for type_index={}",
-                type_index
-            );
-        }
-        let type_info = &*type_info;
-        for i in 0..type_info.num_methods as usize {
-            let method_info = &*type_info.methods.add(i);
-            if method_info.name.as_str() != method_name {
-                continue;
-            }
-            if !<Function as 
AnyCompatible>::check_any_strict(&method_info.method) {
-                crate::bail!(
-                    crate::error::TYPE_ERROR,
-                    "Method `{}` of type `{}` is not a Function",
-                    method_name,
-                    type_info.type_key.as_str()
-                );
-            }
-            // the table entry stores the method as a non-owning AnyView;
-            // copy out a strong reference
-            return Ok(<Function as 
AnyCompatible>::copy_from_any_view_after_check(
-                &method_info.method,
-            ));
-        }
-        crate::bail!(
-            crate::error::TYPE_ERROR,
-            "Cannot find method `{}` of type `{}`",
-            method_name,
-            type_info.type_key.as_str()
-        );
-    }
-}
-
-/// What an [`AccessStep`] accesses: an attribute, an array item or a map
-/// item, or one of those that is missing.
-///
-/// A closed set: the values are part of the ABI.
-#[repr(i32)]
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-pub enum AccessKind {
-    /// An object attribute; the key is its name.
-    Attr = 0,
-    /// An array item; the key is its index.
-    ArrayItem = 1,
-    /// A map item; the key is the map key.
-    MapItem = 2,
-    /// An attribute that does not exist, in an error report.
-    AttrMissing = 3,
-    /// An array index out of range, in an error report.
-    ArrayItemMissing = 4,
-    /// A map key that is absent, in an error report.
-    MapItemMissing = 5,
-}
-
-/// The object behind an [`AccessStep`], laid out as the C++ `AccessStepObj`.
-#[repr(C)]
-#[derive(Object)]
-#[type_key = "ffi.reflection.AccessStep"]
-#[type_final]
-pub struct AccessStepObj {
-    object: Object,
-    /// What the step accesses.
-    pub kind: AccessKind,
-    /// The attribute name, array index or map key.
-    pub key: Any,
-}
-
-/// One step of an [`AccessPath`]: an attribute, array index or map key.
-#[repr(C)]
-#[derive(ObjectRef, Clone)]
-pub struct AccessStep {
-    data: ObjectArc<AccessStepObj>,
-}
-
-/// The object behind an [`AccessPath`], laid out as the C++ `AccessPathObj`.
-#[repr(C)]
-#[derive(Object)]
-#[type_key = "ffi.reflection.AccessPath"]
-#[type_final]
-pub struct AccessPathObj {
-    object: Object,
-    /// The path without its last step; `None` at the root.
-    pub parent: Option<ObjectRef>,
-    /// The last step; `None` at the root.
-    pub step: Option<AccessStep>,
-    /// The number of steps; 0 at the root.
-    pub depth: i32,
-}
-
-/// A path from a root value to a nested one, such as `root.field[0]`, as a
-/// chain of parents; its methods are reached with `reflection::get_method`.
-#[repr(C)]
-#[derive(ObjectRef, Clone)]
-pub struct AccessPath {
-    data: ObjectArc<AccessPathObj>,
-}
-
-/// Resolves a reflected field once, then uses its registered C ABI getter.
-#[derive(Clone, Copy)]
-pub struct FieldGetter {
-    owner_type_index: i32,
-    owner_type_depth: i32,
-    field_offset: i64,
-    getter: TVMFFIFieldGetter,
-}
-
-impl FieldGetter {
-    /// Resolve a reflected field declared by `type_index` or one of its bases.
-    pub fn new(type_index: i32, field_name: &str) -> Result<Self> {
-        let type_info = unsafe { TVMFFIGetTypeInfo(type_index) };
-        if type_info.is_null() {
-            return Err(Error::new(
-                TYPE_ERROR,
-                &format!("Cannot find type info for type_index={type_index}"),
-                "",
-            ));
-        }
-
-        let field = unsafe { find_field(type_info, field_name) }.ok_or_else(|| 
{
-            let type_key = unsafe { (*type_info).type_key.as_str() };
-            Error::new(
-                TYPE_ERROR,
-                &format!("Cannot find reflected field `{field_name}` in type 
`{type_key}`"),
-                "",
-            )
-        })?;
-        let field = unsafe { field.as_ref() };
-        let getter = field.getter.ok_or_else(|| {
-            Error::new(
-                TYPE_ERROR,
-                &format!("Reflected field `{}` has no getter", 
field.name.as_str()),
-                "",
-            )
-        })?;
-        Ok(Self {
-            owner_type_index: type_index,
-            owner_type_depth: unsafe { (*type_info).type_depth },
-            field_offset: field.offset,
-            getter,
-        })
-    }
-
-    /// Read the field as an owning [`Any`].
-    ///
-    /// `object` may have the declared owner type or any registered subtype.
-    pub fn get_any<N: ObjectCore>(&self, object: &N) -> Result<Any> {
-        let object_pointer = std::ptr::from_ref(object);
-        let header = object_pointer.cast::<TVMFFIObject>();
-        let dynamic_type_index = unsafe { (*header).type_index };
-        if !unsafe {
-            is_type_or_subtype(
-                dynamic_type_index,
-                self.owner_type_index,
-                self.owner_type_depth,
-            )
-        } {
-            return Err(Error::new(
-                TYPE_ERROR,
-                &format!(
-                    "Cannot read a field of type_index={} from object 
type_index={dynamic_type_index}",
-                    self.owner_type_index
-                ),
-                "",
-            ));
-        }
-
-        let field_address = unsafe {
-            object_pointer
-                .cast::<u8>()
-                .offset(self.field_offset as isize)
-                .cast_mut()
-                .cast::<c_void>()
-        };
-        let mut result = Any::new();
-        if unsafe { (self.getter)(field_address, Any::as_raw_ffi_any_mut(&mut 
result)) } != 0 {
-            return Err(Error::from_raised());
-        }
-        Ok(result)
-    }
-
-    /// Read and convert the field to `T`.
-    pub fn get<N, T>(&self, object: &N) -> Result<T>
-    where
-        N: ObjectCore,
-        T: TryFrom<Any, Error = Error>,
-    {
-        T::try_from(self.get_any(object)?)
-    }
-}
-
-unsafe fn find_field(
-    type_info: *const crate::tvm_ffi_sys::TVMFFITypeInfo,
-    field_name: &str,
-) -> Option<NonNull<TVMFFIFieldInfo>> {
-    // Prefer the most-derived declaration, then search nearest ancestors.
-    if let Some(field) = find_field_at_level(type_info, field_name) {
-        return Some(field);
-    }
-    for depth in (0..(*type_info).type_depth).rev() {
-        let ancestor = *(*type_info).type_acenstors.add(depth as usize);
-        if let Some(field) = find_field_at_level(ancestor, field_name) {
-            return Some(field);
-        }
-    }
-    None
-}
-
-unsafe fn find_field_at_level(
-    type_info: *const crate::tvm_ffi_sys::TVMFFITypeInfo,
-    field_name: &str,
-) -> Option<NonNull<TVMFFIFieldInfo>> {
-    if type_info.is_null() || (*type_info).fields.is_null() {
-        return None;
-    }
-    for index in 0..(*type_info).num_fields as usize {
-        let field = (*type_info).fields.add(index);
-        if (*field).name.as_str() == field_name {
-            return NonNull::new(field.cast_mut());
-        }
-    }
-    None
-}
-
-unsafe fn is_type_or_subtype(
-    dynamic_type_index: i32,
-    target_type_index: i32,
-    target_type_depth: i32,
-) -> bool {
-    if dynamic_type_index == target_type_index {
-        return true;
-    }
-    let dynamic_info = TVMFFIGetTypeInfo(dynamic_type_index);
-    if dynamic_info.is_null()
-        || (*dynamic_info).type_depth <= target_type_depth
-        || (*dynamic_info).type_acenstors.is_null()
-    {
-        return false;
-    }
-    let ancestor = *(*dynamic_info)
-        .type_acenstors
-        .add(target_type_depth as usize);
-    !ancestor.is_null() && (*ancestor).type_index == target_type_index
-}
diff --git a/rust/tvm-ffi/src/reflection/access_path.rs 
b/rust/tvm-ffi/src/reflection/access_path.rs
new file mode 100644
index 00000000..f74c2e50
--- /dev/null
+++ b/rust/tvm-ffi/src/reflection/access_path.rs
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+//! Access paths, which locate a value nested in another.
+
+use crate::derive::{Object, ObjectRef};
+use crate::object::{Object, ObjectArc, ObjectRef};
+use crate::Any;
+
+/// What an [`AccessStep`] accesses: an attribute, an array item or a map
+/// item, or one of those that is missing.
+///
+/// A closed set: the values are part of the ABI.
+#[repr(i32)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum AccessKind {
+    /// An object attribute; the key is its name.
+    Attr = 0,
+    /// An array item; the key is its index.
+    ArrayItem = 1,
+    /// A map item; the key is the map key.
+    MapItem = 2,
+    /// An attribute that does not exist, in an error report.
+    AttrMissing = 3,
+    /// An array index out of range, in an error report.
+    ArrayItemMissing = 4,
+    /// A map key that is absent, in an error report.
+    MapItemMissing = 5,
+}
+
+/// The object behind an [`AccessStep`], laid out as the C++ `AccessStepObj`.
+#[repr(C)]
+#[derive(Object)]
+#[type_key = "ffi.reflection.AccessStep"]
+#[type_final]
+pub struct AccessStepObj {
+    object: Object,
+    /// What the step accesses.
+    pub kind: AccessKind,
+    /// The attribute name, array index or map key.
+    pub key: Any,
+}
+
+/// One step of an [`AccessPath`]: an attribute, array index or map key.
+#[repr(C)]
+#[derive(ObjectRef, Clone)]
+pub struct AccessStep {
+    data: ObjectArc<AccessStepObj>,
+}
+
+/// The object behind an [`AccessPath`], laid out as the C++ `AccessPathObj`.
+#[repr(C)]
+#[derive(Object)]
+#[type_key = "ffi.reflection.AccessPath"]
+#[type_final]
+pub struct AccessPathObj {
+    object: Object,
+    /// The path without its last step; `None` at the root.
+    pub parent: Option<ObjectRef>,
+    /// The last step; `None` at the root.
+    pub step: Option<AccessStep>,
+    /// The number of steps; 0 at the root.
+    pub depth: i32,
+}
+
+/// A path from a root value to a nested one, such as `root.field[0]`, as a
+/// chain of parents; its methods are reached with `reflection::get_method`.
+#[repr(C)]
+#[derive(ObjectRef, Clone)]
+pub struct AccessPath {
+    data: ObjectArc<AccessPathObj>,
+}
diff --git a/rust/tvm-ffi/src/reflection.rs 
b/rust/tvm-ffi/src/reflection/accessor.rs
similarity index 80%
copy from rust/tvm-ffi/src/reflection.rs
copy to rust/tvm-ffi/src/reflection/accessor.rs
index f8ae5fd8..fe72c130 100644
--- a/rust/tvm-ffi/src/reflection.rs
+++ b/rust/tvm-ffi/src/reflection/accessor.rs
@@ -17,14 +17,11 @@
  * under the License.
  */
 
-//! Object reflection: look up the methods, fields and type attributes of
-//! registered types, and register Rust object types with [`ObjectDef`].
+//! Reads of registered types: methods, fields and type attributes.
 
 use std::ffi::c_void;
 use std::ptr::NonNull;
 
-use crate::derive::{Object, ObjectRef};
-use crate::object::{Object, ObjectArc, ObjectRef};
 use crate::tvm_ffi_sys::{
     TVMFFIAny, TVMFFIByteArray, TVMFFIFieldGetter, TVMFFIFieldInfo, 
TVMFFIGetTypeAttrColumn,
     TVMFFIGetTypeInfo, TVMFFIObject, TVMFFITypeAttrColumn, TVMFFITypeIndex, 
TVMFFITypeKeyToIndex,
@@ -32,13 +29,6 @@ use crate::tvm_ffi_sys::{
 use crate::type_traits::AnyCompatible;
 use crate::{Any, AnyView, Error, Function, ObjectCore, Result, TYPE_ERROR};
 
-mod registry;
-mod unsafe_init;
-pub use registry::{FieldFlags, ObjectDef, SEqHashKind, ShallowCopy};
-#[doc(hidden)]
-pub use unsafe_init::{drop_if_defined, unsafe_init_creator};
-pub use unsafe_init::{unsafe_init, UnsafeInit};
-
 /// A registry-owned type-attribute column indexed by runtime type.
 ///
 /// [`TypeAttrColumn::get`] returns owning copies. Registration must not race
@@ -122,10 +112,10 @@ pub fn get_method(type_key: &str, method_name: &str) -> 
Result<Function> {
 /// [`get_method`] does by type key.
 ///
 /// Reflected methods, registered in any language (such as with
-/// [`ObjectDef::def`]), live in the type's method table rather than the global
-/// function table. The constructor is reachable under the reserved name
-/// `__ffi_init__`. For instance methods, the first argument is the object
-/// itself.
+/// [`ObjectDef::def`](super::ObjectDef::def)), live in the type's method
+/// table rather than the global function table. The constructor is reachable
+/// under the reserved name `__ffi_init__`. For instance methods, the first
+/// argument is the object itself.
 ///
 /// `type_index` must be a registered type index, obtained for example from a
 /// live object via `Any::type_index` or from a type key; the C API treats an
@@ -181,71 +171,6 @@ pub fn get_method_by_index(type_index: i32, method_name: 
&str) -> Result<Functio
         );
     }
 }
-
-/// What an [`AccessStep`] accesses: an attribute, an array item or a map
-/// item, or one of those that is missing.
-///
-/// A closed set: the values are part of the ABI.
-#[repr(i32)]
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-pub enum AccessKind {
-    /// An object attribute; the key is its name.
-    Attr = 0,
-    /// An array item; the key is its index.
-    ArrayItem = 1,
-    /// A map item; the key is the map key.
-    MapItem = 2,
-    /// An attribute that does not exist, in an error report.
-    AttrMissing = 3,
-    /// An array index out of range, in an error report.
-    ArrayItemMissing = 4,
-    /// A map key that is absent, in an error report.
-    MapItemMissing = 5,
-}
-
-/// The object behind an [`AccessStep`], laid out as the C++ `AccessStepObj`.
-#[repr(C)]
-#[derive(Object)]
-#[type_key = "ffi.reflection.AccessStep"]
-#[type_final]
-pub struct AccessStepObj {
-    object: Object,
-    /// What the step accesses.
-    pub kind: AccessKind,
-    /// The attribute name, array index or map key.
-    pub key: Any,
-}
-
-/// One step of an [`AccessPath`]: an attribute, array index or map key.
-#[repr(C)]
-#[derive(ObjectRef, Clone)]
-pub struct AccessStep {
-    data: ObjectArc<AccessStepObj>,
-}
-
-/// The object behind an [`AccessPath`], laid out as the C++ `AccessPathObj`.
-#[repr(C)]
-#[derive(Object)]
-#[type_key = "ffi.reflection.AccessPath"]
-#[type_final]
-pub struct AccessPathObj {
-    object: Object,
-    /// The path without its last step; `None` at the root.
-    pub parent: Option<ObjectRef>,
-    /// The last step; `None` at the root.
-    pub step: Option<AccessStep>,
-    /// The number of steps; 0 at the root.
-    pub depth: i32,
-}
-
-/// A path from a root value to a nested one, such as `root.field[0]`, as a
-/// chain of parents; its methods are reached with `reflection::get_method`.
-#[repr(C)]
-#[derive(ObjectRef, Clone)]
-pub struct AccessPath {
-    data: ObjectArc<AccessPathObj>,
-}
-
 /// Resolves a reflected field once, then uses its registered C ABI getter.
 #[derive(Clone, Copy)]
 pub struct FieldGetter {
diff --git a/rust/tvm-ffi/src/reflection/enum_def.rs 
b/rust/tvm-ffi/src/reflection/enum_def.rs
new file mode 100644
index 00000000..9b39d056
--- /dev/null
+++ b/rust/tvm-ffi/src/reflection/enum_def.rs
@@ -0,0 +1,282 @@
+/*
+ * 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.
+ */
+
+//! Registration of enum values and their attributes, kept in each enum type's
+//! registry (the `__ffi_enum__` type attribute) that every language shares.
+
+use std::marker::PhantomData;
+use std::mem::{align_of, size_of};
+
+use super::{get_method, TypeAttrColumn, TypeAttrDef};
+use crate::any::TryFromTemp;
+use crate::derive::{Object, ObjectRef};
+use crate::object::{is_instance_of, Object, ObjectArc, ObjectCore, ObjectRef};
+use crate::tvm_ffi_sys::{TVMFFIGetTypeInfo, TVMFFIObject, TVMFFITypeInfo};
+use crate::type_traits::AnyCompatible;
+use crate::{Any, AnyView, EnumObj, EnumRef, Error, List, Result, String, 
TYPE_ERROR, VALUE_ERROR};
+
+/// Registers a value of the enum type `E`, then its attributes.
+///
+/// [`new`](Self::new) registers the value at once, and
+/// [`set_attr`](Self::set_attr) sets each attribute on it, which
+/// [`EnumRef::attr_map`] reads. The value's object is an [`EnumObj`], so `E`'s
+/// object type adds no fields to its parent `EnumObj`.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::derive::{Object, ObjectRef};
+/// use tvm_ffi::reflection::EnumDef;
+/// use tvm_ffi::{EnumObj, EnumRef, ObjectArc};
+///
+/// #[repr(C)]
+/// #[derive(Object)]
+/// #[type_key = "my_ext.Color"]
+/// #[type_register]
+/// #[type_final]
+/// pub struct ColorObj {
+///     base_: EnumObj,
+/// }
+///
+/// #[derive(Clone, ObjectRef)]
+/// pub struct Color {
+///     data: ObjectArc<ColorObj>,
+/// }
+///
+/// impl EnumRef for Color {}
+///
+/// let red = EnumDef::<Color>::new("red")?.set_attr("my_ext.hex", 
0xff0000_i64)?.value();
+///
+/// let hex = Color::attr_map::<i64>("my_ext.hex")?.unwrap();
+/// assert_eq!(hex.get(&red)?, Some(0xff0000));
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
+pub struct EnumDef<E: EnumRef> {
+    value: E,
+}
+
+impl<E: EnumRef> EnumDef<E> {
+    /// Register the value `name` of `E`.
+    ///
+    /// # Arguments
+    ///
+    /// * `name` - The name of the value.
+    ///
+    /// # Returns
+    ///
+    /// The builder of the value's attributes.
+    ///
+    /// # Errors
+    ///
+    /// Returns a `ValueError` if `E` already has a value named `name`, or a
+    /// `TypeError` if `E` is not an enum type or is an integer enum
+    /// (`ffi.IntEnum`), whose values name their own indices.
+    pub fn new(name: &str) -> Result<Self> {
+        const {
+            assert!(
+                size_of::<E::ContainerType>() == size_of::<EnumObj>()
+                    && align_of::<E::ContainerType>() == align_of::<EnumObj>(),
+                "an enum type registered from Rust adds no fields to `EnumObj`"
+            )
+        };
+        let type_index = E::ContainerType::type_index();
+        let type_key = E::ContainerType::TYPE_KEY;
+        if !is_instance_of::<EnumObj>(type_index) || is_int_enum(type_index) {
+            return Err(Error::new(
+                TYPE_ERROR,
+                &format!("`{type_key}` is not an enum type whose values Rust 
can register"),
+                "",
+            ));
+        }
+        let state = ensure_state::<E>()?;
+        let index = state.entries.len() as i64;
+        let key = String::from(name);
+        let indexes = AnyView::from(&state.indexes);
+        if dict_get(indexes, AnyView::from(&key))?.is_some()
+            || dict_get(indexes, AnyView::from(&index))?.is_some()
+        {
+            return Err(Error::new(
+                VALUE_ERROR,
+                &format!("Enum `{type_key}` already has a value named 
`{name}`"),
+                "",
+            ));
+        }
+        let object = ObjectArc::into_raw(ObjectArc::new(EnumObj::new(index, 
name)));
+        // SAFETY: `E`'s object is an `EnumObj` with `E`'s type index (asserted
+        // above), and the object is not shared yet. The deleter stays that of
+        // `EnumObj`, which drops the same fields.
+        let value = unsafe {
+            (*object.cast_mut().cast::<TVMFFIObject>()).type_index = 
type_index;
+            E::from_data(ObjectArc::from_raw(object.cast()))
+        };
+        state.entries.push(Any::from(value.clone()))?;
+        dict_set(indexes, AnyView::from(&index), AnyView::from(&value))?;
+        dict_set(indexes, AnyView::from(&key), AnyView::from(&value))?;
+        Ok(Self { value })
+    }
+
+    /// Set the attribute `name` of the value.
+    ///
+    /// See also [`EnumRef::attr_map`].
+    ///
+    /// # Arguments
+    ///
+    /// * `name` - The name of the attribute.
+    /// * `value` - The value of the attribute.
+    ///
+    /// # Returns
+    ///
+    /// The builder, for the next attribute.
+    pub fn set_attr(self, name: &str, value: impl Into<Any>) -> Result<Self> {
+        let state = ensure_state::<E>()?;
+        let attrs = AnyView::from(&state.attrs);
+        let key = String::from(name);
+        let column = match dict_get(attrs, AnyView::from(&key))? {
+            Some(column) => column,
+            None => {
+                let column = 
crate::cached_global_func!("ffi.Dict").call_packed(&[])?;
+                dict_set(attrs, AnyView::from(&key), AnyView::from(&column))?;
+                column
+            }
+        };
+        let value = value.into();
+        dict_set(
+            AnyView::from(&column),
+            AnyView::from(&self.value),
+            AnyView::from(&value),
+        )?;
+        Ok(self)
+    }
+
+    /// The registered value.
+    pub fn value(&self) -> E {
+        self.value.clone()
+    }
+}
+
+/// One attribute of the values of the enum type `E`, as `V`, returned by
+/// [`EnumRef::attr_map`].
+pub struct EnumAttrMap<E, V> {
+    column: Any,
+    _types: PhantomData<fn() -> (E, V)>,
+}
+
+impl<E: EnumRef, V: AnyCompatible> EnumAttrMap<E, V> {
+    pub(crate) fn lookup(name: &str) -> Result<Option<Self>> {
+        let Some(state) = enum_state(E::ContainerType::type_index())? else {
+            return Ok(None);
+        };
+        let key = String::from(name);
+        let column = dict_get(AnyView::from(&state.attrs), 
AnyView::from(&key))?;
+        Ok(column.map(|column| Self {
+            column,
+            _types: PhantomData,
+        }))
+    }
+
+    /// The attribute of `value`, or `None` when it has none.
+    ///
+    /// # Errors
+    ///
+    /// Returns a `TypeError` if the attribute is not a `V`.
+    pub fn get(&self, value: &E) -> Result<Option<V>> {
+        dict_get(AnyView::from(&self.column), AnyView::from(value))?
+            .map(any_into)
+            .transpose()
+    }
+}
+
+/// The type attribute that holds an enum type's registry.
+const ENUM_STATE_ATTR: &str = "__ffi_enum__";
+
+/// The registry of one enum type: its values in registration order, the
+/// values by index and by name (`Dict<Any, ObjectRef>`), and the attribute
+/// columns (`Dict<String, Dict<ObjectRef, Any>>`).
+#[repr(C)]
+#[derive(Object)]
+#[type_key = "ffi.EnumState"]
+#[type_final]
+pub(crate) struct EnumStateObj {
+    base_: Object,
+    entries: List<Any>,
+    // The crate has no `Dict`; `dict_get` and `dict_set` call its global
+    // functions instead.
+    pub(crate) indexes: ObjectRef,
+    attrs: ObjectRef,
+}
+
+#[repr(C)]
+#[derive(ObjectRef, Clone)]
+pub(crate) struct EnumState {
+    data: ObjectArc<EnumStateObj>,
+}
+
+/// The registry of the enum type `type_index`, or `None` before its first 
value.
+pub(crate) fn enum_state(type_index: i32) -> Result<Option<EnumState>> {
+    let Some(column) = TypeAttrColumn::new(ENUM_STATE_ATTR) else {
+        return Ok(None);
+    };
+    column.get(type_index).map(EnumState::try_from).transpose()
+}
+
+/// The registry of `E`, created with its first value.
+fn ensure_state<E: EnumRef>() -> Result<EnumState> {
+    if let Some(state) = enum_state(E::ContainerType::type_index())? {
+        return Ok(state);
+    }
+    let state: EnumState =
+        any_into(get_method("ffi.EnumState", 
"__ffi_init__")?.call_packed(&[])?)?;
+    TypeAttrDef::<E::ContainerType>::new().attr(ENUM_STATE_ATTR, 
state.clone())?;
+    Ok(state)
+}
+
+fn is_int_enum(type_index: i32) -> bool {
+    let is_int_enum =
+        |info: *const TVMFFITypeInfo| unsafe { (*info).type_key.as_str() == 
"ffi.IntEnum" };
+    unsafe {
+        let info = TVMFFIGetTypeInfo(type_index);
+        !info.is_null()
+            && (is_int_enum(info)
+                || (0..(*info).type_depth as usize)
+                    .any(|depth| 
is_int_enum(*(*info).type_acenstors.add(depth))))
+    }
+}
+
+pub(crate) fn dict_get(dict: AnyView<'_>, key: AnyView<'_>) -> 
Result<Option<Any>> {
+    // `ffi.DictGetItemOrMissing` saves a call but returns a sentinel object
+    // that Rust cannot name.
+    let count: i64 = crate::cached_global_func!("ffi.DictCount")
+        .call_packed(&[dict, key])?
+        .try_into()?;
+    if count == 0 {
+        return Ok(None);
+    }
+    crate::cached_global_func!("ffi.DictGetItem")
+        .call_packed(&[dict, key])
+        .map(Some)
+}
+
+fn dict_set(dict: AnyView<'_>, key: AnyView<'_>, value: AnyView<'_>) -> 
Result<()> {
+    crate::cached_global_func!("ffi.DictSetItem").call_packed(&[dict, key, 
value])?;
+    Ok(())
+}
+
+pub(crate) fn any_into<T: AnyCompatible>(value: Any) -> Result<T> {
+    
TryFromTemp::<T>::try_from(AnyView::from(&value)).map(TryFromTemp::into_value)
+}
diff --git a/rust/tvm-ffi/src/reflection/registry.rs 
b/rust/tvm-ffi/src/reflection/registry.rs
index d21279d9..979412d4 100644
--- a/rust/tvm-ffi/src/reflection/registry.rs
+++ b/rust/tvm-ffi/src/reflection/registry.rs
@@ -165,7 +165,8 @@ impl BitOr for FieldFlags {
 ///
 /// ```
 /// use tvm_ffi::derive::ObjectRef;
-/// use tvm_ffi::{reflection, rs_object, Object, ObjectArc, ObjectDef, Result};
+/// use tvm_ffi::reflection::{self, ObjectDef};
+/// use tvm_ffi::{rs_object, Object, ObjectArc, Result};
 ///
 /// #[rs_object("my_ext.MyPair", final, reflection = 
MyPairObj::reflect_methods)]
 /// pub struct MyPairObj {
@@ -215,8 +216,7 @@ impl<N: ObjectCore> ObjectDef<N> {
     /// The caller holds `TYPE_REGISTRATION` for the builder's lifetime, and
     /// `type_index` is `N`'s. `creator` returns an owned handle of an object 
of
     /// `N` that the field setters can fill.
-    #[doc(hidden)]
-    pub unsafe fn registering(
+    unsafe fn registering(
         type_index: i32,
         kind: Option<SEqHashKind>,
         creator: Option<TVMFFIObjectCreator>,
@@ -304,8 +304,7 @@ impl<N: ObjectCore> ObjectDef<N> {
 
     /// Registers `__ffi_shallow_copy__` from [`ShallowCopy`], which reflected
     /// mutation (copy on write through the field setters) needs.
-    #[doc(hidden)]
-    pub fn shallow_copy(self) -> Result<Self>
+    fn shallow_copy(self) -> Result<Self>
     where
         N: ShallowCopy,
     {
@@ -392,8 +391,7 @@ impl<N: ObjectCore> ObjectDef<N> {
     /// `__ffi_init__` from the fields unless the type defines its own, then
     /// `__ffi_type_final__`, which no other registered metadata exposes; and
     /// reports the first failed method registration.
-    #[doc(hidden)]
-    pub fn finish(self) -> Result<()> {
+    fn finish(self) -> Result<()> {
         if let Some(error) = self.error {
             return Err(error);
         }
@@ -425,6 +423,85 @@ impl<N: ObjectCore> ObjectDef<N> {
     }
 }
 
+/// Registers the reflection of `N` for `#[derive(Object)]`: its metadata,
+/// then `fields` its fields, its `__ffi_shallow_copy__`, then `methods` its
+/// methods, then its type attributes.
+///
+/// # Safety
+///
+/// As `ObjectDef::registering`; `fields` registers fields of `N` as
+/// [`ObjectDef::field`] requires.
+#[doc(hidden)]
+pub unsafe fn register_object<N: ObjectCore + ShallowCopy>(
+    type_index: i32,
+    kind: Option<SEqHashKind>,
+    creator: Option<TVMFFIObjectCreator>,
+    fields: impl FnOnce(ObjectDef<N>) -> Result<ObjectDef<N>>,
+    methods: impl FnOnce(&mut ObjectDef<N>),
+) -> Result<()> {
+    let mut def = fields(ObjectDef::registering(type_index, kind, 
creator)?)?.shallow_copy()?;
+    methods(&mut def);
+    def.finish()
+}
+
+/// Registers type attributes of the object type `T`, each as soon as
+/// [`attr`](Self::attr) names it, for every language to read with
+/// [`TypeAttrColumn`](crate::reflection::TypeAttrColumn).
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::derive::Object;
+/// use tvm_ffi::reflection::TypeAttrDef;
+/// use tvm_ffi::reflection::TypeAttrColumn;
+/// use tvm_ffi::{Object, ObjectCore};
+///
+/// #[repr(C)]
+/// #[derive(Object)]
+/// #[type_key = "my_ext.MyPair"]
+/// #[type_register]
+/// pub struct MyPairObj {
+///     base_: Object,
+/// }
+///
+/// TypeAttrDef::<MyPairObj>::new().attr("my_ext.arity", 2_i64)?;
+///
+/// let column = TypeAttrColumn::new("my_ext.arity").unwrap();
+/// let arity: i64 = column.get(MyPairObj::type_index()).unwrap().try_into()?;
+/// assert_eq!(arity, 2);
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
+pub struct TypeAttrDef<T: ObjectCore> {
+    _type: PhantomData<fn() -> T>,
+}
+
+impl<T: ObjectCore> TypeAttrDef<T> {
+    /// A builder for the type attributes of `T`.
+    #[allow(clippy::new_without_default)]
+    pub fn new() -> Self {
+        Self { _type: PhantomData }
+    }
+
+    /// Register the type attribute `name` of `T`.
+    ///
+    /// # Arguments
+    ///
+    /// * `name` - The name of the attribute.
+    /// * `value` - The value of the attribute.
+    ///
+    /// # Returns
+    ///
+    /// The builder, for the next attribute.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if `T` already has the attribute `name`.
+    pub fn attr(self, name: &str, value: impl Into<Any>) -> Result<Self> {
+        register_type_attr(T::type_index(), name, value.into())?;
+        Ok(self)
+    }
+}
+
 /// The structural kind of the nearest ancestor of `info` with metadata.
 unsafe fn inherited_kind(info: *const crate::tvm_ffi_sys::TVMFFITypeInfo) -> 
i32 {
     for depth in (0..(*info).type_depth).rev() {
diff --git a/rust/tvm-ffi/tests/test_enum.rs b/rust/tvm-ffi/tests/test_enum.rs
new file mode 100644
index 00000000..54aecd95
--- /dev/null
+++ b/rust/tvm-ffi/tests/test_enum.rs
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+//! An enum registered in C++ (`testing.TestEnumVariant`, values `Alpha` and 
`Beta` with the
+//! attribute `code`), extended from Rust.
+
+use tvm_ffi::derive::{Object, ObjectRef};
+use tvm_ffi::reflection::EnumDef;
+use tvm_ffi::{EnumObj, EnumRef, ObjectArc, ObjectRefCore, Result, VALUE_ERROR};
+
+#[repr(C)]
+#[derive(Object)]
+#[type_key = "testing.TestEnumVariant"]
+#[type_final]
+struct TestEnumVariantObj {
+    base_: EnumObj,
+}
+
+#[derive(Clone, ObjectRef)]
+struct TestEnumVariant {
+    data: ObjectArc<TestEnumVariantObj>,
+}
+
+impl EnumRef for TestEnumVariant {}
+
+#[test]
+fn test_enum_lookup_register_and_attrs() -> Result<()> {
+    // Keep the testing library linked so its static type registrations run.
+    assert_eq!(
+        unsafe { tvm_ffi::tvm_ffi_sys::TVMFFITestingDummyTarget() },
+        0
+    );
+
+    let alpha = TestEnumVariant::get("Alpha")?;
+    assert_eq!(alpha.name(), "Alpha");
+    assert!(alpha.same_as(&TestEnumVariant::get("Alpha")?));
+    assert_eq!(
+        TestEnumVariant::get("Delta").unwrap_err().kind(),
+        VALUE_ERROR
+    );
+
+    let code = TestEnumVariant::attr_map::<i64>("code")?.unwrap();
+    assert_eq!(code.get(&alpha)?, Some(10));
+    assert!(TestEnumVariant::attr_map::<i64>("no_such_attr")?.is_none());
+
+    let gamma = EnumDef::<TestEnumVariant>::new("Gamma")?
+        .set_attr("code", 30_i64)?
+        .value();
+    assert!(gamma.same_as(&TestEnumVariant::get("Gamma")?));
+    // The map read before the registration sees the new value.
+    assert_eq!(code.get(&gamma)?, Some(30));
+    assert_eq!(
+        EnumDef::<TestEnumVariant>::new("Beta")
+            .err()
+            .unwrap()
+            .kind(),
+        VALUE_ERROR
+    );
+    Ok(())
+}
diff --git a/rust/tvm-ffi/tests/test_object_reflection.rs 
b/rust/tvm-ffi/tests/test_object_reflection.rs
index e58436a0..6071039a 100644
--- a/rust/tvm-ffi/tests/test_object_reflection.rs
+++ b/rust/tvm-ffi/tests/test_object_reflection.rs
@@ -18,7 +18,7 @@
  */
 use tvm_ffi::derive::{Object, ObjectRef};
 use tvm_ffi::reflection::TypeAttrColumn;
-use tvm_ffi::reflection::{FieldFlags, ObjectDef};
+use tvm_ffi::reflection::{FieldFlags, FieldGetter, ObjectDef};
 use tvm_ffi::*;
 use tvm_ffi_sys::{
     TVMFFIFieldFlagBitMask, TVMFFIFieldInfo, TVMFFIFieldSetter, 
TVMFFIGetTypeInfo,
diff --git a/rust/tvm-ffi/tests/test_rs_object.rs 
b/rust/tvm-ffi/tests/test_rs_object.rs
index 7a4aadb4..848fef67 100644
--- a/rust/tvm-ffi/tests/test_rs_object.rs
+++ b/rust/tvm-ffi/tests/test_rs_object.rs
@@ -21,7 +21,9 @@ use std::borrow::Borrow;
 use tvm_ffi::derive::{Object, ObjectRef};
 use tvm_ffi::object::{ObjectArc, ObjectRef, ObjectRefCore};
 use tvm_ffi::reflection::TypeAttrColumn;
-use tvm_ffi::reflection::{drop_if_defined, FieldFlags, ObjectDef};
+use tvm_ffi::reflection::{
+    drop_if_defined, FieldFlags, FieldGetter, ObjectDef, ShallowCopy, 
UnsafeInit,
+};
 use tvm_ffi::*;
 use tvm_ffi_sys::{TVMFFIFieldInfo, TVMFFIGetTypeInfo, TVMFFISEqHashKind, 
TVMFFITypeInfo};
 
diff --git a/rust/tvm-ffi/tests/test_structural_mutate.rs 
b/rust/tvm-ffi/tests/test_structural_mutate.rs
index 6d9a388a..3b6059e1 100644
--- a/rust/tvm-ffi/tests/test_structural_mutate.rs
+++ b/rust/tvm-ffi/tests/test_structural_mutate.rs
@@ -22,11 +22,12 @@ use tvm_ffi::collections::map::MapObj;
 use tvm_ffi::extra::structural_mutate::IntoMutateResult;
 use tvm_ffi::function::FunctionObj;
 use tvm_ffi::object::ObjectRef;
+use tvm_ffi::reflection::FieldGetter;
 use tvm_ffi::ObjectRefCore;
 use tvm_ffi::{
     dispatch, structural_map, structural_mutate, structural_visit, 
structural_walk, Any, AnyView,
-    Array, DefRegionKind, Dispatcher, Error, FieldGetter, Function, 
InplaceMode, IntoDispatcher,
-    Map, MutationView, Object, ObjectArc, Result, String as FfiString, 
StructuralMutatorView,
+    Array, DefRegionKind, Dispatcher, Error, Function, InplaceMode, 
IntoDispatcher, Map,
+    MutationView, Object, ObjectArc, Result, String as FfiString, 
StructuralMutatorView,
     StructuralVisitorView, TypeIndex, Unchanged, UnchangedOr, VisitInterrupt, 
WalkOrder,
     WalkResult, RUNTIME_ERROR,
 };
diff --git a/rust/tvm-ffi/tests/test_structural_visit.rs 
b/rust/tvm-ffi/tests/test_structural_visit.rs
index fafeb596..47a2a71b 100644
--- a/rust/tvm-ffi/tests/test_structural_visit.rs
+++ b/rust/tvm-ffi/tests/test_structural_visit.rs
@@ -19,13 +19,12 @@
 
 use std::cell::{Cell, RefCell};
 use tvm_ffi::object::ObjectRef;
-use tvm_ffi::reflection::TypeAttrColumn;
+use tvm_ffi::reflection::{FieldGetter, TypeAttrColumn};
 use tvm_ffi::ObjectRefCore;
 use tvm_ffi::{
     dispatch, structural_visit, structural_walk, Any, AnyView, Array, 
DLDataType, DLDataTypeCode,
-    DefRegionKind, Dispatcher, Error, FieldGetter, Function, Map, Object, 
Result,
-    String as FfiString, StructuralVisitorView, TypeIndex, VisitInterrupt, 
WalkOrder, WalkResult,
-    RUNTIME_ERROR,
+    DefRegionKind, Dispatcher, Error, Function, Map, Object, Result, String as 
FfiString,
+    StructuralVisitorView, TypeIndex, VisitInterrupt, WalkOrder, WalkResult, 
RUNTIME_ERROR,
 };
 
 fn runtime_error(message: &str) -> Error {
diff --git a/tests/python/test_stubgen_rust.py 
b/tests/python/test_stubgen_rust.py
index 1ad7c48b..14d48e5a 100644
--- a/tests/python/test_stubgen_rust.py
+++ b/tests/python/test_stubgen_rust.py
@@ -423,7 +423,7 @@ def test_render_root_object() -> None:
         "tvm_ffi::Object",
         "tvm_ffi::ObjectArc",
         "tvm_ffi::ObjectCore",
-        "tvm_ffi::FieldGetter",
+        "tvm_ffi::reflection::FieldGetter",
         "tvm_ffi::Result",
         "tvm_ffi::String",
         "tvm_ffi::Array",
@@ -475,7 +475,7 @@ def test_render_derived_object_cross_module() -> None:
 
 
 def test_render_object_under_builtin_parent() -> None:
-    """A builtin parent is embedded via header-only stand-ins so `TYPE_DEPTH` 
matches the registry."""
+    """A builtin parent the crate lacks is a header-only stand-in, so 
`TYPE_DEPTH` matches."""
     info = _info(
         "demo.Color",
         (_field("value", "int"),),
@@ -484,13 +484,12 @@ def test_render_object_under_builtin_parent() -> None:
     )
     text, imports = _render(info)
     assert "    base_: FfiIntEnumObj," in text
-    # One stand-in per builtin ancestor below `ffi.Object`, chained through 
`base`.
-    assert imports.builtin_mirrors == {"ffi.Enum": "Object", "ffi.IntEnum": 
"FfiEnumObj"}
-    assert "tvm_ffi::Object" in _uses(imports)
-    # No Deref, upcast or crate import for the stand-ins.
+    # One stand-in per builtin ancestor below the crate's `EnumObj`, chained 
through `base`.
+    assert imports.builtin_mirrors == {"ffi.IntEnum": "EnumObj"}
+    assert "tvm_ffi::EnumObj" in _uses(imports)
+    # No Deref or upcast for the stand-ins.
     assert "impl Deref for ColorObj" not in text
     assert "unsafe_impl_object_ref_upcast" not in text
-    assert not any("Enum" in path for path in _uses(imports))
     # A child of `ffi.Object` embeds the crate's header.
     text, imports = _render(_info("demo.Root"))
     assert "    base_: Object," in text
@@ -507,25 +506,16 @@ def test_render_object_under_builtin_parent() -> None:
 
 
 BUILTIN_MIRRORS_EXPECTED = """\
-use tvm_ffi::Object;
+use tvm_ffi::EnumObj;
 use tvm_ffi::ObjectArc;
 
-/// Header-only stand-in for the builtin `ffi.Enum`; it only carries the 
ancestor depth.
-#[allow(dead_code)]
-#[repr(C)]
-#[derive(tvm_ffi::derive::Object)]
-#[type_key = "ffi.Enum"]
-struct FfiEnumObj {
-    base_: Object,
-}
-
 /// Header-only stand-in for the builtin `ffi.IntEnum`; it only carries the 
ancestor depth.
 #[allow(dead_code)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::Object)]
 #[type_key = "ffi.IntEnum"]
 struct FfiIntEnumObj {
-    base_: FfiEnumObj,
+    base_: EnumObj,
 }
 
 /// Header-only stand-in for the builtin `ffi.StrEnum`; it only carries the 
ancestor depth.
@@ -534,7 +524,7 @@ struct FfiIntEnumObj {
 #[derive(tvm_ffi::derive::Object)]
 #[type_key = "ffi.StrEnum"]
 struct FfiStrEnumObj {
-    base_: FfiEnumObj,
+    base_: EnumObj,
 }"""
 
 
@@ -849,8 +839,8 @@ def test_render_complete_expr_golden() -> None:
         "std::ptr::addr_of_mut",
         "tvm_ffi::Object",
         "tvm_ffi::ObjectArc",
-        "tvm_ffi::ShallowCopy",
-        "tvm_ffi::UnsafeInit",
+        "tvm_ffi::reflection::ShallowCopy",
+        "tvm_ffi::reflection::UnsafeInit",
         "tvm_ffi::reflection::drop_if_defined",
     }
 
@@ -971,7 +961,7 @@ def test_render_complete_enum_field() -> None:
     assert "    pub kind: Kind,\n" in text
     assert "    pub fn new(a: i64, kind: impl Into<Kind>) -> Self {" in text
     assert {"tvm_ffi::Result", "tvm_ffi::Error", "tvm_ffi::VALUE_ERROR"} <= 
_uses(imports)
-    assert "tvm_ffi::FieldGetter" not in _uses(imports)
+    assert "tvm_ffi::reflection::FieldGetter" not in _uses(imports)
 
 
 def test_allow_missing_docs_and_closed_enum() -> None:
@@ -1069,7 +1059,7 @@ def test_final_type_has_no_unsafe_init() -> None:
     info = _info("demo.Leaf", (_field("a", "int", 24, 8),), total_size=32, 
is_final=True)
     text, imports = _render(info)
     assert "UnsafeInit" not in text
-    assert "tvm_ffi::UnsafeInit" not in _uses(imports)
+    assert "tvm_ffi::reflection::UnsafeInit" not in _uses(imports)
 
 
 def test_complete_list_field_mirrors() -> None:
@@ -1279,19 +1269,16 @@ def test_opaque_directive_vetoes_a_complete_type() -> 
None:
 
 
 def test_builtin_parent_keeps_the_type_opaque() -> None:
-    """The crate never mirrors a builtin's bytes: the header-only stand-in 
cannot be complete."""
-    # Fieldless under `ffi.Enum` (48 bytes): the fill criterion alone would 
call this complete.
-    info = _info(
-        "demo.Flag", parent="ffi.Enum", ancestors=["ffi.Object", "ffi.Enum"], 
total_size=48
-    )
-    text, _ = _render(info)
-    assert "/// Opaque: parent 'ffi.Enum' is opaque (no-mirror)." in text
-    assert "    base_: FfiEnumObj," in text
-    assert "const _: () =" not in text
+    """The crate's `EnumObj` is a complete parent; a builtin stand-in cannot 
be one."""
+    # An enum type embeds `EnumObj`; its registry allocates the values, so 
there is no `new`.
+    text, _ = _render(object_info_from_type_key("testing.TestEnumVariant"))
+    assert "/// Complete: reflected fields fill [48, 48) exactly." in text
+    assert "    base_: EnumObj," in text
+    assert "size_of::<TestEnumVariantObj>() == 48" in text
     assert "fn new(" not in text
-    # The registry fixtures under `ffi.Enum` / `ffi.IntEnum` / `ffi.StrEnum` 
follow the same rule.
+    assert "impl tvm_ffi::EnumRef for TestEnumVariant {}" in text
+    # The crate never mirrors the bytes of `ffi.IntEnum` / `ffi.StrEnum`.
     for type_key, parent in (
-        ("testing.TestEnumVariant", "ffi.Enum"),
         ("testing.TestCxxIntEnum", "ffi.IntEnum"),
         ("testing.TestCxxStrEnum", "ffi.StrEnum"),
     ):
@@ -1299,6 +1286,7 @@ def test_builtin_parent_keeps_the_type_opaque() -> None:
         assert f"/// Opaque: parent '{parent}' is opaque (no-mirror)." in text
         assert "const _: () =" not in text
         assert "fn new(" not in text
+        assert f"impl tvm_ffi::EnumRef for {type_key.rpartition('.')[2]} {{}}" 
in text
 
 
 def test_opaque_parent_keeps_the_child_opaque() -> None:
@@ -1438,7 +1426,7 @@ def test_registry_complete_chain_is_mirrored() -> None:
         "tvm_ffi::unsafe_impl_object_ref_upcast!(TestCxxClassDerivedDerived => 
TestCxxClassBase, "
         "TestCxxClassDerivedDerived => TestCxxClassDerived);"
     )
-    assert {"tvm_ffi::String", "tvm_ffi::ShallowCopy"} <= _uses(imports)
+    assert {"tvm_ffi::String", "tvm_ffi::reflection::ShallowCopy"} <= 
_uses(imports)
 
 
 def test_registry_hidden_field_and_vptr_stay_opaque() -> None:
@@ -1498,7 +1486,9 @@ def test_cli_init_generates_a_module_tree(tmp_path: Path, 
monkeypatch: pytest.Mo
     assert (root / "mod.rs").read_text(encoding="utf-8") == "pub mod 
testing;\n"
     text = (root / "testing" / "mod.rs").read_text(encoding="utf-8")
     assert text.startswith("#![allow(dead_code, unused_imports)]\n")
-    assert "use tvm_ffi::FieldGetter;" in text  # the opaque fixtures read 
through getters
+    assert (
+        "use tvm_ffi::reflection::FieldGetter;" in text
+    )  # the opaque fixtures read through getters
     assert '#[type_key = "testing.TestCxxClassDerivedDerived"]' in text
     assert "    base_: TestCxxClassDerivedObj,\n    pub v_str: String," in text
     assert (
@@ -1506,11 +1496,11 @@ def test_cli_init_generates_a_module_tree(tmp_path: 
Path, monkeypatch: pytest.Mo
         "TestCxxClassDerivedDerived => TestCxxClassDerived);"
     ) in text
     # Builtin ancestors are mirrored once, in the import section; enum 
fixtures embed the last one.
-    assert text.count("struct FfiEnumObj {\n    base_: Object,\n}") == 1
-    assert text.count("struct FfiIntEnumObj {\n    base_: FfiEnumObj,\n}") == 1
-    assert text.count("struct FfiStrEnumObj {\n    base_: FfiEnumObj,\n}") == 1
+    assert "struct FfiEnumObj" not in text
+    assert text.count("struct FfiIntEnumObj {\n    base_: EnumObj,\n}") == 1
+    assert text.count("struct FfiStrEnumObj {\n    base_: EnumObj,\n}") == 1
     assert text.index("struct FfiIntEnumObj") < 
text.index(f"{C.RUST_SYNTAX.begin} object/")
-    assert "pub struct TestEnumVariantObj {\n    base_: FfiEnumObj,\n}" in text
+    assert "pub struct TestEnumVariantObj {\n    base_: EnumObj,\n}" in text
     assert "pub struct TestCxxIntEnumObj {\n    base_: FfiIntEnumObj,\n}" in 
text
     assert "pub struct TestCxxStrEnumObj {\n    base_: FfiStrEnumObj,\n}" in 
text
     # Running again over the generated tree is a no-op.

Reply via email to