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 658bd6c5 [Rust] ObjectRefAnyCompatible and derive-only object 
reference declaration (#945)
658bd6c5 is described below

commit 658bd6c584aa462e37a9e8ce69ce74c1a519f9bf
Author: Tianqi Chen <[email protected]>
AuthorDate: Fri Nov 6 13:35:53 2026 -0500

    [Rust] ObjectRefAnyCompatible and derive-only object reference declaration 
(#945)
    
    **[Rust] ObjectRefAnyCompatible: overridable AnyCompatible base for
    object references**
    
    A reference with a stricter check than its object type, such as a typed
    expression view, had to hand-write its whole `AnyCompatible` over raw
    `TVMFFIAny` cells. `ObjectRefAnyCompatible` is the object-reference base
    of
    `AnyCompatible`, as C++ `TypeTraits<T>` derives from
    `ObjectRefTypeTraitsBase<T>`: its hooks are safe and defaulted, and a
    blanket
    impl makes each implementer `AnyCompatible` with the object cell
    encoding.
    
    - `extra_check(&ContainerType) -> bool` runs after the object-type
    check, in
      every strict and converting cast.
    - `convert_fallback(AnyView) -> Option<Self>` converts other values, as
    C++
    `ConvertFallbackValue` does, for converting casts only (`TryFrom<Any>`,
      typed function arguments); `downcast_ref`, `downcast` and `match_any!`
      never call it.
    - `custom_type_str()` and `custom_mismatch_type_info(AnyView)` replace
    the
      names in conversion errors and type schemas.
    - The hidden `MATCH_ANY_EXACT` defaults to false, so a hand-written impl
    is
    never routed by type index in `match_any!`; `#[derive(ObjectRef)]`,
    whose
      impl overrides nothing, sets it, and the blanket impl adds the object
      type's finality.
    
    `#[derive(ObjectRef)]` now emits `unsafe impl ObjectRefAnyCompatible for
    T`
    with the defaults instead of its own `AnyCompatible`. Its helper
    attributes
    become one namespaced `#[object_ref(skip_debug)]`; the unused `type_key`
    and `type_index` declarations are dropped. Containers keep their own
    `AnyCompatible`.
    
    
    **[Rust] Declare object references with #[derive(ObjectRef)] only**
    
    An object reference had two declaration forms: `def_object_ref!`, which
    also added `Deref`, `From<Obj>` and the parent upcast, and
    `#[derive(ObjectRef)]`, which left those to hand-written code. The
    derive
    becomes the one form, configured by one namespaced helper attribute
    `#[object_ref(...)]`:
    
    - `Deref<Target = Obj>` for field reads, as C++ `operator->`;
    `skip_deref`
    opts out for a reference whose object is internal (`Error`, `Function`,
      `Tensor`, `Shape`, `Module`, `ObjectRef`, ...).
    - A private `fn from_object(object: Obj) -> Self` for the type's own
    constructors instead of a public `From<Obj>`, so construction stays with
      the type's author.
    - `parent = Ref` adds the upcast to the parent's reference.
    - `skip_any_compatible` leaves `ObjectRefAnyCompatible` to a
      hand-written impl, so a typed reference derives everything else.
    - `skip_debug` as before, and `crate = path`.
    
    The derive's in-place downcasts now come from
    `unsafe_impl_object_ref_downcast_ref!`. `def_object_ref!` is removed;
    tvm-ffi's
    own docs, tests and `VisitInterrupt` use the derive.
    The Rust stub generator no longer emits a reference `Deref`, and the
    `rust_stubgen` example bindings are regenerated; bindings generated
    before
    this change define that `Deref` themselves and need regenerating.
    
    
    
    
    
    `unsafe_impl_object_upcast!` is renamed
    `unsafe_impl_object_ref_upcast!`; the stub generator emits the new name.
    
    
    The in-place borrow macro is renamed
    `unsafe_impl_object_ref_downcast_ref!`, and the new
    `impl_any_compatible_conversions!` emits the `TryFrom`, `call_tuple` and
    typed-function argument impls for a type that implements `AnyCompatible`
    by hand; the derive uses it, and its three building blocks are hidden.
---
 docs/guides/rust_lang_guide.rst                    |  40 ++-
 .../rust/src/generated/rust_stubgen/mod.rs         |   8 -
 python/tvm_ffi/stub/rust_generator/codegen.py      |  13 +-
 rust/tvm-ffi-macros/src/def_object_ref.rs          | 148 ----------
 rust/tvm-ffi-macros/src/lib.rs                     |  13 +-
 rust/tvm-ffi-macros/src/object_macros.rs           | 311 ++++++++++-----------
 rust/tvm-ffi/src/any.rs                            |   8 +-
 rust/tvm-ffi/src/collections/shape.rs              |   1 +
 rust/tvm-ffi/src/collections/tensor.rs             |   1 +
 rust/tvm-ffi/src/derive.rs                         |  72 ++++-
 rust/tvm-ffi/src/error.rs                          |   2 +-
 rust/tvm-ffi/src/extra/module.rs                   |   1 +
 rust/tvm-ffi/src/extra/structural_mutate.rs        |   5 +-
 rust/tvm-ffi/src/extra/structural_visit.rs         |  35 +--
 rust/tvm-ffi/src/function.rs                       |   1 +
 rust/tvm-ffi/src/function_internal.rs              |  48 +---
 rust/tvm-ffi/src/lib.rs                            |  60 +---
 rust/tvm-ffi/src/macros.rs                         | 121 ++++----
 rust/tvm-ffi/src/object.rs                         | 193 ++++++++++++-
 rust/tvm-ffi/src/reflection.rs                     |   2 +
 rust/tvm-ffi/src/type_traits.rs                    |  17 +-
 rust/tvm-ffi/tests/test_cast.rs                    |   2 +-
 rust/tvm-ffi/tests/test_object_ref_any.rs          | 159 +++++++++++
 rust/tvm-ffi/tests/test_object_reflection.rs       |   2 +-
 rust/tvm-ffi/tests/test_object_registration.rs     |   2 +-
 rust/tvm-ffi/tests/test_rs_object.rs               | 102 +++++--
 tests/python/test_stubgen_rust.py                  |  59 +---
 27 files changed, 811 insertions(+), 615 deletions(-)

diff --git a/docs/guides/rust_lang_guide.rst b/docs/guides/rust_lang_guide.rst
index 7a810785..1332452a 100644
--- a/docs/guides/rust_lang_guide.rst
+++ b/docs/guides/rust_lang_guide.rst
@@ -141,6 +141,9 @@ borrows it in place, with the same rules as ``Any`` and 
``AnyView``. ``try_from`
 ``Any`` may convert by the FFI's conversion rules, such as an integer into a 
float, while
 ``try_from`` a reference only checks.
 
+``AnyCompatible`` is the root contract for crossing into ``Any``; 
``#[derive(ObjectRef)]``
+is its object-reference default, and ``ObjectRefAnyCompatible`` customizes it.
+
 Loading a Module
 ~~~~~~~~~~~~~~~~
 
@@ -336,7 +339,7 @@ first ``type_index()``, which allocating an instance calls:
 
    impl PointObj {
        fn reflect_methods(def: &mut ObjectDef<Self>) {
-           def.def("x_abs", |p: Point| -> Result<i64> { Ok(p.data.x.abs()) }, 
"The |x|.")
+           def.def("x_abs", |p: Point| -> Result<i64> { Ok(p.x.abs()) }, "The 
|x|.")
                .def_static("zero", || -> Result<i64> { Ok(0) }, "");
        }
    }
@@ -366,12 +369,13 @@ Defining Object Types from Their Fields
 ``py_class``. The first field, ``base``, is the parent's object; every other 
field
 is reflected, and ``#[reflect(...)]`` sets its flags, name and doc string.
 ``reflection = path`` after the type key is ``#[type_reflection(path)]``.
-``def_object_ref!(TypeName, ParentType, ObjectName)`` defines the reference:
+``#[derive(ObjectRef)]`` declares the reference, and ``#[object_ref(parent = 
Point)]``
+names the reference to the parent's object:
 
 .. code-block:: rust
 
-   use tvm_ffi::object::ObjectRef;
-   use tvm_ffi::{def_object_ref, rs_object, Object};
+   use tvm_ffi::derive::ObjectRef;
+   use tvm_ffi::{rs_object, Object, ObjectArc};
 
    #[rs_object("my.Point", s_eq_hash_kind = "TreeNode")]
    pub struct PointObj {
@@ -381,7 +385,10 @@ is reflected, and ``#[reflect(...)]`` sets its flags, name 
and doc string.
        pub label: tvm_ffi::String,
    }
 
-   def_object_ref!(pub Point, ObjectRef, PointObj);
+   #[derive(Clone, ObjectRef)]
+   pub struct Point {
+       data: ObjectArc<PointObj>,
+   }
 
    #[rs_object("my.Point3", final)]
    pub struct Point3Obj {
@@ -389,9 +396,19 @@ is reflected, and ``#[reflect(...)]`` sets its flags, name 
and doc string.
        pub z: i64,
    }
 
-   def_object_ref!(pub Point3, Point, Point3Obj);
+   #[derive(Clone, ObjectRef)]
+   #[object_ref(parent = Point)]
+   pub struct Point3 {
+       data: ObjectArc<Point3Obj>,
+   }
+
+   impl Point3 {
+       pub fn new(x: i64, label: &str, z: i64) -> Self {
+           Self::from_object(Point3Obj::new(PointObj::new(Object::new(), x, 
label), z))
+       }
+   }
 
-   let point3 = Point3::from(Point3Obj::new(PointObj::new(Object::new(), 1, 
"p"), 3));
+   let point3 = Point3::new(1, "p", 3);
    assert_eq!((point3.x, point3.z), (1, 3));
    let point: Point = point3.into();
 
@@ -399,10 +416,11 @@ is reflected, and ``#[reflect(...)]`` sets its flags, 
name and doc string.
 ``Point3Obj::new``, which takes the parent's object, then the fields: a scalar
 (``bool``, an integer or a float) as its own type, so that a literal infers as 
it,
 and any other field from anything that converts into it, as the ``new`` of a 
type
-bound by ``tvm-ffi-stubgen`` does. The reference dereferences to the object, 
and
-the object converts into it. The reference also converts into ``ParentType``, 
the
-reference to its parent, and every reference converts into ``ObjectRef``;
-``unsafe_impl_object_upcast!`` converts it into the references to further 
ancestors.
+bound by ``tvm-ffi-stubgen`` does. The reference dereferences to the object
+(``#[object_ref(skip_deref)]`` opts out), and its private ``from_object`` 
wraps a new
+object for the type's constructors. The reference also converts into 
``parent``,
+and every reference converts into ``ObjectRef``; 
``unsafe_impl_object_ref_upcast!``
+converts it into the references to further ancestors.
 
 The type also registers a creator, and from it ``__ffi_new__`` and 
``__ffi_init__``
 (unless ``reflection = path`` defines ``__ffi_init__``), so that a JSON graph 
reads
diff --git a/examples/rust_stubgen/rust/src/generated/rust_stubgen/mod.rs 
b/examples/rust_stubgen/rust/src/generated/rust_stubgen/mod.rs
index 3d660241..a920ad01 100644
--- a/examples/rust_stubgen/rust/src/generated/rust_stubgen/mod.rs
+++ b/examples/rust_stubgen/rust/src/generated/rust_stubgen/mod.rs
@@ -24,7 +24,6 @@
 // tvm-ffi-stubgen(prefix): rust_stubgen
 
 // tvm-ffi-stubgen(begin): import-section
-use std::ops::Deref;
 use tvm_ffi::Error;
 use tvm_ffi::Object;
 use tvm_ffi::ObjectArc;
@@ -87,13 +86,6 @@ pub struct IntPair {
     base: ObjectArc<IntPairObj>,
 }
 
-impl Deref for IntPair {
-    type Target = IntPairObj;
-    fn deref(&self) -> &IntPairObj {
-        &self.base
-    }
-}
-
 impl IntPairObj {
     pub(crate) fn new(a: i64, b: i64, kind: PairKind) -> Self {
         let base = Object::new();
diff --git a/python/tvm_ffi/stub/rust_generator/codegen.py 
b/python/tvm_ffi/stub/rust_generator/codegen.py
index a8bf35c4..e4378814 100644
--- a/python/tvm_ffi/stub/rust_generator/codegen.py
+++ b/python/tvm_ffi/stub/rust_generator/codegen.py
@@ -16,8 +16,9 @@
 # under the License.
 """Rust code generation for ``tvm-ffi-stubgen``.
 
-Every reflected object gets a ``#[repr(C)]`` object struct, a reference 
wrapper,
-``Deref``, and the upcasts along its ancestor chain. The struct's contents 
follow
+Every reflected object gets a ``#[repr(C)]`` object struct, a reference wrapper
+(whose ``#[derive(ObjectRef)]`` adds ``Deref`` to the object), and the upcasts
+along its ancestor chain. The struct's contents follow
 the verdict of :mod:`tvm_ffi.stub.layout`:
 
 - *complete*: the struct mirrors every physical field at its real offset and
@@ -416,7 +417,7 @@ class _ObjectRenderer:
         ]
 
     def _upcast_lines(self) -> list[str]:
-        """``unsafe_impl_object_upcast!`` to every ancestor's wrapper, then 
the ``upcast`` directives."""
+        """``unsafe_impl_object_ref_upcast!`` to every ancestor's wrapper, 
then the ``upcast`` directives."""
         targets: list[str] = []
         for key in self.info.ancestors:
             provider = self._provider(key)
@@ -429,7 +430,7 @@ class _ObjectRenderer:
         if not targets:
             return []
         pairs = ", ".join(f"{self.leaf} => {target}" for target in targets)
-        return [f"tvm_ffi::unsafe_impl_object_upcast!({pairs});"]
+        return [f"tvm_ffi::unsafe_impl_object_ref_upcast!({pairs});"]
 
     def _struct_lines(self, verdict: Verdict, base: str) -> list[str]:
         """Render the object struct: every field when complete, the parent 
alone when opaque."""
@@ -655,9 +656,10 @@ class _ObjectRenderer:
         self.imports.reserved.update((self.leaf, self.obj_struct))
         verdict = self.classify()
         # Derive macros are spelled by full path: their leaves collide with 
`Object` / `ObjectRef`.
-        self.imports.record("std::ops::Deref")
         self.imports.record("tvm_ffi::ObjectArc")
         base, has_parent = self._base_type()
+        if has_parent:
+            self.imports.record("std::ops::Deref")
         fields = self.info.fields
         has_accessors = bool(fields) and not verdict.is_complete
         if has_accessors:
@@ -682,7 +684,6 @@ class _ObjectRenderer:
                 "}",
             ]
         )
-        sections.append(self._deref_lines(self.leaf, self.obj_struct, "base"))
         if has_parent:
             sections.append(self._deref_lines(self.obj_struct, base, "base"))
         if has_accessors:
diff --git a/rust/tvm-ffi-macros/src/def_object_ref.rs 
b/rust/tvm-ffi-macros/src/def_object_ref.rs
deleted file mode 100644
index 14b01eab..00000000
--- a/rust/tvm-ffi-macros/src/def_object_ref.rs
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * 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.
- */
-use proc_macro::TokenStream;
-use proc_macro2::TokenStream as TokenStream2;
-use quote::quote;
-use syn::parse::{Parse, ParseStream};
-use syn::{parse_macro_input, Attribute, DeriveInput, Ident, Path, Token, Type, 
Visibility};
-
-use crate::object_macros::expand_object_ref;
-use crate::utils::get_tvm_ffi_crate;
-
-pub(crate) fn def_object_ref(input: TokenStream) -> TokenStream {
-    let args = parse_macro_input!(input as DefObjectRefArgs);
-    expand(&args)
-        .unwrap_or_else(|error| error.to_compile_error())
-        .into()
-}
-
-/// `def_object_ref!(#[attrs] pub TypeName, ParentType, ObjectName, crate = 
path)`.
-struct DefObjectRefArgs {
-    attrs: Vec<Attribute>,
-    vis: Visibility,
-    type_name: Ident,
-    parent_type: Type,
-    object_name: Type,
-    tvm_ffi_crate: Option<Path>,
-}
-
-impl Parse for DefObjectRefArgs {
-    fn parse(input: ParseStream) -> syn::Result<Self> {
-        let attrs = input.call(Attribute::parse_outer)?;
-        let vis = input.parse()?;
-        let type_name = input.parse()?;
-        input.parse::<Token![,]>()?;
-        let parent_type = input.parse()?;
-        input.parse::<Token![,]>()?;
-        let object_name = input.parse()?;
-        let mut tvm_ffi_crate = None;
-        if input.parse::<Option<Token![,]>>()?.is_some() && 
input.peek(Token![crate]) {
-            input.parse::<Token![crate]>()?;
-            input.parse::<Token![=]>()?;
-            tvm_ffi_crate = Some(Path::parse_mod_style(input)?);
-            input.parse::<Option<Token![,]>>()?;
-        }
-        if !input.is_empty() {
-            return Err(input.error("expected `crate = <path>` after the object 
type"));
-        }
-        Ok(DefObjectRefArgs {
-            attrs,
-            vis,
-            type_name,
-            parent_type,
-            object_name,
-            tvm_ffi_crate,
-        })
-    }
-}
-
-fn expand(args: &DefObjectRefArgs) -> syn::Result<TokenStream2> {
-    let tvm_ffi = match &args.tvm_ffi_crate {
-        Some(path) => quote!(#path),
-        None => get_tvm_ffi_crate(),
-    };
-    let DefObjectRefArgs {
-        attrs,
-        vis,
-        type_name,
-        parent_type,
-        object_name,
-        ..
-    } = args;
-    let ref_input: DeriveInput = syn::parse2(quote! {
-        #vis struct #type_name {
-            base: #tvm_ffi::object::ObjectArc<#object_name>,
-        }
-    })?;
-    let ref_impl = expand_object_ref(&ref_input, &tvm_ffi);
-    // `#[derive(ObjectRef)]` already upcasts every reference to `ObjectRef`.
-    let parent_is_root = match parent_type {
-        syn::Type::Path(path) => path
-            .path
-            .segments
-            .last()
-            .is_some_and(|s| s.ident == "ObjectRef"),
-        _ => false,
-    };
-    let parent_upcast = if parent_is_root {
-        None
-    } else {
-        Some(quote!(#tvm_ffi::unsafe_impl_object_upcast!(#type_name => 
#parent_type);))
-    };
-    let doc = if attrs.iter().any(|a| a.path.is_ident("doc")) {
-        None
-    } else {
-        let doc = format!(
-            "The reference to [`{}`].",
-            quote!(#object_name).to_string().replace(' ', "")
-        );
-        Some(quote!(#[doc = #doc]))
-    };
-
-    Ok(quote! {
-        #doc
-        #(#attrs)*
-        #[repr(C)]
-        #[derive(::core::clone::Clone)]
-        #vis struct #type_name {
-            base: #tvm_ffi::object::ObjectArc<#object_name>,
-        }
-
-        #ref_impl
-
-        impl ::core::ops::Deref for #type_name {
-            type Target = #object_name;
-            #[inline]
-            fn deref(&self) -> &#object_name {
-                &self.base
-            }
-        }
-
-        impl ::core::convert::From<#object_name> for #type_name {
-            #[inline]
-            fn from(object: #object_name) -> Self {
-                Self {
-                    base: #tvm_ffi::object::ObjectArc::new(object),
-                }
-            }
-        }
-
-        #parent_upcast
-    })
-}
diff --git a/rust/tvm-ffi-macros/src/lib.rs b/rust/tvm-ffi-macros/src/lib.rs
index d589c777..ee52a0a1 100644
--- a/rust/tvm-ffi-macros/src/lib.rs
+++ b/rust/tvm-ffi-macros/src/lib.rs
@@ -20,7 +20,6 @@
 use proc_macro::TokenStream;
 use proc_macro_error::proc_macro_error;
 
-mod def_object_ref;
 mod dispatch;
 mod match_any;
 mod object_macros;
@@ -105,8 +104,10 @@ pub fn derive_object(input: TokenStream) -> TokenStream {
     object_macros::derive_object(input)
 }
 
+// Documented on the `tvm_ffi::derive::ObjectRef` re-export, whose examples
+// run as tvm-ffi doctests.
 #[proc_macro_error]
-#[proc_macro_derive(ObjectRef, attributes(type_key, type_index, custom_debug))]
+#[proc_macro_derive(ObjectRef, attributes(object_ref))]
 pub fn derive_object_ref(input: TokenStream) -> TokenStream {
     object_macros::derive_object_ref(input)
 }
@@ -118,11 +119,3 @@ pub fn derive_object_ref(input: TokenStream) -> 
TokenStream {
 pub fn rs_object(args: TokenStream, item: TokenStream) -> TokenStream {
     rs_object::rs_object(args, item)
 }
-
-// Documented on the `tvm_ffi::def_object_ref` re-export, whose examples run as
-// tvm-ffi doctests.
-#[proc_macro_error]
-#[proc_macro]
-pub fn def_object_ref(input: TokenStream) -> TokenStream {
-    def_object_ref::def_object_ref(input)
-}
diff --git a/rust/tvm-ffi-macros/src/object_macros.rs 
b/rust/tvm-ffi-macros/src/object_macros.rs
index 607b0cb0..b6d3aba1 100644
--- a/rust/tvm-ffi-macros/src/object_macros.rs
+++ b/rust/tvm-ffi-macros/src/object_macros.rs
@@ -18,6 +18,7 @@
  */
 use proc_macro::TokenStream;
 use proc_macro2::TokenStream as TokenStream2;
+use proc_macro_error::abort;
 use quote::{format_ident, quote};
 use syn::ext::IdentExt;
 use syn::DeriveInput;
@@ -515,13 +516,18 @@ pub fn derive_object_ref(input: proc_macro::TokenStream) 
-> TokenStream {
     TokenStream::from(expand_object_ref(&derive_input, &get_tvm_ffi_crate()))
 }
 
-/// The `ObjectRefCore` and `AnyCompatible` impls of a reference struct,
+/// The impls of a reference struct, as configured by `#[object_ref(...)]`,
 /// naming the tvm-ffi crate `tvm_ffi_crate`.
 pub(crate) fn expand_object_ref(
     derive_input: &DeriveInput,
     tvm_ffi_crate: &TokenStream2,
 ) -> TokenStream2 {
     let struct_name = derive_input.ident.clone();
+    let options = object_ref_options(derive_input);
+    let tvm_ffi_crate = &match &options.tvm_ffi_crate {
+        Some(path) => quote!(#path),
+        None => tvm_ffi_crate.clone(),
+    };
 
     // The `ObjectArc<T>` slot is the first field; its name is the struct's 
choice
     // (`data` in this crate, `base` in stubgen output, so that reflected 
fields
@@ -535,10 +541,13 @@ pub(crate) fn expand_object_ref(
         _ => panic!("derive only works for structs"),
     }
     .expect("First field must be `<name>: ObjectArc<T>`");
+    // Spell the object type as written, so rustdoc shows `Target = FooObj`.
+    let object_ty = object_arc_argument(&data_ty)
+        .unwrap_or_else(|| quote!(<#data_ty as ::core::ops::Deref>::Target));
 
     let mut expanded = quote! {
         unsafe impl #tvm_ffi_crate::object::ObjectRefCore for #struct_name {
-            type ContainerType = <#data_ty as std::ops::Deref>::Target;
+            type ContainerType = #object_ty;
             #[inline]
             fn data(this: &Self) -> 
&#tvm_ffi_crate::object::ObjectArc<Self::ContainerType> {
                 &this.#data_id
@@ -555,6 +564,15 @@ pub(crate) fn expand_object_ref(
             }
         }
 
+        impl #struct_name {
+            /// The reference to a new object, for the type's constructors.
+            #[allow(dead_code)]
+            #[inline(always)]
+            fn from_object(object: #object_ty) -> Self {
+                Self { #data_id: 
#tvm_ffi_crate::object::ObjectArc::new(object) }
+            }
+        }
+
         // The handle is exactly its object pointer, which an FFI cell's
         // payload word holds, so an `Array` cell can lend `&Self` in place.
         const _: () = {
@@ -567,12 +585,8 @@ pub(crate) fn expand_object_ref(
             );
         };
 
-        unsafe impl #tvm_ffi_crate::type_traits::RefFromAnyUnchecked for 
#struct_name {
-            #[inline(always)]
-            unsafe fn ref_from_any_unchecked(cell: 
&#tvm_ffi_crate::tvm_ffi_sys::TVMFFIAny) -> &Self {
-                &*(::core::ptr::addr_of!(cell.data_union) as *const Self)
-            }
-        }
+        // SAFETY: one `ObjectArc` (asserted above), checked by 
`check_any_strict`.
+        #tvm_ffi_crate::unsafe_impl_object_ref_downcast_ref!(#struct_name);
 
         // As `str`, `Path` and `[T]` do, so `impl AsRef<Base>` bounds accept 
the
         // base itself; upcasts add `AsRef` to each base.
@@ -583,24 +597,6 @@ pub(crate) fn expand_object_ref(
             }
         }
 
-        unsafe impl #tvm_ffi_crate::type_traits::DowncastRefFromAny for 
#struct_name {
-            #[inline(always)]
-            unsafe fn downcast_ref_from_any(
-                cell: &#tvm_ffi_crate::tvm_ffi_sys::TVMFFIAny,
-            ) -> ::core::option::Option<&Self> {
-                
#tvm_ffi_crate::type_traits::downcast_ref_from_any_in_place::<Self>(cell)
-            }
-        }
-
-        unsafe impl #tvm_ffi_crate::type_traits::DowncastRefFromObject for 
#struct_name {
-            #[inline(always)]
-            unsafe fn downcast_ref_from_object(
-                place: &*mut #tvm_ffi_crate::tvm_ffi_sys::TVMFFIObject,
-            ) -> ::core::option::Option<&Self> {
-                
#tvm_ffi_crate::type_traits::downcast_ref_from_object_in_place::<Self>(place)
-            }
-        }
-
         impl #tvm_ffi_crate::object::IntoObjectRef<#struct_name> for 
#struct_name {
             type Output = Self;
             #[inline(always)]
@@ -623,157 +619,146 @@ pub(crate) fn expand_object_ref(
                 value.clone()
             }
         }
-
-        // implement AnyCompatible for #struct_name
-        unsafe impl #tvm_ffi_crate::type_traits::AnyCompatible for 
#struct_name {
-            const MATCH_ANY_EXACT: bool = {
-                type ContainerType =
-                    <#struct_name as 
#tvm_ffi_crate::object::ObjectRefCore>::ContainerType;
-                <ContainerType as 
#tvm_ffi_crate::object::ObjectCore>::TYPE_FINAL
-            };
-
-            const MATCH_ANY_STRICT_EXACT: bool = Self::MATCH_ANY_EXACT;
-
-            #[inline]
-            fn match_any_exact_type_index() -> i32 {
-                type ContainerType = <#struct_name as 
#tvm_ffi_crate::object::ObjectRefCore>
-                    ::ContainerType;
-                <ContainerType as 
#tvm_ffi_crate::object::ObjectCore>::type_index()
-            }
-
-            #[inline]
-            unsafe fn is_undefined(field: *const Self) -> bool {
-                
#tvm_ffi_crate::object::ObjectArc::is_null(::core::ptr::addr_of!((*field).#data_id))
-            }
-
-            fn type_str() -> std::string::String {
-                type ContainerType = <#struct_name as 
#tvm_ffi_crate::object::ObjectRefCore>
-                    ::ContainerType;
-                <ContainerType as 
#tvm_ffi_crate::object::ObjectCore>::TYPE_KEY.into()
-            }
-
-            #[inline(always)]
-            unsafe fn copy_to_any_view(
-                src: &Self,
-                data: &mut  #tvm_ffi_crate::tvm_ffi_sys::TVMFFIAny
-            ) {
-                type ContainerType = <#struct_name as 
#tvm_ffi_crate::object::ObjectRefCore>
-                    ::ContainerType;
-                let data_ptr = 
#tvm_ffi_crate::object::ObjectArc::<ContainerType>::as_ptr(
-                    &src.#data_id
-                );
-                let object_ptr =
-                    data_ptr as *mut ContainerType as *mut 
#tvm_ffi_crate::tvm_ffi_sys::TVMFFIObject;
-                data.type_index = (*object_ptr).type_index;
-                // SAFETY: ObjectRefCore guarantees a live allocation whose 
header
-                // contains its registered object-range dynamic type index.
-                ::core::hint::assert_unchecked(
-                    data.type_index >=
-                        
#tvm_ffi_crate::tvm_ffi_sys::TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32
-                );
-                data.small_str_len = 0;
-                data.data_union.v_obj = object_ptr;
-            }
-
-            #[inline(always)]
-            unsafe fn check_any_strict(
-                data: & #tvm_ffi_crate::tvm_ffi_sys::TVMFFIAny
-            ) -> bool {
-                type ContainerType = <#struct_name as 
#tvm_ffi_crate::object::ObjectRefCore>
-                    ::ContainerType;
-                
#tvm_ffi_crate::object::is_instance_of::<ContainerType>(data.type_index)
-            }
-
-            unsafe fn copy_from_any_view_after_check(
-                data: & #tvm_ffi_crate::tvm_ffi_sys::TVMFFIAny
-            ) -> Self {
-                type ContainerType = <#struct_name as 
#tvm_ffi_crate::object::ObjectRefCore>
-                    ::ContainerType;
-                let data_ptr = data.data_union.v_obj;
-                // need to increase ref because original weak ptr
-                // do not own the code
-                #tvm_ffi_crate::object::unsafe_::inc_ref(
-                    data_ptr as *mut  #tvm_ffi_crate::tvm_ffi_sys::TVMFFIObject
-                );
-                Self {
-                    #data_id: #tvm_ffi_crate::object::ObjectArc::from_raw(
-                        data_ptr as *mut ContainerType
-                    )
-                }
-            }
-
-            #[inline(always)]
-            unsafe fn move_to_any(
-                src: Self,
-                data: &mut  #tvm_ffi_crate::tvm_ffi_sys::TVMFFIAny
-            ) {
-                type ContainerType = <#struct_name as 
#tvm_ffi_crate::object::ObjectRefCore>
-                    ::ContainerType;
-                let data_ptr = #tvm_ffi_crate::object::ObjectArc::into_raw(
-                    src.#data_id
-                );
-                let object_ptr =
-                    data_ptr as *mut ContainerType as *mut 
#tvm_ffi_crate::tvm_ffi_sys::TVMFFIObject;
-                data.type_index = (*object_ptr).type_index;
-                // SAFETY: ObjectRefCore guarantees a live allocation whose 
header
-                // contains its registered object-range dynamic type index.
-                ::core::hint::assert_unchecked(
-                    data.type_index >=
-                        
#tvm_ffi_crate::tvm_ffi_sys::TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32
-                );
-                data.small_str_len = 0;
-                data.data_union.v_obj = object_ptr;
-            }
-
-            #[inline(always)]
-            unsafe fn move_from_any_after_check(
-                data: &mut  #tvm_ffi_crate::tvm_ffi_sys::TVMFFIAny
-            ) -> Self {
-                type ContainerType = <#struct_name as 
#tvm_ffi_crate::object::ObjectRefCore>
-                    ::ContainerType;
-                let data_ptr = data.data_union.v_obj as *mut ContainerType;
-                Self {
-                    #data_id: 
#tvm_ffi_crate::object::ObjectArc::<ContainerType>::from_raw(data_ptr)
-                }
+    };
+    // The default `AnyCompatible`: the object type check alone, which
+    // `match_any!` may route by.
+    if !options.skip_any_compatible {
+        expanded.extend(quote! {
+            unsafe impl #tvm_ffi_crate::object::ObjectRefAnyCompatible for 
#struct_name {
+                const MATCH_ANY_EXACT: bool = true;
             }
-
-            unsafe fn try_cast_from_any_view(
-                data: & #tvm_ffi_crate::tvm_ffi_sys::TVMFFIAny
-            ) -> Result<Self, ()> {
-                type ContainerType = <#struct_name as 
#tvm_ffi_crate::object::ObjectRefCore>
-                    ::ContainerType;
-                if 
#tvm_ffi_crate::object::is_instance_of::<ContainerType>(data.type_index) {
-                    Ok(Self::copy_from_any_view_after_check(data))
-                } else {
-                    Err(())
+        });
+    }
+    // Field reads through the reference, as C++ `operator->`.
+    if !options.skip_deref {
+        expanded.extend(quote! {
+            impl ::core::ops::Deref for #struct_name {
+                type Target = #object_ty;
+                #[inline(always)]
+                fn deref(&self) -> &Self::Target {
+                    &self.#data_id
                 }
             }
-        }
-    };
+        });
+    }
     // The runtime type key and object address, so `Result<_, Self>` from
-    // `downcast` can be unwrapped. `#[custom_debug]` keeps a hand-written one.
-    let custom_debug = derive_input
-        .attrs
-        .iter()
-        .any(|attr| attr.path.is_ident("custom_debug"));
-    if !custom_debug {
+    // `downcast` can be unwrapped.
+    if !options.skip_debug {
         expanded.extend(quote! {
-        impl ::core::fmt::Debug for #struct_name {
-            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> 
::core::fmt::Result {
+            impl ::core::fmt::Debug for #struct_name {
+                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> 
::core::fmt::Result {
                     #tvm_ffi_crate::object::fmt_object_ref(self, f)
                 }
             }
         });
     }
-    // skip ObjectRef since it can create circular dependency with any.rs
+    // `ObjectRef` itself skips these: `any.rs` implements them for it.
     if struct_name != "ObjectRef" {
         expanded.extend(quote! {
-            #tvm_ffi_crate::impl_try_from_any!(#struct_name);
-            #tvm_ffi_crate::impl_arg_into_ref!(#struct_name);
-            #tvm_ffi_crate::impl_into_arg_holder_default!(#struct_name);
+            #tvm_ffi_crate::impl_any_compatible_conversions!(#struct_name);
             // Every object reference upcasts to the root `ObjectRef`.
-            #tvm_ffi_crate::unsafe_impl_object_upcast!(#struct_name => 
#tvm_ffi_crate::object::ObjectRef);
+            #tvm_ffi_crate::unsafe_impl_object_ref_upcast!(#struct_name => 
#tvm_ffi_crate::object::ObjectRef);
+        });
+    }
+    // The root upcast is already above.
+    if let Some(parent) = options.parent.filter(|parent| 
!is_object_ref_root(parent)) {
+        expanded.extend(quote! {
+            #tvm_ffi_crate::unsafe_impl_object_ref_upcast!(#struct_name => 
#parent);
         });
     }
     expanded
 }
+
+/// `T` of a field type spelled `ObjectArc<T>`.
+fn object_arc_argument(ty: &syn::Type) -> Option<TokenStream2> {
+    let syn::Type::Path(path) = ty else {
+        return None;
+    };
+    let last = path.path.segments.last()?;
+    if last.ident != "ObjectArc" {
+        return None;
+    }
+    match &last.arguments {
+        syn::PathArguments::AngleBracketed(args) => match args.args.first()? {
+            syn::GenericArgument::Type(object) if args.args.len() == 1 => 
Some(quote!(#object)),
+            _ => None,
+        },
+        _ => None,
+    }
+}
+
+/// Whether `ty` names the root `ObjectRef`.
+fn is_object_ref_root(ty: &syn::Type) -> bool {
+    match ty {
+        syn::Type::Path(path) => path
+            .path
+            .segments
+            .last()
+            .is_some_and(|s| s.ident == "ObjectRef"),
+        _ => false,
+    }
+}
+
+/// The options of `#[object_ref(...)]` on an `ObjectRef` derive.
+#[derive(Default)]
+struct ObjectRefOptions {
+    tvm_ffi_crate: Option<syn::Path>,
+    parent: Option<syn::Type>,
+    skip_deref: bool,
+    skip_debug: bool,
+    skip_any_compatible: bool,
+}
+
+/// One `#[object_ref(...)]` item: a flag, `parent = Type` or `crate = path`.
+enum ObjectRefOption {
+    Flag(syn::Ident),
+    Parent(Box<syn::Type>),
+    Crate(syn::Path),
+}
+
+impl syn::parse::Parse for ObjectRefOption {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let name: syn::Ident = input.call(syn::Ident::parse_any)?;
+        if name == "parent" {
+            input.parse::<syn::Token![=]>()?;
+            return Ok(ObjectRefOption::Parent(Box::new(input.parse()?)));
+        }
+        if name == "crate" {
+            input.parse::<syn::Token![=]>()?;
+            return 
Ok(ObjectRefOption::Crate(syn::Path::parse_mod_style(input)?));
+        }
+        Ok(ObjectRefOption::Flag(name))
+    }
+}
+
+fn object_ref_options(derive_input: &DeriveInput) -> ObjectRefOptions {
+    let mut options = ObjectRefOptions::default();
+    for attr in derive_input
+        .attrs
+        .iter()
+        .filter(|a| a.path.is_ident("object_ref"))
+    {
+        let items = attr
+            .parse_args_with(
+                syn::punctuated::Punctuated::<ObjectRefOption, 
syn::Token![,]>::parse_terminated,
+            )
+            .unwrap_or_else(|error| abort!(error.span(), "{}", error));
+        for item in items {
+            match item {
+                ObjectRefOption::Parent(parent) => options.parent = 
Some(*parent),
+                ObjectRefOption::Crate(path) => options.tvm_ffi_crate = 
Some(path),
+                ObjectRefOption::Flag(flag) if flag == "skip_deref" => 
options.skip_deref = true,
+                ObjectRefOption::Flag(flag) if flag == "skip_debug" => 
options.skip_debug = true,
+                ObjectRefOption::Flag(flag) if flag == "skip_any_compatible" 
=> {
+                    options.skip_any_compatible = true
+                }
+                ObjectRefOption::Flag(flag) => abort!(
+                    flag,
+                    "unknown `object_ref` option; expected `parent = ...`, 
`skip_deref`, \
+                     `skip_debug`, `skip_any_compatible` or `crate = ...`"
+                ),
+            }
+        }
+    }
+    options
+}
diff --git a/rust/tvm-ffi/src/any.rs b/rust/tvm-ffi/src/any.rs
index 12f34939..31a2e061 100644
--- a/rust/tvm-ffi/src/any.rs
+++ b/rust/tvm-ffi/src/any.rs
@@ -503,7 +503,8 @@ impl<T: AnyCompatible> TryFrom<Any> for TryFromTemp<T> {
     }
 }
 
-crate::impl_try_from_any!(
+crate::impl_any_compatible_conversions!(
+    (),
     bool,
     i8,
     i16,
@@ -517,15 +518,14 @@ crate::impl_try_from_any!(
     usize,
     f32,
     f64,
-    (),
-    *mut core::ffi::c_void,
     crate::string::String,
     crate::string::Bytes,
     crate::big_int::BigInt,
-    crate::object::ObjectRef,
     tvm_ffi_sys::dlpack::DLDataType,
     tvm_ffi_sys::dlpack::DLDevice,
 );
+// Only out of `Any`: a raw pointer and the root reference are not call 
arguments.
+crate::impl_try_from_any!(*mut core::ffi::c_void, crate::object::ObjectRef);
 
 crate::impl_try_from_any_for_parametric!(Option<T>);
 
diff --git a/rust/tvm-ffi/src/collections/shape.rs 
b/rust/tvm-ffi/src/collections/shape.rs
index 55b9e715..18bea9b2 100644
--- a/rust/tvm-ffi/src/collections/shape.rs
+++ b/rust/tvm-ffi/src/collections/shape.rs
@@ -50,6 +50,7 @@ pub struct ShapeObj {
 /// ```
 #[repr(C)]
 #[derive(ObjectRef, Clone)]
+#[object_ref(skip_deref)]
 pub struct Shape {
     data: ObjectArc<ShapeObj>,
 }
diff --git a/rust/tvm-ffi/src/collections/tensor.rs 
b/rust/tvm-ffi/src/collections/tensor.rs
index a2cfad7e..fe486b64 100644
--- a/rust/tvm-ffi/src/collections/tensor.rs
+++ b/rust/tvm-ffi/src/collections/tensor.rs
@@ -118,6 +118,7 @@ pub struct TensorObj {
 /// ```
 #[repr(C)]
 #[derive(ObjectRef, Clone)]
+#[object_ref(skip_deref)]
 pub struct Tensor {
     data: ObjectArc<TensorObj>,
 }
diff --git a/rust/tvm-ffi/src/derive.rs b/rust/tvm-ffi/src/derive.rs
index 629b9495..4d882b82 100644
--- a/rust/tvm-ffi/src/derive.rs
+++ b/rust/tvm-ffi/src/derive.rs
@@ -16,5 +16,73 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/// namespace to re-export derive macros
-pub use tvm_ffi_macros::{Object, ObjectRef};
+//! The derive macros of object types and object references.
+
+pub use tvm_ffi_macros::Object;
+
+/// Declare an object reference: the managed handle to an object type, as a
+/// C++ `ObjectRef` subclass is.
+///
+/// The struct holds one field, `ObjectArc<FooObj>`. The derive implements
+/// [`ObjectRefCore`](crate::ObjectRefCore), the default
+/// [`ObjectRefAnyCompatible`](crate::ObjectRefAnyCompatible) (conversions to
+/// and from [`Any`](crate::Any), in-place downcasts, typed function
+/// arguments), the upcast to [`ObjectRef`](crate::object::ObjectRef),
+/// `Deref<Target = FooObj>` for field reads, `Debug`, and a private
+/// `fn from_object(object: FooObj) -> Self` for the type's constructors.
+///
+/// # Options
+///
+/// `#[object_ref(...)]` takes:
+///
+/// * `parent = Ref` - The reference to the parent's object; `Self` converts
+///   into it by 
[`unsafe_impl_object_ref_upcast!`](crate::unsafe_impl_object_ref_upcast),
+///   whose requirement it carries. A further ancestor takes its own
+///   `unsafe_impl_object_ref_upcast!`.
+/// * `skip_deref` - No `Deref`, for a reference whose object is internal.
+/// * `skip_debug` - `Debug` is written by hand.
+/// * `skip_any_compatible` - `ObjectRefAnyCompatible` is written by hand,
+///   for a typed reference with a stricter check or fallback conversions.
+/// * `crate = path` - Where the generated code finds tvm-ffi, as for
+///   [`rs_object`](macro@crate::rs_object).
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::derive::ObjectRef;
+/// use tvm_ffi::{rs_object, Object, ObjectArc};
+///
+/// #[rs_object("my_ext.Point")]
+/// pub struct PointObj {
+///     base: Object,
+///     pub x: i64,
+/// }
+///
+/// #[derive(Clone, ObjectRef)]
+/// pub struct Point {
+///     data: ObjectArc<PointObj>,
+/// }
+///
+/// #[rs_object("my_ext.Point3", final)]
+/// pub struct Point3Obj {
+///     base: PointObj,
+///     pub z: i64,
+/// }
+///
+/// #[derive(Clone, ObjectRef)]
+/// #[object_ref(parent = Point)]
+/// pub struct Point3 {
+///     data: ObjectArc<Point3Obj>,
+/// }
+///
+/// impl Point3 {
+///     pub fn new(x: i64, z: i64) -> Self {
+///         Self::from_object(Point3Obj::new(PointObj::new(Object::new(), x), 
z))
+///     }
+/// }
+///
+/// let p = Point3::new(1, 2);
+/// assert_eq!(p.x + p.z, 3);
+/// let _parent: Point = p.into();
+/// ```
+pub use tvm_ffi_macros::ObjectRef;
diff --git a/rust/tvm-ffi/src/error.rs b/rust/tvm-ffi/src/error.rs
index 1fc4e7bd..f4777f06 100644
--- a/rust/tvm-ffi/src/error.rs
+++ b/rust/tvm-ffi/src/error.rs
@@ -111,7 +111,7 @@ pub struct ErrorObj {
 /// An `Error` is `Send + Sync`, so a `Result` can cross threads.
 #[repr(C)]
 #[derive(Clone, ObjectRef)]
-#[custom_debug]
+#[object_ref(skip_deref, skip_debug)]
 pub struct Error {
     data: ObjectArc<ErrorObj>,
 }
diff --git a/rust/tvm-ffi/src/extra/module.rs b/rust/tvm-ffi/src/extra/module.rs
index 59ef322c..a0820f94 100644
--- a/rust/tvm-ffi/src/extra/module.rs
+++ b/rust/tvm-ffi/src/extra/module.rs
@@ -40,6 +40,7 @@ pub struct ModuleObj {
 /// ABI-stable owned Module for FFI operations.
 #[repr(C)]
 #[derive(ObjectRef, Clone)]
+#[object_ref(skip_deref)]
 pub struct Module {
     data: ObjectArc<ModuleObj>,
 }
diff --git a/rust/tvm-ffi/src/extra/structural_mutate.rs 
b/rust/tvm-ffi/src/extra/structural_mutate.rs
index 7a5c433a..eb348284 100644
--- a/rust/tvm-ffi/src/extra/structural_mutate.rs
+++ b/rust/tvm-ffi/src/extra/structural_mutate.rs
@@ -557,7 +557,8 @@ impl<T: object::ObjectRefCore> MutationView<'_, T> {
     /// # use tvm_ffi::*;
     /// # #[rs_object("my_ext.MyPair", final)]
     /// # pub struct MyPairObj { base: Object, pub first: Array<i64>, pub 
second: Array<i64> }
-    /// # def_object_ref!(pub MyPair, ObjectRef, MyPairObj);
+    /// # #[derive(Clone, tvm_ffi::derive::ObjectRef)]
+    /// # pub struct MyPair { data: ObjectArc<MyPairObj> }
     /// # struct Pass;
     /// # #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     /// # impl Pass {
@@ -567,7 +568,7 @@ impl<T: object::ObjectRefCore> MutationView<'_, T> {
     /// #     }
     /// # }
     /// # let pair = MyPairObj::new(Object::new(), 
Array::from_vec(vec![1_i64]), Array::from_vec(vec![2_i64]));
-    /// # structural_mutate(MyPair::from(pair), Pass)?;
+    /// # structural_mutate(MyPair::from_object(pair), Pass)?;
     /// # Ok::<(), tvm_ffi::Error>(())
     /// ```
     ///
diff --git a/rust/tvm-ffi/src/extra/structural_visit.rs 
b/rust/tvm-ffi/src/extra/structural_visit.rs
index 38e96019..1a72734b 100644
--- a/rust/tvm-ffi/src/extra/structural_visit.rs
+++ b/rust/tvm-ffi/src/extra/structural_visit.rs
@@ -62,7 +62,7 @@ use super::cold_init_lazy_lock::ColdInitLazyLock;
 pub use super::structural_walk::*;
 
 use crate::any::{Any, AnyView};
-use crate::derive::Object;
+use crate::derive::{Object, ObjectRef};
 use crate::error::{Error, Result, TYPE_ERROR};
 use crate::function::Function;
 use crate::object::{Object, ObjectArc};
@@ -238,19 +238,21 @@ const _: () = {
     assert!(DefRegionKind::Simple as i32 == 
TVMFFIDefRegionKind::kTVMFFIDefRegionKindSimple as i32);
 };
 
-crate::def_object_ref! {
-    /// Interrupt state of a traversal, mirroring C++ `ffi.VisitInterrupt`.
-    ///
-    /// Entry points and visitor-layer calls return
-    /// `Result<Option<VisitInterrupt>>`: `Ok(None)` means the (sub)graph was
-    /// visited completely, `Ok(Some(..))` means a handler halted the traversal
-    /// with this interrupt, and `Err` means it failed.
-    ///
-    /// This is an owning object reference, like [`Error`]. Payload reads 
through
-    /// `interrupt.value` borrow the shared [`VisitInterruptObj`]; use 
[`Self::new`]
-    /// instead of a payload-only struct literal. Moving or replacing the 
payload
-    /// through this reference is not supported; clone it to retain a separate 
owner.
-    pub VisitInterrupt, crate::object::ObjectRef, VisitInterruptObj
+/// Interrupt state of a traversal, mirroring C++ `ffi.VisitInterrupt`.
+///
+/// Entry points and visitor-layer calls return
+/// `Result<Option<VisitInterrupt>>`: `Ok(None)` means the (sub)graph was
+/// visited completely, `Ok(Some(..))` means a handler halted the traversal
+/// with this interrupt, and `Err` means it failed.
+///
+/// This is an owning object reference, like [`Error`]. Payload reads through
+/// `interrupt.value` borrow the shared [`VisitInterruptObj`]; use 
[`Self::new`]
+/// instead of a payload-only struct literal. Moving or replacing the payload
+/// through this reference is not supported; clone it to retain a separate 
owner.
+#[repr(C)]
+#[derive(Clone, ObjectRef)]
+pub struct VisitInterrupt {
+    data: ObjectArc<VisitInterruptObj>,
 }
 
 impl VisitInterrupt {
@@ -266,11 +268,10 @@ impl VisitInterrupt {
     /// The interrupt.
     #[inline(always)]
     pub fn new<T: Into<Any>>(payload: T) -> Self {
-        VisitInterruptObj {
+        Self::from_object(VisitInterruptObj {
             base: Object::new(),
             value: payload.into(),
-        }
-        .into()
+        })
     }
 }
 
diff --git a/rust/tvm-ffi/src/function.rs b/rust/tvm-ffi/src/function.rs
index 520fe300..c6a1d26f 100644
--- a/rust/tvm-ffi/src/function.rs
+++ b/rust/tvm-ffi/src/function.rs
@@ -68,6 +68,7 @@ pub struct FunctionObj {
 /// tvm_ffi::Function::from_typed(move || -> tvm_ffi::Result<i64> { Ok(*count) 
});
 /// ```
 #[derive(Clone, ObjectRef)]
+#[object_ref(skip_deref)]
 pub struct Function {
     data: ObjectArc<FunctionObj>,
 }
diff --git a/rust/tvm-ffi/src/function_internal.rs 
b/rust/tvm-ffi/src/function_internal.rs
index 4fce5b18..9fdbc192 100644
--- a/rust/tvm-ffi/src/function_internal.rs
+++ b/rust/tvm-ffi/src/function_internal.rs
@@ -164,28 +164,8 @@ pub trait IntoArgHolder {
     fn into_arg_holder(self) -> Self::Target;
 }
 
-crate::impl_into_arg_holder_default!(
-    (),
-    bool,
-    i8,
-    i16,
-    i32,
-    i64,
-    isize,
-    u8,
-    u16,
-    u32,
-    u64,
-    usize,
-    f32,
-    f64,
-    String,
-    Bytes,
-    crate::big_int::BigInt,
-    Any,
-    crate::DLDataType,
-    crate::DLDevice
-);
+// `Any` itself is an argument; every other value type is listed in `any.rs`.
+crate::impl_into_arg_holder_default!(Any);
 
 // string will be converted to String for argument passing
 impl IntoArgHolder for &str {
@@ -277,28 +257,8 @@ where
     }
 }
 
-crate::impl_arg_into_ref!(
-    (),
-    bool,
-    i8,
-    i16,
-    i32,
-    i64,
-    isize,
-    u8,
-    u16,
-    u32,
-    u64,
-    usize,
-    f32,
-    f64,
-    String,
-    Bytes,
-    crate::big_int::BigInt,
-    Any,
-    crate::DLDataType,
-    crate::DLDevice
-);
+// `Any` itself is an argument; every other value type is listed in `any.rs`.
+crate::impl_arg_into_ref!(Any);
 
 // Generic holders require explicit implementations rather than scalar macro 
entries.
 impl<T: AnyCompatible> IntoArgHolder for Option<T> {
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index 7431787c..c3672c82 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -74,7 +74,7 @@ pub use crate::function::Function;
 pub use crate::function_internal::AsPackedCallable;
 pub use crate::object::{
     ImmutableObject, IntoObjectRef, Object, ObjectArc, ObjectCore, 
ObjectCoreWithExtraItems,
-    ObjectIdentity, ObjectRefCore,
+    ObjectIdentity, ObjectRefAnyCompatible, ObjectRefCore,
 };
 pub use crate::optional::{Optional, OptionalCompatible};
 pub use crate::reflection::{
@@ -205,8 +205,8 @@ pub use tvm_ffi_macros::match_any;
 /// type with the reflection of every field, so that C++ structural equality
 /// and hashing, `repr`, deep copy and Python see the fields. The first field,
 /// `base`, is the parent's object, as the base class of a C++ object is:
-/// `base: Object` for a type under `ffi.Object`. Define the managed reference
-/// with [`def_object_ref!`].
+/// `base: Object` for a type under `ffi.Object`. Declare the managed reference
+/// with [`#[derive(ObjectRef)]`](derive::ObjectRef).
 ///
 /// The macro adds to the struct:
 ///
@@ -246,8 +246,8 @@ pub use tvm_ffi_macros::match_any;
 /// # Examples
 ///
 /// ```
-/// use tvm_ffi::object::ObjectRef;
-/// use tvm_ffi::{def_object_ref, rs_object, Object};
+/// use tvm_ffi::derive::ObjectRef;
+/// use tvm_ffi::{rs_object, Object, ObjectArc};
 ///
 /// #[rs_object("my_ext.MyPair", final)]
 /// pub struct MyPairObj {
@@ -256,56 +256,18 @@ pub use tvm_ffi_macros::match_any;
 ///     #[reflect(seq_hash_ignore)]
 ///     pub second: tvm_ffi::String,
 /// }
-/// def_object_ref!(pub MyPair, ObjectRef, MyPairObj);
 ///
-/// let pair = MyPair::from(MyPairObj::new(Object::new(), 1, "one"));
+/// #[derive(Clone, ObjectRef)]
+/// pub struct MyPair {
+///     data: ObjectArc<MyPairObj>,
+/// }
+///
+/// let pair = MyPair::from_object(MyPairObj::new(Object::new(), 1, "one"));
 /// assert_eq!(pair.first, 1);
 /// assert_eq!(pair.second.as_str(), "one");
 /// ```
 pub use tvm_ffi_macros::rs_object;
 
-/// Define the managed reference to an object type, as C++
-/// `TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE` does.
-///
-/// `def_object_ref!(TypeName, ParentType, ObjectName)` defines `TypeName`, a
-/// `derive(ObjectRef)` type that holds an `ObjectName`. It dereferences to
-/// `ObjectName`, and an `ObjectName` converts into it.
-///
-/// # Arguments
-///
-/// * `TypeName` - The reference type to define. Attributes and visibility
-///   before it go on the struct.
-/// * `ParentType` - The reference to the parent's object, `ObjectRef` for a
-///   type under `ffi.Object`. `TypeName` converts into it by
-///   `unsafe_impl_object_upcast!`, and `&TypeName` borrows as `&ParentType` 
through
-///   `AsRef`; a conversion into a further ancestor takes its own
-///   `unsafe_impl_object_upcast!`.
-/// * `ObjectName` - The object type.
-/// * `crate = path` - Optional, after `ObjectName`: where the generated code
-///   finds tvm-ffi, as for [`rs_object`](macro@rs_object).
-///
-/// # Examples
-///
-/// ```
-/// use tvm_ffi::object::ObjectRef;
-/// use tvm_ffi::{def_object_ref, rs_object, Object};
-///
-/// #[rs_object("my_ext.MyPair", final)]
-/// pub struct MyPairObj {
-///     base: Object,
-///     pub first: i64,
-///     pub second: i64,
-/// }
-///
-/// def_object_ref!(pub MyPair, ObjectRef, MyPairObj);
-///
-/// let pair = MyPair::from(MyPairObj::new(Object::new(), 1, 2));
-/// assert_eq!(pair.first + pair.second, 3);
-/// let _borrowed: &ObjectRef = pair.as_ref();
-/// let _parent: ObjectRef = pair.into();
-/// ```
-pub use tvm_ffi_macros::def_object_ref;
-
 pub use tvm_ffi_sys::TVMFFITypeIndex as TypeIndex;
 pub use tvm_ffi_sys::{
     DLDataType, DLDataTypeCode, DLDevice, DLDeviceType, TVMFFIAny, 
TVMFFIObject, TVMFFIStreamHandle,
diff --git a/rust/tvm-ffi/src/macros.rs b/rust/tvm-ffi/src/macros.rs
index 47a43e81..c87d65f3 100644
--- a/rust/tvm-ffi/src/macros.rs
+++ b/rust/tvm-ffi/src/macros.rs
@@ -90,7 +90,7 @@ macro_rules! cached_global_func {
 /// the generated safe conversions unsound. The Rust stub generator writes 
these
 /// from the C++ type hierarchy.
 #[macro_export]
-macro_rules! unsafe_impl_object_upcast {
+macro_rules! unsafe_impl_object_ref_upcast {
     ($($source:ty => $target:ty),+ $(,)?) => {
         $(
             impl ::std::convert::From<$source> for $target {
@@ -185,9 +185,10 @@ macro_rules! unsafe_impl_object_upcast {
     };
 }
 
-/// Implement the in-place reference downcasts (`array[i]`, 
`Any::downcast_ref`,
+/// Implement the in-place `downcast_ref` borrows (`array[i]`, 
`Any::downcast_ref`,
 /// [`ObjectRefCore::downcast_ref`](crate::ObjectRefCore::downcast_ref)) for 
each
-/// listed reference type implemented by hand, as `#[derive(ObjectRef)]` does.
+/// listed object reference type. 
[`#[derive(ObjectRef)]`](crate::derive::ObjectRef)
+/// emits it; a reference that implements `ObjectRefCore` by hand calls it.
 ///
 /// # Safety
 ///
@@ -198,9 +199,9 @@ macro_rules! unsafe_impl_object_upcast {
 /// # Examples
 ///
 /// ```
-/// use tvm_ffi::object::{ObjectArc, ObjectRef};
-/// use tvm_ffi::{def_object_ref, rs_object, unsafe_impl_ref_downcast, Object, 
ObjectRefCore};
-/// # use tvm_ffi::{tvm_ffi_sys::TVMFFIAny, AnyCompatible};
+/// use tvm_ffi::derive::ObjectRef;
+/// use tvm_ffi::{rs_object, unsafe_impl_object_ref_downcast_ref, Any};
+/// use tvm_ffi::{Object, ObjectArc, ObjectRefAnyCompatible, ObjectRefCore};
 ///
 /// #[rs_object("my_ext.MyPair", final)]
 /// pub struct MyPairObj {
@@ -208,47 +209,42 @@ macro_rules! unsafe_impl_object_upcast {
 ///     pub first: i64,
 ///     pub second: i64,
 /// }
-/// def_object_ref!(pub MyPair, ObjectRef, MyPairObj);
+///
+/// #[derive(Clone, ObjectRef)]
+/// pub struct MyPair {
+///     data: ObjectArc<MyPairObj>,
+/// }
 ///
 /// /// A `MyPair` whose `first` is positive.
-/// #[repr(transparent)]
 /// #[derive(Clone)]
+/// #[repr(transparent)]
 /// pub struct PositivePair(ObjectArc<MyPairObj>);
-/// # unsafe impl ObjectRefCore for PositivePair {
-/// #     type ContainerType = MyPairObj;
-/// #     fn data(this: &Self) -> &ObjectArc<MyPairObj> { &this.0 }
-/// #     fn into_data(this: Self) -> ObjectArc<MyPairObj> { this.0 }
-/// #     unsafe fn from_data(data: ObjectArc<MyPairObj>) -> Self { Self(data) 
}
-/// # }
-/// # unsafe impl AnyCompatible for PositivePair {
-/// #     fn type_str() -> String { "PositivePair".into() }
-/// #     unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
-/// #         MyPair::copy_to_any_view(std::mem::transmute::<&Self, 
&MyPair>(src), data)
-/// #     }
-/// #     unsafe fn move_to_any(src: Self, data: &mut TVMFFIAny) {
-/// #         MyPair::move_to_any(MyPair::from_data(src.0), data)
-/// #     }
-/// #     unsafe fn check_any_strict(data: &TVMFFIAny) -> bool {
-/// #         MyPair::check_any_strict(data) && 
(*data.data_union.v_obj.cast::<MyPairObj>()).first > 0
-/// #     }
-/// #     unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) -> Self {
-/// #         
Self(MyPair::into_data(MyPair::copy_from_any_view_after_check(data)))
-/// #     }
-/// #     unsafe fn move_from_any_after_check(data: &mut TVMFFIAny) -> Self {
-/// #         Self(MyPair::into_data(MyPair::move_from_any_after_check(data)))
-/// #     }
-/// #     unsafe fn try_cast_from_any_view(data: &TVMFFIAny) -> Result<Self, 
()> {
-/// #         if Self::check_any_strict(data) { 
Ok(Self::copy_from_any_view_after_check(data)) } else { Err(()) }
-/// #     }
-/// # }
-/// // (`ObjectRefCore` and `AnyCompatible` for `PositivePair` are written by 
hand.)
-/// unsafe_impl_ref_downcast!(PositivePair);
-///
-/// let pair: ObjectRef = MyPair::from(MyPairObj::new(Object::new(), 1, 
2)).into();
+///
+/// unsafe impl ObjectRefCore for PositivePair {
+///     type ContainerType = MyPairObj;
+///     fn data(this: &Self) -> &ObjectArc<MyPairObj> {
+///         &this.0
+///     }
+///     fn into_data(this: Self) -> ObjectArc<MyPairObj> {
+///         this.0
+///     }
+///     unsafe fn from_data(data: ObjectArc<MyPairObj>) -> Self {
+///         Self(data)
+///     }
+/// }
+///
+/// unsafe impl ObjectRefAnyCompatible for PositivePair {
+///     fn extra_check(pair: &MyPairObj) -> bool {
+///         pair.first > 0
+///     }
+/// }
+/// unsafe_impl_object_ref_downcast_ref!(PositivePair);
+///
+/// let pair = Any::from(MyPair::from_object(MyPairObj::new(Object::new(), 1, 
2)));
 /// assert!(pair.downcast_ref::<PositivePair>().is_some());
 /// ```
 #[macro_export]
-macro_rules! unsafe_impl_ref_downcast {
+macro_rules! unsafe_impl_object_ref_downcast_ref {
     ($($name:ty),+ $(,)?) => {
         $(
             // SAFETY: the macro declaration promises that `$name` is one 
object
@@ -452,15 +448,26 @@ macro_rules! attach_context {
 // Macros for any definitions
 // ----------------------------------------------------------------------------
 
-/// Implement `TryFrom<AnyView>`, `TryFrom<Any>` and `TryFrom<&Any>` for each 
listed
-/// [`AnyCompatible`](crate::AnyCompatible) type, so it converts out of FFI 
values
-/// by its `AnyCompatible` checks.
+/// Implement the conversions between each listed 
[`AnyCompatible`](crate::AnyCompatible)
+/// type and FFI values: `TryFrom<AnyView>`, `TryFrom<Any>` and 
`TryFrom<&Any>`,
+/// and use as an argument of 
[`Function::call_tuple`](crate::Function::call_tuple)
+/// and of functions from [`into_typed_fn!`](crate::into_typed_fn).
 ///
-/// `#[derive(ObjectRef)]` calls it. A reference type implemented by hand, such
-/// as a typed view over another reference, calls it with
-/// [`impl_arg_into_ref!`](crate::impl_arg_into_ref),
-/// [`impl_into_arg_holder_default!`](crate::impl_into_arg_holder_default) and
-/// [`unsafe_impl_ref_downcast!`](crate::unsafe_impl_ref_downcast).
+/// `#[derive(ObjectRef)]` calls it; a type that implements `AnyCompatible` by 
hand
+/// calls it after that impl. See also
+/// 
[`unsafe_impl_object_ref_downcast_ref!`](crate::unsafe_impl_object_ref_downcast_ref)
+/// for an object reference implemented by hand.
+#[macro_export]
+macro_rules! impl_any_compatible_conversions {
+    ($($t:ty),* $(,)?) => {
+        $crate::impl_try_from_any!($($t),*);
+        $crate::impl_arg_into_ref!($($t),*);
+        $crate::impl_into_arg_holder_default!($($t),*);
+    };
+}
+
+/// `TryFrom` from FFI values; part of 
[`impl_any_compatible_conversions!`](crate::impl_any_compatible_conversions).
+#[doc(hidden)]
 #[macro_export]
 macro_rules! impl_try_from_any {
     ($($t:ty),* $(,)?) => {
@@ -537,13 +544,8 @@ macro_rules! impl_try_from_any_for_parametric {
     };
 }
 
-/// Let each listed [`AnyCompatible`](crate::AnyCompatible) type, owned or
-/// borrowed, be an argument of a function from
-/// [`into_typed_fn!`](crate::into_typed_fn).
-///
-/// A reference type implemented by hand calls it with
-/// [`impl_try_from_any!`](crate::impl_try_from_any) and
-/// [`impl_arg_into_ref!`](crate::impl_arg_into_ref).
+/// Typed-function arguments; part of 
[`impl_any_compatible_conversions!`](crate::impl_any_compatible_conversions).
+#[doc(hidden)]
 #[macro_export]
 macro_rules! impl_into_arg_holder_default {
     ($($t:ty),*) => {
@@ -564,13 +566,8 @@ macro_rules! impl_into_arg_holder_default {
     };
 }
 
-/// Let each listed [`AnyCompatible`](crate::AnyCompatible) type, owned or
-/// borrowed, be an element of the arguments of
-/// [`Function::call_tuple`](crate::Function::call_tuple).
-///
-/// A reference type implemented by hand calls it with
-/// [`impl_try_from_any!`](crate::impl_try_from_any) and
-/// [`impl_into_arg_holder_default!`](crate::impl_into_arg_holder_default).
+/// `call_tuple` arguments; part of 
[`impl_any_compatible_conversions!`](crate::impl_any_compatible_conversions).
+#[doc(hidden)]
 #[macro_export]
 macro_rules! impl_arg_into_ref {
     ($($t:ty),*) => {
diff --git a/rust/tvm-ffi/src/object.rs b/rust/tvm-ffi/src/object.rs
index ccfc8cc6..51ab2919 100644
--- a/rust/tvm-ffi/src/object.rs
+++ b/rust/tvm-ffi/src/object.rs
@@ -20,6 +20,7 @@ use std::ops::Deref;
 use std::ptr::NonNull;
 use std::sync::atomic::AtomicU64;
 
+use crate::any::AnyView;
 use crate::derive::ObjectRef;
 use crate::type_traits::AnyCompatible;
 pub use tvm_ffi_sys::TVMFFITypeIndex as TypeIndex;
@@ -253,11 +254,189 @@ pub unsafe trait ObjectRefCore: Sized + Clone + 
AnyCompatible {
     }
 }
 
+/// The [`AnyCompatible`] implementation of an object reference, with every
+/// hook defaulted, as C++ `TypeTraits<T>` derives from
+/// `ObjectRefTypeTraitsBase<T>`: every implementer is `AnyCompatible`.
+///
+/// `#[derive(ObjectRef)]` implements it with the defaults. A typed reference
+/// that accepts only some objects of its type, or converts other values,
+/// implements it by hand and overrides only what differs, as C++ `IntExpr`
+/// overrides `CheckAnyStrict` and `ConvertFallbackValue`. A type with another
+/// representation, such as [`Array`](crate::Array), implements
+/// `AnyCompatible` instead.
+///
+/// # Safety
+///
+/// `Self` is exactly one [`ObjectArc<Self::ContainerType>`](ObjectArc). The
+/// hooks are safe and cannot break the [`AnyCompatible`] contract.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::derive::ObjectRef;
+/// use tvm_ffi::{rs_object, Any, AnyView, Object, ObjectArc, 
ObjectRefAnyCompatible};
+///
+/// #[rs_object("my_ext.MyPair", final)]
+/// pub struct MyPairObj {
+///     base: Object,
+///     pub first: i64,
+///     pub second: i64,
+/// }
+///
+/// #[derive(Clone, ObjectRef)]
+/// pub struct MyPair {
+///     data: ObjectArc<MyPairObj>,
+/// }
+///
+/// /// A `MyPair` whose `first` is positive.
+/// #[derive(Clone, ObjectRef)]
+/// #[object_ref(skip_any_compatible)]
+/// pub struct PositivePair {
+///     data: ObjectArc<MyPairObj>,
+/// }
+///
+/// unsafe impl ObjectRefAnyCompatible for PositivePair {
+///     fn extra_check(pair: &MyPairObj) -> bool {
+///         pair.first > 0
+///     }
+///
+///     // A positive integer `n` converts to the pair `(n, n)`.
+///     fn convert_fallback(value: AnyView<'_>) -> Option<Self> {
+///         let n = i64::try_from(value).ok().filter(|n| *n > 0)?;
+///         Some(Self::from_object(MyPairObj::new(Object::new(), n, n)))
+///     }
+/// }
+///
+/// let negative = Any::from(MyPair::from_object(MyPairObj::new(Object::new(), 
-1, 2)));
+/// assert!(negative.downcast_ref::<PositivePair>().is_none());
+/// assert!(PositivePair::try_from(negative).is_err());
+/// assert_eq!(PositivePair::try_from(Any::from(3_i64))?.second, 3);
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
+pub unsafe trait ObjectRefAnyCompatible: ObjectRefCore {
+    /// Whether no hook is overridden, so that `match_any!` may route a final
+    /// object type by its type index alone; only `#[derive(ObjectRef)]` sets
+    /// it. See [`AnyCompatible::MATCH_ANY_EXACT`].
+    #[doc(hidden)]
+    const MATCH_ANY_EXACT: bool = false;
+
+    /// Whether `obj`, an instance of `ContainerType`, is a value of `Self`.
+    /// Every strict and converting cast checks it, after the object type.
+    #[inline]
+    fn extra_check(obj: &Self::ContainerType) -> bool {
+        let _ = obj;
+        true
+    }
+
+    /// Convert a value that is not a `Self` object, as C++
+    /// `ConvertFallbackValue` does. Only converting casts (`TryFrom<Any>`,
+    /// typed function arguments) call it; `downcast_ref`, `downcast` and
+    /// `match_any!` never do.
+    #[inline]
+    fn convert_fallback(value: AnyView<'_>) -> Option<Self> {
+        let _ = value;
+        None
+    }
+
+    /// The type name in conversion errors and type schemas; by default the
+    /// object's type key. See [`AnyCompatible::type_str`].
+    fn custom_type_str() -> String {
+        Self::ContainerType::TYPE_KEY.into()
+    }
+
+    /// The name of the mismatched `value` in conversion errors; by default its
+    /// runtime type key. See [`AnyCompatible::get_mismatch_type_info`].
+    fn custom_mismatch_type_info(value: AnyView<'_>) -> String {
+        crate::type_traits::runtime_type_key(value.type_index())
+    }
+}
+
+/// Write the object cell of the live object `ptr`.
+#[inline(always)]
+unsafe fn write_object_cell<T: ObjectCore>(ptr: *const T, data: &mut 
TVMFFIAny) {
+    let object = ptr as *mut TVMFFIObject;
+    data.type_index = (*object).type_index;
+    // SAFETY: a live object's header holds its registered object-range type 
index.
+    std::hint::assert_unchecked(data.type_index >= 
TypeIndex::kTVMFFIStaticObjectBegin as i32);
+    data.small_str_len = 0;
+    data.data_union.v_obj = object;
+}
+
+// The object cell encoding, with the strict check `is_instance_of` then
+// `extra_check`, and `convert_fallback` after it in converting casts.
+unsafe impl<T: ObjectRefAnyCompatible> AnyCompatible for T {
+    // An overridden hook makes the type inexact; a non-final object type 
always is.
+    const MATCH_ANY_EXACT: bool = <T as 
ObjectRefAnyCompatible>::MATCH_ANY_EXACT
+        && <T::ContainerType as ObjectCore>::TYPE_FINAL;
+    const MATCH_ANY_STRICT_EXACT: bool = <T as AnyCompatible>::MATCH_ANY_EXACT;
+
+    #[inline]
+    fn match_any_exact_type_index() -> i32 {
+        T::ContainerType::type_index()
+    }
+
+    #[inline]
+    unsafe fn is_undefined(field: *const Self) -> bool {
+        // `Self` is one `ObjectArc`, so its null form is the undefined field.
+        ObjectArc::is_null(field.cast::<ObjectArc<T::ContainerType>>())
+    }
+
+    #[inline(always)]
+    unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
+        write_object_cell(ObjectArc::as_ptr(T::data(src)), data);
+    }
+
+    #[inline(always)]
+    unsafe fn move_to_any(src: Self, data: &mut TVMFFIAny) {
+        write_object_cell(ObjectArc::into_raw(T::into_data(src)), data);
+    }
+
+    #[inline(always)]
+    unsafe fn check_any_strict(data: &TVMFFIAny) -> bool {
+        // An instance of `ContainerType` points to one, so `extra_check` may 
read it.
+        is_instance_of::<T::ContainerType>(data.type_index)
+            && 
T::extra_check(&*data.data_union.v_obj.cast::<T::ContainerType>())
+    }
+
+    #[inline]
+    unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) -> Self {
+        // The view does not own the object, so the new reference takes one.
+        unsafe_::inc_ref(data.data_union.v_obj);
+        T::from_data(ObjectArc::from_raw(data.data_union.v_obj.cast()))
+    }
+
+    #[inline(always)]
+    unsafe fn move_from_any_after_check(data: &mut TVMFFIAny) -> Self {
+        T::from_data(ObjectArc::from_raw(data.data_union.v_obj.cast()))
+    }
+
+    #[inline]
+    unsafe fn try_cast_from_any_view(data: &TVMFFIAny) -> Result<Self, ()> {
+        if Self::check_any_strict(data) {
+            return Ok(Self::copy_from_any_view_after_check(data));
+        }
+        // A moved-from rvalue slot is consumed only by an exact object match.
+        if data.type_index == TypeIndex::kTVMFFIObjectRValueRef as i32 {
+            return Err(());
+        }
+        T::convert_fallback(AnyView::from_raw_ffi_any(*data)).ok_or(())
+    }
+
+    fn get_mismatch_type_info(data: &TVMFFIAny) -> String {
+        // SAFETY: the caller's cell is valid for the borrow.
+        T::custom_mismatch_type_info(unsafe { AnyView::from_raw_ffi_any(*data) 
})
+    }
+
+    fn type_str() -> String {
+        T::custom_type_str()
+    }
+}
+
 /// A value that converts into the object reference `T`, owned or borrowed.
 ///
 /// An owned reference converts into an owned `T`, and a borrowed one borrows
 /// as `&T` without changing a reference count, as [`IntoIterator`] consumes a
-/// `Vec` but borrows from a `&Vec`. 
[`unsafe_impl_object_upcast!`](crate::unsafe_impl_object_upcast)
+/// `Vec` but borrows from a `&Vec`. 
[`unsafe_impl_object_ref_upcast!`](crate::unsafe_impl_object_ref_upcast)
 /// implements it for each derived reference and its reference into the base,
 /// and `#[derive(ObjectRef)]` for the type itself. A function that only reads
 /// the value borrows the output with `.borrow()`; one that keeps it takes
@@ -268,8 +447,9 @@ pub unsafe trait ObjectRefCore: Sized + Clone + 
AnyCompatible {
 ///
 /// ```
 /// use std::borrow::Borrow;
+/// use tvm_ffi::derive::ObjectRef;
 /// use tvm_ffi::object::{IntoObjectRef, ObjectRef, ObjectRefCore};
-/// use tvm_ffi::{def_object_ref, rs_object, Object};
+/// use tvm_ffi::{rs_object, Object, ObjectArc};
 ///
 /// #[rs_object("my_ext.MyPair", final)]
 /// pub struct MyPairObj {
@@ -277,13 +457,17 @@ pub unsafe trait ObjectRefCore: Sized + Clone + 
AnyCompatible {
 ///     pub first: i64,
 ///     pub second: i64,
 /// }
-/// def_object_ref!(pub MyPair, ObjectRef, MyPairObj);
+///
+/// #[derive(Clone, ObjectRef)]
+/// pub struct MyPair {
+///     data: ObjectArc<MyPairObj>,
+/// }
 ///
 /// fn same(a: impl IntoObjectRef<ObjectRef>, b: &ObjectRef) -> bool {
 ///     a.into_object_ref().borrow().same_as(b)
 /// }
 ///
-/// let pair = MyPair::from(MyPairObj::new(Object::new(), 1, 2));
+/// let pair = MyPair::from_object(MyPairObj::new(Object::new(), 1, 2));
 /// let base: ObjectRef = pair.clone().into();
 /// assert!(same(&pair, &base)); // borrowed as `&ObjectRef`, no new reference
 /// assert!(same(pair.clone(), &base)); // moved into an `ObjectRef`
@@ -503,6 +687,7 @@ pub(crate) fn type_key_of_index(type_index: i32) -> String {
 /// through reference counting.
 #[repr(C)]
 #[derive(ObjectRef, Clone)]
+#[object_ref(skip_deref)]
 pub struct ObjectRef {
     data: ObjectArc<Object>,
 }
diff --git a/rust/tvm-ffi/src/reflection.rs b/rust/tvm-ffi/src/reflection.rs
index 0ce9d30c..85f25ff5 100644
--- a/rust/tvm-ffi/src/reflection.rs
+++ b/rust/tvm-ffi/src/reflection.rs
@@ -196,6 +196,7 @@ pub struct AccessStepObj {
 
 #[repr(C)]
 #[derive(ObjectRef, Clone)]
+#[object_ref(skip_deref)]
 pub struct AccessStep {
     data: ObjectArc<AccessStepObj>,
 }
@@ -211,6 +212,7 @@ pub struct AccessPathObj {
 
 #[repr(C)]
 #[derive(ObjectRef, Clone)]
+#[object_ref(skip_deref)]
 pub struct AccessPath {
     data: ObjectArc<AccessPathObj>,
 }
diff --git a/rust/tvm-ffi/src/type_traits.rs b/rust/tvm-ffi/src/type_traits.rs
index 268a82aa..3ed6349e 100644
--- a/rust/tvm-ffi/src/type_traits.rs
+++ b/rust/tvm-ffi/src/type_traits.rs
@@ -129,11 +129,7 @@ pub unsafe trait AnyCompatible: Sized {
     unsafe fn try_cast_from_any_view(data: &TVMFFIAny) -> Result<Self, ()>;
     /// Get the type key of a type when TryCastFromAnyView fails.
     fn get_mismatch_type_info(data: &TVMFFIAny) -> String {
-        unsafe {
-            let info = TVMFFIGetTypeInfo(data.type_index);
-            assert!(!info.is_null(), "TVMFFIGetTypeInfo returned null");
-            (*info).type_key.as_str().to_string()
-        }
+        runtime_type_key(data.type_index)
     }
     /// the type string of the type
     fn type_str() -> String;
@@ -161,6 +157,15 @@ pub unsafe trait AnyCompatible: Sized {
     }
 }
 
+/// The type key of the runtime type index `type_index`.
+pub(crate) fn runtime_type_key(type_index: i32) -> String {
+    unsafe {
+        let info = TVMFFIGetTypeInfo(type_index);
+        assert!(!info.is_null(), "TVMFFIGetTypeInfo returned null");
+        (*info).type_key.as_str().to_string()
+    }
+}
+
 /// Writes a type schema, `{"type":"<origin>"}` or, with type arguments,
 /// `{"type":"<origin>","args":[...]}`, as C++ `TypeTraits<T>::TypeSchema` 
does.
 pub(crate) fn type_schema(origin: &str, args: &[String]) -> String {
@@ -244,7 +249,7 @@ impl ContainerElement for Any {}
 /// cell, as [`Array`](crate::Array) indexing does.
 ///
 /// A reference type implemented by hand gets it from
-/// [`unsafe_impl_ref_downcast!`](crate::unsafe_impl_ref_downcast).
+/// 
[`unsafe_impl_object_ref_downcast_ref!`](crate::unsafe_impl_object_ref_downcast_ref).
 ///
 /// # Safety
 ///
diff --git a/rust/tvm-ffi/tests/test_cast.rs b/rust/tvm-ffi/tests/test_cast.rs
index 6e1e1386..f4159806 100644
--- a/rust/tvm-ffi/tests/test_cast.rs
+++ b/rust/tvm-ffi/tests/test_cast.rs
@@ -66,7 +66,7 @@ struct TestDerived {
     data: ObjectArc<TestDerivedObj>,
 }
 
-tvm_ffi::unsafe_impl_object_upcast!(TestDerived => TestBase);
+tvm_ffi::unsafe_impl_object_ref_upcast!(TestDerived => TestBase);
 
 // unwrap_err() requires the Ok type to implement Debug, which ObjectRef types 
do not
 fn expect_err<T>(res: Result<T>) -> Error {
diff --git a/rust/tvm-ffi/tests/test_object_ref_any.rs 
b/rust/tvm-ffi/tests/test_object_ref_any.rs
new file mode 100644
index 00000000..c3c18582
--- /dev/null
+++ b/rust/tvm-ffi/tests/test_object_ref_any.rs
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+// The three ways a downstream crate makes an object reference `AnyCompatible`.
+
+use tvm_ffi::derive::ObjectRef;
+use tvm_ffi::object::ObjectArc;
+use tvm_ffi::tvm_ffi_sys::TVMFFIAny;
+use tvm_ffi::{impl_any_compatible_conversions, match_any, rs_object};
+use tvm_ffi::{Any, AnyCompatible, AnyView, Object, ObjectRefCore};
+
+// A derived reference.
+#[rs_object("testing.object_ref_any.Pair", final)]
+pub struct PairObj {
+    base: Object,
+    pub first: i64,
+    pub second: i64,
+}
+#[derive(Clone, ObjectRef)]
+pub struct Pair {
+    data: ObjectArc<PairObj>,
+}
+
+fn pair(first: i64, second: i64) -> Pair {
+    Pair::from_object(PairObj::new(Object::new(), first, second))
+}
+
+// A typed reference: a `Pair` whose `first` is positive; a positive integer
+// `n` converts to `(n, n)`.
+#[derive(Clone, ObjectRef)]
+#[object_ref(skip_any_compatible)]
+pub struct PositivePair {
+    data: ObjectArc<PairObj>,
+}
+
+unsafe impl tvm_ffi::ObjectRefAnyCompatible for PositivePair {
+    fn extra_check(pair: &PairObj) -> bool {
+        pair.first > 0
+    }
+
+    fn convert_fallback(value: AnyView<'_>) -> Option<Self> {
+        let n = i64::try_from(value).ok().filter(|n| *n > 0)?;
+        Some(Self::from_object(PairObj::new(Object::new(), n, n)))
+    }
+
+    fn custom_type_str() -> String {
+        "PositivePair".into()
+    }
+
+    fn custom_mismatch_type_info(value: AnyView<'_>) -> String {
+        match value.downcast_ref::<Pair>() {
+            Some(pair) => format!("Pair(first={})", pair.first),
+            None => "non-pair".into(),
+        }
+    }
+}
+
+// A reference with a hand-written `AnyCompatible`, as the containers have.
+#[derive(Clone)]
+#[repr(transparent)]
+pub struct RawPair(Pair);
+
+unsafe impl ObjectRefCore for RawPair {
+    type ContainerType = PairObj;
+    fn data(this: &Self) -> &ObjectArc<PairObj> {
+        Pair::data(&this.0)
+    }
+    fn into_data(this: Self) -> ObjectArc<PairObj> {
+        Pair::into_data(this.0)
+    }
+    unsafe fn from_data(data: ObjectArc<PairObj>) -> Self {
+        Self(Pair::from_data(data))
+    }
+}
+
+unsafe impl AnyCompatible for RawPair {
+    fn type_str() -> String {
+        "RawPair".into()
+    }
+    unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
+        Pair::copy_to_any_view(&src.0, data)
+    }
+    unsafe fn move_to_any(src: Self, data: &mut TVMFFIAny) {
+        Pair::move_to_any(src.0, data)
+    }
+    unsafe fn check_any_strict(data: &TVMFFIAny) -> bool {
+        Pair::check_any_strict(data)
+    }
+    unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) -> Self {
+        Self(Pair::copy_from_any_view_after_check(data))
+    }
+    unsafe fn move_from_any_after_check(data: &mut TVMFFIAny) -> Self {
+        Self(Pair::move_from_any_after_check(data))
+    }
+    unsafe fn try_cast_from_any_view(data: &TVMFFIAny) -> Result<Self, ()> {
+        Pair::try_cast_from_any_view(data).map(Self)
+    }
+}
+
+impl_any_compatible_conversions!(RawPair);
+
+// The trait's doc example covers the strict check and the fallback; here, what
+// it does not: borrows never fall back, and the mismatch message is custom.
+#[test]
+fn typed_reference_borrow_never_falls_back_and_names_the_mismatch() {
+    assert!(Any::from(3_i64).downcast_ref::<PositivePair>().is_none());
+    let error = PositivePair::try_from(Any::from(pair(-1, 2)))
+        .err()
+        .unwrap();
+    assert_eq!(
+        error.message(),
+        "Cannot convert from type `Pair(first=-1)` to `PositivePair`"
+    );
+}
+
+// With 20 or more arms, `match_any!` routes final object types by type index,
+// which must not skip the typed arm's check.
+macro_rules! classify {
+    ($value:expr, $($pair:ident)*) => {
+        match_any! {
+            $value {
+                PositivePair(_) => "positive",
+                $(Pair($pair) => "pair",)*
+                _ => "other",
+            }
+        }
+    };
+}
+
+#[test]
+fn match_any_checks_a_typed_arm_of_a_final_type() {
+    let classify =
+        |value: Any| classify!(value, _a _b _c _d _e _f _g _h _i _j _k _l _m 
_n _o _p _q _r _s);
+    assert_eq!(classify(Any::from(pair(1, 2))), "positive");
+    assert_eq!(classify(Any::from(pair(-1, 2))), "pair");
+    assert_eq!(classify(Any::from(3_i64)), "other");
+}
+
+#[test]
+fn hand_written_any_compatible_round_trips() {
+    let raw = RawPair::try_from(Any::from(pair(1, 2))).unwrap();
+    assert_eq!(Any::from(raw).downcast_ref::<Pair>().unwrap().second, 2);
+}
diff --git a/rust/tvm-ffi/tests/test_object_reflection.rs 
b/rust/tvm-ffi/tests/test_object_reflection.rs
index 2f2f63ee..cd4cc99c 100644
--- a/rust/tvm-ffi/tests/test_object_reflection.rs
+++ b/rust/tvm-ffi/tests/test_object_reflection.rs
@@ -100,7 +100,7 @@ struct Point3 {
     data: ObjectArc<Point3Obj>,
 }
 
-tvm_ffi::unsafe_impl_object_upcast!(Point3 => Point);
+tvm_ffi::unsafe_impl_object_ref_upcast!(Point3 => Point);
 
 fn point_obj(x: i64, y: f64, label: &str) -> PointObj {
     PointObj {
diff --git a/rust/tvm-ffi/tests/test_object_registration.rs 
b/rust/tvm-ffi/tests/test_object_registration.rs
index 21684172..03403289 100644
--- a/rust/tvm-ffi/tests/test_object_registration.rs
+++ b/rust/tvm-ffi/tests/test_object_registration.rs
@@ -58,7 +58,7 @@ struct RegisteredLeaf {
     data: ObjectArc<RegisteredLeafObj>,
 }
 
-tvm_ffi::unsafe_impl_object_upcast!(RegisteredLeaf => RegisteredBase);
+tvm_ffi::unsafe_impl_object_ref_upcast!(RegisteredLeaf => RegisteredBase);
 
 fn registered_index(type_key: &str) -> i32 {
     let key = unsafe { TVMFFIByteArray::from_str(type_key) };
diff --git a/rust/tvm-ffi/tests/test_rs_object.rs 
b/rust/tvm-ffi/tests/test_rs_object.rs
index a968e3c3..e3dc68b8 100644
--- a/rust/tvm-ffi/tests/test_rs_object.rs
+++ b/rust/tvm-ffi/tests/test_rs_object.rs
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-use tvm_ffi::derive::Object;
+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};
@@ -40,7 +40,10 @@ pub struct RsPointObj {
     pub label: tvm_ffi::String,
 }
 
-def_object_ref!(pub RsPoint, ObjectRef, RsPointObj);
+#[derive(Clone, ObjectRef)]
+pub struct RsPoint {
+    data: ObjectArc<RsPointObj>,
+}
 
 impl RsPointObj {
     fn reflect_methods(def: &mut ObjectDef<Self>) {
@@ -63,12 +66,12 @@ pub struct RsPoint3Obj {
     tag: i64,
 }
 
-def_object_ref!(
-    /// The reference with its own docs.
-    pub RsPoint3,
-    RsPoint,
-    RsPoint3Obj,
-);
+/// The reference with its own docs.
+#[derive(Clone, ObjectRef)]
+#[object_ref(parent = RsPoint)]
+pub struct RsPoint3 {
+    data: ObjectArc<RsPoint3Obj>,
+}
 
 /// A subtype without fields, which still registers its reflection.
 #[rs_object("testing.rust.RsMarker", final)]
@@ -76,7 +79,11 @@ pub struct RsMarkerObj {
     base: RsPointObj,
 }
 
-def_object_ref!(pub RsMarker, RsPoint, RsMarkerObj);
+#[derive(Clone, ObjectRef)]
+#[object_ref(parent = RsPoint)]
+pub struct RsMarker {
+    data: ObjectArc<RsMarkerObj>,
+}
 
 /// Scalar fields, whose literal arguments to `new` infer as the field types.
 #[rs_object("testing.rust.RsScalars", final)]
@@ -88,7 +95,10 @@ pub struct RsScalarsObj {
     pub ratio: f32,
 }
 
-def_object_ref!(pub RsScalars, ObjectRef, RsScalarsObj);
+#[derive(Clone, tvm_ffi::derive::ObjectRef)]
+pub struct RsScalars {
+    data: tvm_ffi::object::ObjectArc<RsScalarsObj>,
+}
 
 // `testing.TestCxxClassBase` from libtvm_ffi_testing, bound as tvm-ffi-stubgen
 // binds a complete C++ type that can have children: with a public `new`,
@@ -130,8 +140,11 @@ unsafe impl UnsafeInit for TestCxxClassBaseObj {
     }
 }
 
-// `def_object_ref!` takes any object type, not only an `rs_object` one.
-def_object_ref!(pub TestCxxClassBase, ObjectRef, TestCxxClassBaseObj);
+// A reference to any object type, not only an `rs_object` one.
+#[derive(Clone, ObjectRef)]
+pub struct TestCxxClassBase {
+    data: ObjectArc<TestCxxClassBaseObj>,
+}
 
 /// A subtype of a C++ type.
 #[rs_object("testing.rust.RsCxxChild", final, s_eq_hash_kind = "TreeNode")]
@@ -140,7 +153,11 @@ pub struct RsCxxChildObj {
     pub name: tvm_ffi::String,
 }
 
-def_object_ref!(pub RsCxxChild, TestCxxClassBase, RsCxxChildObj);
+#[derive(Clone, ObjectRef)]
+#[object_ref(parent = TestCxxClassBase)]
+pub struct RsCxxChild {
+    data: ObjectArc<RsCxxChildObj>,
+}
 
 /// A type that defines its own `__ffi_init__`, over which C++ generates none.
 #[rs_object("testing.rust.RsOwnInit", final, reflection = 
RsOwnInitObj::reflect_methods)]
@@ -149,13 +166,21 @@ pub struct RsOwnInitObj {
     pub v: i64,
 }
 
-def_object_ref!(pub RsOwnInit, ObjectRef, RsOwnInitObj);
+#[derive(Clone, ObjectRef)]
+pub struct RsOwnInit {
+    data: ObjectArc<RsOwnInitObj>,
+}
 
 impl RsOwnInitObj {
     fn reflect_methods(def: &mut ObjectDef<Self>) {
         def.def_static(
             "__ffi_init__",
-            |v: i64| -> Result<RsOwnInit> { 
Ok(RsOwnInitObj::new(Object::new(), 2 * v).into()) },
+            |v: i64| -> Result<RsOwnInit> {
+                Ok(RsOwnInit::from_object(RsOwnInitObj::new(
+                    Object::new(),
+                    2 * v,
+                )))
+            },
             "Twice `v`.",
         );
     }
@@ -172,7 +197,10 @@ pub struct RsInitOffObj {
     pub point: Option<RsPoint>,
 }
 
-def_object_ref!(pub RsInitOff, ObjectRef, RsInitOffObj);
+#[derive(Clone, ObjectRef)]
+pub struct RsInitOff {
+    data: ObjectArc<RsInitOffObj>,
+}
 
 /// An `init_off` reference, which all-zero bytes leave undefined: its 
registration fails.
 #[rs_object("testing.rust.RsInitOffReference", final)]
@@ -194,27 +222,40 @@ mod reexported {
         pub v: i64,
     }
 
-    ::tvm_ffi::def_object_ref!(
-        pub Reexported,
-        ::tvm_ffi::object::ObjectRef,
-        ReexportedObj,
-        crate = ::tvm_ffi
-    );
+    #[derive(Clone, ::tvm_ffi::derive::ObjectRef)]
+    #[object_ref(crate = ::tvm_ffi)]
+    pub struct Reexported {
+        data: ::tvm_ffi::object::ObjectArc<ReexportedObj>,
+    }
+
+    impl Reexported {
+        pub fn new(v: i64) -> Self {
+            Self::from_object(ReexportedObj::new(::tvm_ffi::Object::new(), v))
+        }
+    }
 }
 
 fn point(x: i64, label: &str) -> RsPoint {
-    RsPointObj::new(Object::new(), x, label).into()
+    RsPoint::from_object(RsPointObj::new(Object::new(), x, label))
 }
 
 fn point3(x: i64, z: i64) -> RsPoint3 {
     let tags = Array::from_vec(vec!["a".into(), "b".into()]);
-    RsPoint3Obj::new(RsPointObj::new(Object::new(), x, "p3"), z, tags, 
7).into()
+    RsPoint3::from_object(RsPoint3Obj::new(
+        RsPointObj::new(Object::new(), x, "p3"),
+        z,
+        tags,
+        7,
+    ))
 }
 
 /// The dummy call keeps libtvm_ffi_testing, which registers 
`testing.TestCxxClassBase`, linked.
 fn cxx_child(v_i64: i64, v_i32: i32, name: &str) -> RsCxxChild {
     assert_eq!(unsafe { tvm_ffi_sys::TVMFFITestingDummyTarget() }, 0);
-    RsCxxChildObj::new(TestCxxClassBaseObj::new(v_i64, v_i32), name).into()
+    RsCxxChild::from_object(RsCxxChildObj::new(
+        TestCxxClassBaseObj::new(v_i64, v_i32),
+        name,
+    ))
 }
 
 fn type_info<N: ObjectCore>() -> &'static TVMFFITypeInfo {
@@ -352,7 +393,7 @@ fn test_constructors_deref_and_upcast() {
     assert!(back.same_as(&p3));
     assert!(RsPoint3::try_from(Any::from(point(1, ""))).is_err());
 
-    let marker = 
RsMarker::from(RsMarkerObj::new(RsPointObj::new(Object::new(), 4, "m")));
+    let marker = 
RsMarker::from_object(RsMarkerObj::new(RsPointObj::new(Object::new(), 4, "m")));
     assert_eq!(marker.x, 4);
     assert!(RsPoint::from(&marker).same_as(&marker));
 
@@ -364,7 +405,8 @@ fn test_constructors_deref_and_upcast() {
 #[test]
 fn test_new_takes_literals_of_the_field_types() {
     register_types();
-    let scalars = RsScalars::from(RsScalarsObj::new(Object::new(), 
5_000_000_000, 1, 2, 0.5));
+    let scalars =
+        RsScalars::from_object(RsScalarsObj::new(Object::new(), 5_000_000_000, 
1, 2, 0.5));
     assert_eq!(
         (scalars.big, scalars.count, scalars.port, scalars.ratio),
         (5_000_000_000, 1, 2, 0.5)
@@ -411,7 +453,7 @@ fn test_shallow_copy_and_finality_are_type_attributes() {
     assert!(is_final(RsPoint3Obj::type_index()));
     assert!(is_final(RsMarkerObj::type_index()));
 
-    let original = 
RsMarker::from(RsMarkerObj::new(RsPointObj::new(Object::new(), 3, "m")));
+    let original = 
RsMarker::from_object(RsMarkerObj::new(RsPointObj::new(Object::new(), 3, "m")));
     let shallow_copy =
         Function::try_from(attr(RsMarkerObj::type_index(), 
"__ffi_shallow_copy__")).unwrap();
     let copy = 
RsMarker::try_from(shallow_copy.call_tuple((original.clone(),)).unwrap()).unwrap();
@@ -430,7 +472,7 @@ fn test_subtype_of_a_cpp_type() {
     assert_eq!((child.v_i64, child.v_i32, child.name.as_str()), (7, 8, "c"));
     // The C++ fields are reflected under the subtype.
     let v_i64 = FieldGetter::new(RsCxxChildObj::type_index(), 
"v_i64").unwrap();
-    assert_eq!(v_i64.get::<_, i64>(&*child.base).unwrap(), 7);
+    assert_eq!(v_i64.get::<_, i64>(&*child).unwrap(), 7);
     assert_eq!(field_names::<RsCxxChildObj>(), ["name"]);
 
     let base = TestCxxClassBase::from(child.clone());
@@ -448,7 +490,7 @@ fn test_subtype_of_a_cpp_type() {
 #[test]
 fn test_crate_path_names_a_reexported_tvm_ffi() {
     register_types();
-    let value = 
reexported::Reexported::from(reexported::ReexportedObj::new(Object::new(), 5));
+    let value = reexported::Reexported::new(5);
     assert_eq!(value.v, 5);
     assert!(ObjectRef::from(&value).same_as(&value));
     assert_eq!(field_names::<reexported::ReexportedObj>(), ["v"]);
diff --git a/tests/python/test_stubgen_rust.py 
b/tests/python/test_stubgen_rust.py
index bbb5f043..d60e5708 100644
--- a/tests/python/test_stubgen_rust.py
+++ b/tests/python/test_stubgen_rust.py
@@ -276,7 +276,7 @@ def test_check_tokens_ignores_spelling() -> None:
         "#[derive(tvm_ffi::derive::Object)]",
         "pub struct AddObj { base: ExprObj, pub a: i64, pub b: i64 }",
         "const _: () = assert!(::core::mem::size_of::<AddObj>() == 40);",
-        "tvm_ffi::unsafe_impl_object_upcast!(Add => super::super::ir::Expr);",
+        "tvm_ffi::unsafe_impl_object_ref_upcast!(Add => 
super::super::ir::Expr);",
     ]
     respelled = [
         "use crate::ir::Expr;",
@@ -287,7 +287,7 @@ def test_check_tokens_ignores_spelling() -> None:
         "    pub b: i64,",
         "}",
         "const _: () = assert!(core::mem::size_of::<AddObj>() == 40);",
-        "unsafe_impl_object_upcast!(Add => Expr);",
+        "unsafe_impl_object_ref_upcast!(Add => Expr);",
     ]
     assert check_tokens(generated) == check_tokens(respelled)
     for drift in [("a: i64", "a: i32"), ("40", "48"), ("ExprObj,", 
"StmtObj,"), ("Expr)", "Stmt)")]:
@@ -380,13 +380,6 @@ pub struct Pair {{
     base: ObjectArc<PairObj>,
 }}
 
-impl Deref for Pair {{
-    type Target = PairObj;
-    fn deref(&self) -> &PairObj {{
-        &self.base
-    }}
-}}
-
 impl PairObj {{
     pub fn a(&self) -> Result<i64> {{
         FieldGetter::new(Self::type_index(), "a")?.get(self)
@@ -425,7 +418,6 @@ def test_render_root_object() -> None:
     text, imports = _render(info)
     assert text == ROOT_EXPECTED
     assert _uses(imports) == {
-        "std::ops::Deref",
         "tvm_ffi::Object",
         "tvm_ffi::ObjectArc",
         "tvm_ffi::ObjectCore",
@@ -466,7 +458,7 @@ def test_render_derived_object_same_module() -> None:
     assert "#[type_final]\npub struct AddObj {\n    base: ExprObj,\n}" in text
     assert "impl Deref for AddObj {\n    type Target = ExprObj;" in text
     assert "pub fn a(&self) -> Result<Expr> {" in text
-    assert text.endswith("tvm_ffi::unsafe_impl_object_upcast!(Add => BaseExpr, 
Add => Expr);")
+    assert text.endswith("tvm_ffi::unsafe_impl_object_ref_upcast!(Add => 
BaseExpr, Add => Expr);")
     # Same-module names are local items: nothing to `use`.
     assert not any(path.startswith("demo") for path in _uses(imports))
 
@@ -476,7 +468,7 @@ def test_render_derived_object_cross_module() -> None:
     info = _info("tirx.Add", parent="ir.Expr", ancestors=["ffi.Object", 
"ir.Expr"])
     text, imports = _render(info)
     assert "    base: ExprObj," in text
-    assert text.endswith("tvm_ffi::unsafe_impl_object_upcast!(Add => Expr);")
+    assert text.endswith("tvm_ffi::unsafe_impl_object_ref_upcast!(Add => 
Expr);")
     assert {"crate::ir::ExprObj", "crate::ir::Expr"} <= _uses(imports)
 
 
@@ -495,7 +487,7 @@ def test_render_object_under_builtin_parent() -> None:
     assert "tvm_ffi::Object" in _uses(imports)
     # No Deref, upcast or crate import for the stand-ins.
     assert "impl Deref for ColorObj" not in text
-    assert "unsafe_impl_object_upcast" 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"))
@@ -513,7 +505,6 @@ def test_render_object_under_builtin_parent() -> None:
 
 
 BUILTIN_MIRRORS_EXPECTED = """\
-use std::ops::Deref;
 use tvm_ffi::Object;
 use tvm_ffi::ObjectArc;
 
@@ -609,13 +600,6 @@ pub struct IterVar {{
     base: ObjectArc<IterVarObj>,
 }}
 
-impl Deref for IterVar {{
-    type Target = IterVarObj;
-    fn deref(&self) -> &IterVarObj {{
-        &self.base
-    }}
-}}
-
 impl Deref for IterVarObj {{
     type Target = PrimExprConvertibleObj;
     fn deref(&self) -> &PrimExprConvertibleObj {{
@@ -646,7 +630,7 @@ impl IterVarObj {{
     }}
 }}
 
-tvm_ffi::unsafe_impl_object_upcast!(IterVar => PrimExprConvertible);"""
+tvm_ffi::unsafe_impl_object_ref_upcast!(IterVar => PrimExprConvertible);"""
 
 
 def test_render_iter_var_golden() -> None:
@@ -759,13 +743,6 @@ pub struct Expr {
     base: ObjectArc<ExprObj>,
 }
 
-impl Deref for Expr {
-    type Target = ExprObj;
-    fn deref(&self) -> &ExprObj {
-        &self.base
-    }
-}
-
 impl ExprObj {
     pub fn new(span: Option<Span>, ty: Type) -> Self {
         let base = Object::new();
@@ -824,13 +801,6 @@ pub struct Add {
     base: ObjectArc<AddObj>,
 }
 
-impl Deref for Add {
-    type Target = AddObj;
-    fn deref(&self) -> &AddObj {
-        &self.base
-    }
-}
-
 impl Deref for AddObj {
     type Target = ExprObj;
     fn deref(&self) -> &ExprObj {
@@ -858,7 +828,7 @@ impl Add {
     }
 }
 
-tvm_ffi::unsafe_impl_object_upcast!(Add => Expr, Add => PrimExpr);"""
+tvm_ffi::unsafe_impl_object_ref_upcast!(Add => Expr, Add => PrimExpr);"""
 
 
 def test_render_complete_expr_golden() -> None:
@@ -868,7 +838,6 @@ def test_render_complete_expr_golden() -> None:
     text, imports = _render(_expr(), imports)
     assert text == EXPR_EXPECTED
     assert _uses(imports) == {
-        "std::ops::Deref",
         "std::ptr::addr_of_mut",
         "tvm_ffi::Object",
         "tvm_ffi::ObjectArc",
@@ -1237,7 +1206,7 @@ def test_upcast_directive_adds_typed_views() -> None:
     RUST.add_directive(imports, "upcast", "demo.Leaf -> Other", 2)
     text, imports = _render(info, imports)
     assert text.endswith(
-        "tvm_ffi::unsafe_impl_object_upcast!(Leaf => Base, Leaf => LeafView, 
Leaf => Other);"
+        "tvm_ffi::unsafe_impl_object_ref_upcast!(Leaf => Base, Leaf => 
LeafView, Leaf => Other);"
     )
     assert "crate::typed::LeafView" in _uses(imports)
     assert "fn new(" not in text
@@ -1408,7 +1377,7 @@ def test_registry_complete_chain_is_mirrored() -> None:
         "}"
     ) in dd
     assert dd.endswith(
-        "tvm_ffi::unsafe_impl_object_upcast!(TestCxxClassDerivedDerived => 
TestCxxClassBase, "
+        "tvm_ffi::unsafe_impl_object_ref_upcast!(TestCxxClassDerivedDerived => 
TestCxxClassBase, "
         "TestCxxClassDerivedDerived => TestCxxClassDerived);"
     )
     assert {"tvm_ffi::String", "tvm_ffi::ShallowCopy"} <= _uses(imports)
@@ -1475,7 +1444,7 @@ def test_cli_init_generates_a_module_tree(tmp_path: Path, 
monkeypatch: pytest.Mo
     assert '#[type_key = "testing.TestCxxClassDerivedDerived"]' in text
     assert "    base: TestCxxClassDerivedObj,\n    pub v_str: String," in text
     assert (
-        "tvm_ffi::unsafe_impl_object_upcast!(TestCxxClassDerivedDerived => 
TestCxxClassBase, "
+        "tvm_ffi::unsafe_impl_object_ref_upcast!(TestCxxClassDerivedDerived => 
TestCxxClassBase, "
         "TestCxxClassDerivedDerived => TestCxxClassDerived);"
     ) in text
     # Builtin ancestors are mirrored once, in the import section; enum 
fixtures embed the last one.
@@ -1697,7 +1666,7 @@ def test_blocks_are_named_by_the_module_of_their_file(
     text = nested.read_text(encoding="utf-8")
     assert "use crate::TestCxxClassBase;\nuse crate::TestCxxClassBaseObj;\n" 
in text
     assert "    base: TestCxxClassBaseObj," in text
-    assert "tvm_ffi::unsafe_impl_object_upcast!(TestCxxClassDerived => 
TestCxxClassBase);" in text
+    assert "tvm_ffi::unsafe_impl_object_ref_upcast!(TestCxxClassDerived => 
TestCxxClassBase);" in text
     assert "super::" not in text
     # Moving the parent's block moves the path, not the type key.
     lib.write_text("", encoding="utf-8")
@@ -1720,7 +1689,7 @@ def test_ty_map_parent_redirects_base_deref_and_upcast() 
-> None:
     text = "\n".join(block.lines[1:-1])
     assert "    base: ExprObj," in text
     assert "impl Deref for AddObj {\n    type Target = ExprObj;" in text
-    assert text.endswith("tvm_ffi::unsafe_impl_object_upcast!(Add => Expr);")
+    assert text.endswith("tvm_ffi::unsafe_impl_object_ref_upcast!(Add => 
Expr);")
     assert {"crate::ir::ExprObj", "crate::ir::Expr"} <= _uses(imports)
     assert not any(path.startswith("super::") for path in _uses(imports))
 
@@ -1820,7 +1789,7 @@ def 
test_parent_with_the_objects_own_name_is_spelled_in_full() -> None:
     parent = "crate::demo::t::Frame"
     assert f"    base: {parent}Obj," in text
     assert f"    type Target = {parent}Obj;" in text
-    assert text.endswith(f"tvm_ffi::unsafe_impl_object_upcast!(Frame => 
{parent});")
+    assert text.endswith(f"tvm_ffi::unsafe_impl_object_ref_upcast!(Frame => 
{parent});")
     assert not any(path.startswith(f"{parent}") for path in _uses(imports))
 
 
@@ -1859,7 +1828,7 @@ def 
test_ty_map_target_in_the_files_own_module_is_not_imported() -> None:
     assert "crate::ir::Op" not in _uses(imports)
     text, imports = render(_info("ir.Special", parent="ir.Op", 
ancestors=["ffi.Object", "ir.Op"]))
     assert "    base: OpObj," in text
-    assert text.endswith("tvm_ffi::unsafe_impl_object_upcast!(Special => Op);")
+    assert text.endswith("tvm_ffi::unsafe_impl_object_ref_upcast!(Special => 
Op);")
     assert not any(path.startswith("crate::") for path in _uses(imports))
     # Elsewhere the target is imported as written.
     text, imports = render(_info("tirx.Call", (_field("op", "ir.Op"),)))

Reply via email to