Skip to content

[FEAT][RUST] Register the reflection of Rust-defined object types - #859

Merged
tqchen merged 1 commit into
apache:mainfrom
Seven-Streams:feat/rust-reflect-register
Oct 20, 2026
Merged

tqchen merged 1 commit into
apache:mainfrom
Seven-Streams:feat/rust-reflect-register

Conversation

@Seven-Streams

@Seven-Streams Seven-Streams commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Summary

Let an object type defined in Rust register its reflection with the type
table, as Python _register_py_class and C++ refl::ObjectDef<T> already do.
Once registered, the fields of a Rust type are visible to C++ structural
equality and hashing, ffi.ReprPrint, ffi.DeepCopy and Python's generated
classes, so every language can define first-class types.

#[repr(C)]
#[derive(Object)]
#[type_key = "my.Point"]
#[type_register]
#[type_s_eq_hash_kind = "TreeNode"]
pub struct PointObj {
    base: Object,
    #[reflect]
    pub x: i64,
    #[reflect(writable, seq_hash_ignore)]
    pub label: tvm_ffi::String,
}

Changes

  • tvm-ffi-sys: declare TVMFFITypeRegisterField, TVMFFITypeRegisterMethod
    and TVMFFITypeRegisterMetadata.
  • tvm_ffi::reflection: FieldFlags, SEqHashKind and ShallowCopy
    (implemented for Object), plus a hidden builder, ObjectDef<N>, that only
    the derive calls. It registers the type metadata (size, structural eq/hash
    kind), one field record per field (C ABI getter and setter, type_schema,
    static type index), and the __ffi_shallow_copy__ and __ffi_type_final__
    type attributes. If the type already has metadata, every call is a no-op.
    Its entry point and field are unsafe: the derive takes each field's type
    and offset_of! from the same field, which is what makes the registered
    getter and setter sound. There is no public API for registering fields by
    hand.
  • #[derive(Object)]: on a #[type_register] type, #[reflect] /
    #[reflect(flag, ...)] fields are registered when the type is (on its first
    type_index(), under the existing registration lock), with offsets from
    offset_of!. Flag names map to FieldFlags constants.
    #[type_s_eq_hash_kind = "..."] sets the kind. Without it, the type takes
    its nearest reflected ancestor's kind, as a C++ subclass inherits
    _type_s_eq_hash_kind. The derive also implements ShallowCopy: it copies
    the parent with ShallowCopy and clones every other field.
  • #[derive(ObjectRef)]: name ObjectArc by path in the three places that
    used the bare name, so the derive no longer needs ObjectArc in scope.

Compatibility

  • Additive. Types without #[reflect] / #[type_s_eq_hash_kind] expand as
    before.
  • A reflected type's parent must implement ShallowCopy. Object and
    reflected Rust types do, but a parent bound from C++ needs a hand-written
    impl. The reflected type's other fields must be Clone, and its reflected
    fields must be AnyCompatible.
  • No creator or __ffi_new__ is registered, so ffi.ToJSONGraph rejects
    these types; serialization is out of scope for this PR.
  • The static type index and schema follow C++ TypeTraits: Optional<T>,
    String, Bytes and BigInt are kTVMFFIAny; Array and List have
    their own indices; Map, Dict and other object references are
    kTVMFFIObject.

Tests

  • New tests/test_object_reflection.rs covers:
    • field records, flags, schemas and the inherited kind in the type table
    • reads and writes through the C ABI getter and setter, including the
      TypeError on a value of the wrong type
    • C++ ffi.StructuralEqual / ffi.StructuralHash, including a
      seq_hash_ignore field
    • ffi.ReprPrint and ffi.DeepCopy
    • __ffi_shallow_copy__ and __ffi_type_final__
    • a #[type_register] type without #[reflect] registers no reflection
    • derives expanded in a module with no imports
  • Unit tests for the type-schema derivation.
  • cargo test (whole workspace), cargo clippy --all-targets, cargo doc,
    and pre-commit run on the changed files.

Rust gains what Python `_register_py_class` and C++ `refl::ObjectDef<T>`
already do: register a type's fields and metadata with the type table, so
that C++ structural equality and hashing, `ReprPrint`, `DeepCopy` and
Python see the fields of a Rust type.

- tvm-ffi-sys declares `TVMFFITypeRegisterField`, `TVMFFITypeRegisterMethod`
  and `TVMFFITypeRegisterMetadata`.
- `#[derive(Object)]` on a `#[type_register]` type registers its
  `#[reflect]` / `#[reflect(writable, ...)]` fields when the type is
  registered: the metadata, fields (C ABI getter/setter, type schema, static
  type index), and the `__ffi_shallow_copy__` and `__ffi_type_final__` type
  attributes. It takes `#[type_s_eq_hash_kind = "..."]` (otherwise the
  nearest reflected ancestor's kind) and implements `ShallowCopy`.
- `tvm_ffi::reflection` adds `FieldFlags`, `SEqHashKind` and `ShallowCopy`.
  The registration builder it uses, `ObjectDef`, is hidden and `unsafe`:
  only the derive, which takes each field's type and offset from the same
  field, calls it.
- `#[derive(ObjectRef)]` names `ObjectArc` by path, so it needs nothing
  in scope.
@Seven-Streams
Seven-Streams force-pushed the feat/rust-reflect-register branch from 65a5774 to d6e7dca Compare October 20, 2026 01:41
@Seven-Streams
Seven-Streams marked this pull request as ready for review October 20, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants