This is an automated email from the ASF dual-hosted git repository.

tlopex 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 2a35d327 [FEAT][RUST] Support runtime-checked try_cast between 
ObjectRef types (#684)
2a35d327 is described below

commit 2a35d327a6fe2578693b3d9958fdb05de4e18383
Author: Linzhang Li <[email protected]>
AuthorDate: Mon Jul 27 12:55:36 2026 -0400

    [FEAT][RUST] Support runtime-checked try_cast between ObjectRef types (#684)
    
    This PR adds direct, runtime-checked casting between Rust object
    handles, mirroring C++ `ObjectRef::as<T>` / `Downcast<T>`.
    
    **ObjectRefCast** is blanket-implemented for types implementing both
    `ObjectRefCore` and `AnyCompatible`. `try_cast::<B>(self)` consumes the
    source and, on success, rewraps the same object as `B` without cloning
    or copying it. A failed cast returns a `TypeError` describing the source
    and target types.
    
    Target compatibility is delegated to `B::check_any_strict`. ObjectRef
    hierarchies accept `B` and its runtime subtypes, while parameterized
    containers use their complete type semantics.
    
    ```rust
    let base: TestBase = derived.try_cast()?; // upcast
    let obj: ObjectRef = base.try_cast()?;    // upcast to root
    let d: TestDerived = obj.try_cast()?;     // checked downcast
    ```
    
    The direct `A -> B` path reuses the existing `AnyCompatible` conversion
    traits through a raw `TVMFFIAny`, so callers do not need to construct an
    `Any` or `AnyView`. It first creates a non-owning view for the target
    check and transfers ownership only after that check succeeds, keeping
    failure and panic paths leak-free.
    
    Derived ObjectRef conversions preserve the runtime type index stored in
    the object header. A derived object upcast to a base handle can
    therefore still round-trip through `Any` or `AnyView` and downcast
    correctly.
    
    A hidden `is_instance_of` helper provides hierarchy checks for
    derive-generated object containers. The cast path uses
    `assert_unchecked` after establishing the object type-index invariant;
    release codegen confirms that the intermediate `TVMFFIAny` storage and
    redundant object-range check are eliminated.
    
    The PR also includes the derive fixes required by downstream crates and
    focused tests covering hierarchy casts, failure ownership, dynamic-type
    preservation through `Any`/`AnyView`, direct `try_cast_from_any_view`,
    and parameterized-container mismatch.
    
    ---------
    
    Signed-off-by: yuchuan <[email protected]>
    Co-authored-by: tlopex <[email protected]>
---
 rust/tvm-ffi-macros/src/object_macros.rs |  45 +++----
 rust/tvm-ffi/src/collections/tensor.rs   |   1 +
 rust/tvm-ffi/src/function.rs             |   1 +
 rust/tvm-ffi/src/lib.rs                  |   1 +
 rust/tvm-ffi/src/object.rs               | 127 +++++++++++++++++++-
 rust/tvm-ffi/tests/test_cast.rs          | 194 +++++++++++++++++++++++++++++++
 rust/tvm-ffi/tests/test_object.rs        |   1 +
 7 files changed, 343 insertions(+), 27 deletions(-)

diff --git a/rust/tvm-ffi-macros/src/object_macros.rs 
b/rust/tvm-ffi-macros/src/object_macros.rs
index 8154709d..14dc76fd 100644
--- a/rust/tvm-ffi-macros/src/object_macros.rs
+++ b/rust/tvm-ffi-macros/src/object_macros.rs
@@ -58,7 +58,7 @@ pub fn derive_object(input: proc_macro::TokenStream) -> 
TokenStream {
                                 &type_key_arg, &mut tindex
                             );
                             if ret != 0 {
-                                proc_macro_error::abort!("Failed to get type 
index for type key: {}", #type_key);
+                                panic!("Failed to get type index for type key: 
{}", #type_key);
                             }
                             tindex
                         }
@@ -75,6 +75,9 @@ pub fn derive_object(input: proc_macro::TokenStream) -> 
TokenStream {
             let (base_id, base_ty) = (f.ident.clone()?, f.ty.clone());
             // The transitive case of subtyping
             Some(quote! {
+                const TYPE_DEPTH: i32 =
+                    <#base_ty as 
#tvm_ffi_crate::object::ObjectCore>::TYPE_DEPTH + 1;
+
                 #[inline]
                 unsafe fn object_header_mut(
                     this: &mut Self
@@ -143,35 +146,36 @@ pub fn derive_object_ref(input: proc_macro::TokenStream) 
-> TokenStream {
 
         // implement AnyCompatible for #struct_name
         unsafe impl #tvm_ffi_crate::type_traits::AnyCompatible for 
#struct_name {
-            fn type_str() -> String {
+            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 type_index =
-                    <ContainerType as 
#tvm_ffi_crate::object::ObjectCore>::type_index();
-                data.type_index = type_index as i32;
-                data.small_str_len = 0;
                 let data_ptr = 
#tvm_ffi_crate::object::ObjectArc::<ContainerType>::as_raw(
                     &src.data
                 );
-                data.data_union.v_obj =
-                    data_ptr as *mut ContainerType as *mut  
#tvm_ffi_crate::tvm_ffi_sys::TVMFFIObject;
+                let object_ptr =
+                    data_ptr as *mut ContainerType as *mut 
#tvm_ffi_crate::tvm_ffi_sys::TVMFFIObject;
+                data.type_index = (*object_ptr).type_index;
+                data.small_str_len = 0;
+                data.data_union.v_obj = object_ptr;
             }
 
-            unsafe fn check_any_strict(data: & 
#tvm_ffi_crate::tvm_ffi_sys::TVMFFIAny) -> bool {
+            #[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;
-                let type_index =
-                    <ContainerType as 
#tvm_ffi_crate::object::ObjectCore>::type_index();
-                data.type_index == type_index as i32
+                
#tvm_ffi_crate::object::is_instance_of::<ContainerType>(data.type_index)
             }
 
             unsafe fn copy_from_any_view_after_check(
@@ -192,23 +196,24 @@ pub fn derive_object_ref(input: proc_macro::TokenStream) 
-> TokenStream {
                 }
             }
 
+            #[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 type_index =
-                    <ContainerType as 
#tvm_ffi_crate::object::ObjectCore>::type_index();
-                data.type_index = type_index as i32;
-                data.small_str_len = 0;
                 let data_ptr = #tvm_ffi_crate::object::ObjectArc::into_raw(
                     src.data
                 );
-                data.data_union.v_obj =
-                    data_ptr as *mut ContainerType as *mut  
#tvm_ffi_crate::tvm_ffi_sys::TVMFFIObject;
+                let object_ptr =
+                    data_ptr as *mut ContainerType as *mut 
#tvm_ffi_crate::tvm_ffi_sys::TVMFFIObject;
+                data.type_index = (*object_ptr).type_index;
+                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 {
@@ -225,9 +230,7 @@ pub fn derive_object_ref(input: proc_macro::TokenStream) -> 
TokenStream {
             ) -> Result<Self, ()> {
                 type ContainerType = <#struct_name as 
#tvm_ffi_crate::object::ObjectRefCore>
                     ::ContainerType;
-                let type_index =
-                    <ContainerType as 
#tvm_ffi_crate::object::ObjectCore>::type_index();
-                if data.type_index == type_index as i32 {
+                if 
#tvm_ffi_crate::object::is_instance_of::<ContainerType>(data.type_index) {
                     Ok(Self::copy_from_any_view_after_check(data))
                 } else {
                     Err(())
diff --git a/rust/tvm-ffi/src/collections/tensor.rs 
b/rust/tvm-ffi/src/collections/tensor.rs
index 4dac8490..78928049 100644
--- a/rust/tvm-ffi/src/collections/tensor.rs
+++ b/rust/tvm-ffi/src/collections/tensor.rs
@@ -224,6 +224,7 @@ where
 
 unsafe impl<TNDAlloc: NDAllocator> ObjectCore for 
TensorObjFromNDAlloc<TNDAlloc> {
     const TYPE_KEY: &'static str = TensorObj::TYPE_KEY;
+    const TYPE_DEPTH: i32 = TensorObj::TYPE_DEPTH;
     fn type_index() -> i32 {
         TensorObj::type_index()
     }
diff --git a/rust/tvm-ffi/src/function.rs b/rust/tvm-ffi/src/function.rs
index 447fcd71..4af971bd 100644
--- a/rust/tvm-ffi/src/function.rs
+++ b/rust/tvm-ffi/src/function.rs
@@ -95,6 +95,7 @@ impl<F: Fn(&[AnyView]) -> Result<Any> + 'static> 
CallbackFunctionObjImpl<F> {
 
 unsafe impl<F: Fn(&[AnyView]) -> Result<Any> + 'static> ObjectCore for 
CallbackFunctionObjImpl<F> {
     const TYPE_KEY: &'static str = FunctionObj::TYPE_KEY;
+    const TYPE_DEPTH: i32 = FunctionObj::TYPE_DEPTH;
     fn type_index() -> i32 {
         FunctionObj::type_index()
     }
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index d5e2b2e8..1ba502b5 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -45,6 +45,7 @@ pub use crate::error::{
 };
 pub use crate::extra::module::Module;
 pub use crate::function::Function;
+pub use crate::object::ObjectRefCast;
 pub use crate::object::{Object, ObjectArc, ObjectCore, 
ObjectCoreWithExtraItems, ObjectRefCore};
 pub use crate::optional::Optional;
 pub use crate::string::{Bytes, String};
diff --git a/rust/tvm-ffi/src/object.rs b/rust/tvm-ffi/src/object.rs
index 8cb77ce6..78394001 100644
--- a/rust/tvm-ffi/src/object.rs
+++ b/rust/tvm-ffi/src/object.rs
@@ -20,9 +20,13 @@ use std::ops::{Deref, DerefMut};
 use std::sync::atomic::AtomicU64;
 
 use crate::derive::ObjectRef;
+use crate::type_traits::AnyCompatible;
 pub use tvm_ffi_sys::TVMFFITypeIndex as TypeIndex;
 /// Object related ABI handling
-use tvm_ffi_sys::{TVMFFIGetCustomAllocator, TVMFFIObject, 
COMBINED_REF_COUNT_BOTH_ONE};
+use tvm_ffi_sys::{
+    TVMFFIAny, TVMFFIGetCustomAllocator, TVMFFIGetTypeInfo, TVMFFIObject,
+    COMBINED_REF_COUNT_BOTH_ONE,
+};
 
 /// Object type is by default the TVMFFIObject
 #[repr(C)]
@@ -50,6 +54,12 @@ unsafe impl<T: Send + Sync + ObjectCore> Sync for 
ObjectArc<T> {}
 pub unsafe trait ObjectCore: Sized + 'static {
     /// the type key of the object
     const TYPE_KEY: &'static str;
+    /// Depth of this type in the object inheritance tree.
+    ///
+    /// The root [`Object`] has depth zero, and every registered subtype has
+    /// depth one greater than its parent. This value must be non-negative and
+    /// agree with the runtime type table entry for `Self`.
+    const TYPE_DEPTH: i32;
     // return the type index of the object
     fn type_index() -> i32;
     /// Return the object header
@@ -95,6 +105,20 @@ pub unsafe trait ObjectCoreWithExtraItems: ObjectCore {
 /// used by the ffi Any system and not user facing
 ///
 /// We mark as unsafe since it moves out the internal of the ObjectRef
+///
+/// # Safety
+///
+/// `data`, `into_data`, and `from_data` must preserve the same object
+/// allocation and form an ownership-preserving round trip. That allocation 
must
+/// start with a valid `TVMFFIObject` header whose registered object-range
+/// runtime type index correctly describes its layout and inheritance.
+///
+/// When `Self` also implements [`AnyCompatible`], `copy_to_any_view` must
+/// produce a non-owning view, while `move_to_any` must transfer ownership of
+/// the same object pointer and dynamic type index. `move_from_any_after_check`
+/// must be able to reclaim that owned representation exactly once, and a true
+/// `check_any_strict` result must guarantee that both after-check constructors
+/// are valid for it.
 pub unsafe trait ObjectRefCore: Sized + Clone {
     type ContainerType: ObjectCore;
     fn data(this: &Self) -> &ObjectArc<Self::ContainerType>;
@@ -102,6 +126,95 @@ pub unsafe trait ObjectRefCore: Sized + Clone {
     fn from_data(data: ObjectArc<Self::ContainerType>) -> Self;
 }
 
+/// Check whether a runtime type index refers to `Target` or one of its
+/// subtypes.
+///
+/// The subtype relation lives in the process-wide type table maintained by the
+/// tvm-ffi library: every registered type records its depth in the single
+/// inheritance tree together with the chain of its ancestors. The check is
+/// O(1) — if `target` really is an ancestor, it must appear in the candidate's
+/// ancestor array exactly at `target`'s depth.
+///
+/// This is a hidden support function for derive-generated object checks. 
Object
+/// indices in the registered range must refer to entries in the runtime type
+/// table.
+#[doc(hidden)]
+#[inline(always)]
+pub fn is_instance_of<Target: ObjectCore>(object_type_index: i32) -> bool {
+    let target_type_index = Target::type_index();
+    if object_type_index == target_type_index {
+        return true;
+    }
+    let object_begin = TypeIndex::kTVMFFIStaticObjectBegin as i32;
+    // Only object types participate in the type hierarchy.
+    if object_type_index < object_begin || target_type_index < object_begin {
+        return false;
+    }
+    // Parent indices are always smaller than their descendants.
+    if object_type_index < target_type_index {
+        return false;
+    }
+    unsafe {
+        let object_info = TVMFFIGetTypeInfo(object_type_index);
+        if object_info.is_null() {
+            return false;
+        }
+        let target_depth = Target::TYPE_DEPTH;
+        if (*object_info).type_depth <= target_depth {
+            return false;
+        }
+        let ancestor = *(*object_info).type_acenstors.add(target_depth as 
usize);
+        !ancestor.is_null() && (*ancestor).type_index == target_type_index
+    }
+}
+
+/// Runtime-checked casting between arbitrary `ObjectRef` types.
+///
+/// The cast uses the target's [`AnyCompatible::check_any_strict`] 
implementation,
+/// mirroring the semantics of `ObjectRef::as<T>` in C++. This supports both
+/// object hierarchies and parameterized object containers.
+///
+/// This trait is blanket-implemented for every [`ObjectRefCore`] type that is
+/// also [`AnyCompatible`].
+pub trait ObjectRefCast: ObjectRefCore + AnyCompatible {
+    /// Consume `self` and rewrap the underlying object as `B` without copying.
+    #[inline(always)]
+    fn try_cast<B>(self) -> crate::error::Result<B>
+    where
+        B: ObjectRefCore + AnyCompatible,
+    {
+        let mut any_data = TVMFFIAny::new();
+        unsafe {
+            // Keep ownership in `self` while the target check runs. This makes
+            // the failure and panic paths unwind normally instead of stranding
+            // an owned object inside a raw TVMFFIAny.
+            Self::copy_to_any_view(&self, &mut any_data);
+            debug_assert!(any_data.type_index >= 
TypeIndex::kTVMFFIStaticObjectBegin as i32);
+            // SAFETY: ObjectRefCore's contract requires its AnyCompatible
+            // representation to contain a valid object-range type index.
+            std::hint::assert_unchecked(
+                any_data.type_index >= TypeIndex::kTVMFFIStaticObjectBegin as 
i32,
+            );
+
+            if B::check_any_strict(&any_data) {
+                // Transfer ownership only after the borrowed representation 
has
+                // passed the target's complete hierarchy/container check.
+                Self::move_to_any(self, &mut any_data);
+                Ok(B::move_from_any_after_check(&mut any_data))
+            } else {
+                let msg = format!(
+                    "Cannot convert from type `{}` to `{}`",
+                    B::get_mismatch_type_info(&any_data),
+                    B::type_str()
+                );
+                Err(crate::error::Error::new(crate::error::TYPE_ERROR, &msg, 
""))
+            }
+        }
+    }
+}
+
+impl<T: ObjectRefCore + AnyCompatible> ObjectRefCast for T {}
+
 /// Base class for ObjectRef
 ///
 /// This class is used to store the data of the ObjectRef
@@ -112,7 +225,8 @@ pub struct ObjectRef {
 }
 
 /// Unsafe operations on object
-pub(crate) mod unsafe_ {
+#[doc(hidden)]
+pub mod unsafe_ {
     use tvm_ffi_sys::{
         COMBINED_REF_COUNT_BOTH_ONE, COMBINED_REF_COUNT_MASK_U32, 
COMBINED_REF_COUNT_STRONG_ONE,
         COMBINED_REF_COUNT_WEAK_ONE,
@@ -145,7 +259,7 @@ pub(crate) mod unsafe_ {
     /// # Arguments
     /// * `obj` - The object to decrease the reference count
     #[inline]
-    pub unsafe fn dec_ref(handle: *mut TVMFFIObject) {
+    pub(crate) unsafe fn dec_ref(handle: *mut TVMFFIObject) {
         let obj = &mut *handle;
         let old_combined_count = obj
             .combined_ref_count
@@ -186,20 +300,20 @@ pub(crate) mod unsafe_ {
     }
 
     #[inline]
-    pub unsafe fn strong_count(handle: *mut TVMFFIObject) -> usize {
+    pub(crate) unsafe fn strong_count(handle: *mut TVMFFIObject) -> usize {
         let obj = &mut *handle;
         (obj.combined_ref_count.load(Ordering::Relaxed) & 
COMBINED_REF_COUNT_MASK_U32) as usize
     }
 
     #[inline]
-    pub unsafe fn weak_count(handle: *mut TVMFFIObject) -> usize {
+    pub(crate) unsafe fn weak_count(handle: *mut TVMFFIObject) -> usize {
         let obj = &mut *handle;
         (obj.combined_ref_count.load(Ordering::Relaxed) >> 32) as usize
     }
 
     /// Generic object deleter for objects allocated through the registered
     /// `TVMFFICustomAllocator`.
-    pub unsafe extern "C" fn object_deleter_for_new<T>(ptr: *mut c_void, 
flags: i32)
+    pub(crate) unsafe extern "C" fn object_deleter_for_new<T>(ptr: *mut 
c_void, flags: i32)
     where
         T: super::ObjectCore,
     {
@@ -233,6 +347,7 @@ impl Object {
 
 unsafe impl ObjectCore for Object {
     const TYPE_KEY: &'static str = "ffi.Object";
+    const TYPE_DEPTH: i32 = 0;
     #[inline]
     fn type_index() -> i32 {
         TypeIndex::kTVMFFIStaticObjectBegin as i32
diff --git a/rust/tvm-ffi/tests/test_cast.rs b/rust/tvm-ffi/tests/test_cast.rs
new file mode 100644
index 00000000..fb4d8c94
--- /dev/null
+++ b/rust/tvm-ffi/tests/test_cast.rs
@@ -0,0 +1,194 @@
+/*
+ * 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 std::sync::atomic::{AtomicU32, Ordering};
+use std::sync::Arc;
+use tvm_ffi::derive::{Object, ObjectRef};
+use tvm_ffi::object::{is_instance_of, ObjectRef};
+use tvm_ffi::*;
+
+// The type keys below are registered by libtvm_ffi_testing with the hierarchy
+// Object <- testing.TestObjectBase <- testing.TestObjectDerived. The Rust-side
+// field layout does not need to match the C++ classes: the objects are created
+// and destroyed purely on the Rust side, and the casts only consult the type
+// index stored in the object header.
+
+// must have repr(C) for the object header to stay in the same position
+#[repr(C)]
+#[derive(Object)]
+#[type_key = "testing.TestObjectBase"]
+struct TestBaseObj {
+    base: Object,
+    value: i64,
+    // counter for recording the number of times the object is deleted
+    delete_counter: Arc<AtomicU32>,
+}
+
+impl Drop for TestBaseObj {
+    fn drop(&mut self) {
+        self.delete_counter.fetch_add(1, Ordering::Relaxed);
+    }
+}
+
+#[repr(C)]
+#[derive(ObjectRef, Clone)]
+struct TestBase {
+    data: ObjectArc<TestBaseObj>,
+}
+
+#[repr(C)]
+#[derive(Object)]
+#[type_key = "testing.TestObjectDerived"]
+struct TestDerivedObj {
+    base: TestBaseObj,
+    extra: i64,
+}
+
+#[repr(C)]
+#[derive(ObjectRef, Clone)]
+struct TestDerived {
+    data: ObjectArc<TestDerivedObj>,
+}
+
+// unwrap_err() requires the Ok type to implement Debug, which ObjectRef types 
do not
+fn expect_err<T>(res: Result<T>) -> Error {
+    match res {
+        Ok(_) => panic!("expected the cast to fail"),
+        Err(err) => err,
+    }
+}
+
+fn new_base(value: i64, delete_counter: Arc<AtomicU32>) -> TestBase {
+    TestBase {
+        data: ObjectArc::new(TestBaseObj {
+            base: Object::new(),
+            value,
+            delete_counter,
+        }),
+    }
+}
+
+fn new_derived(value: i64, extra: i64, delete_counter: Arc<AtomicU32>) -> 
TestDerived {
+    TestDerived {
+        data: ObjectArc::new(TestDerivedObj {
+            base: TestBaseObj {
+                base: Object::new(),
+                value,
+                delete_counter,
+            },
+            extra,
+        }),
+    }
+}
+
+#[test]
+fn test_is_instance_of() {
+    // Keep the testing library linked so its static type registrations run.
+    assert_eq!(unsafe { tvm_ffi_sys::TVMFFITestingDummyTarget() }, 0);
+
+    let object_index = tvm_ffi::Object::type_index();
+    let base_index = TestBaseObj::type_index();
+    let derived_index = TestDerivedObj::type_index();
+    assert_eq!(Object::TYPE_DEPTH, 0);
+    assert_eq!(TestBaseObj::TYPE_DEPTH, 1);
+    assert_eq!(TestDerivedObj::TYPE_DEPTH, 2);
+    // reflexive
+    assert!(is_instance_of::<TestBaseObj>(base_index));
+    // child -> ancestors at every depth
+    assert!(is_instance_of::<TestBaseObj>(derived_index));
+    assert!(is_instance_of::<Object>(derived_index));
+    assert!(is_instance_of::<Object>(base_index));
+    // the reverse direction does not hold
+    assert!(!is_instance_of::<TestDerivedObj>(base_index));
+    assert!(!is_instance_of::<TestBaseObj>(object_index));
+    // non-object type indices never match an object type
+    assert!(!is_instance_of::<Object>(TypeIndex::kTVMFFIInt as i32));
+}
+
+#[test]
+fn test_upcast_downcast_roundtrip() {
+    let delete_counter = Arc::new(AtomicU32::new(0));
+    let derived = new_derived(7, 8, delete_counter.clone());
+    // upcast to the direct parent
+    let base: TestBase = derived.try_cast().unwrap();
+    assert_eq!(base.data.value, 7);
+    // upcast further to the root ObjectRef
+    let obj: ObjectRef = base.try_cast().unwrap();
+    // downcast all the way back
+    let derived2: TestDerived = obj.try_cast().unwrap();
+    assert_eq!(derived2.data.base.value, 7);
+    assert_eq!(derived2.data.extra, 8);
+    // every step moved ownership; no extra references were created
+    assert_eq!(ObjectArc::strong_count(&derived2.data), 1);
+    assert_eq!(delete_counter.load(Ordering::Relaxed), 0);
+    drop(derived2);
+    assert_eq!(delete_counter.load(Ordering::Relaxed), 1);
+}
+
+#[test]
+fn test_cast_checks_parameterized_container_type() {
+    assert!(Array::new(vec![1_i64, 2_i64])
+        .try_cast::<Array<f32>>()
+        .is_err());
+}
+
+#[test]
+fn test_downcast_failure() {
+    let delete_counter = Arc::new(AtomicU32::new(0));
+    let base = new_base(1, delete_counter.clone());
+    let err = expect_err(base.try_cast::<TestDerived>());
+    assert!(err.message().contains("testing.TestObjectBase"));
+    assert!(err.message().contains("testing.TestObjectDerived"));
+    // try_cast consumes the value even when the cast fails
+    assert_eq!(delete_counter.load(Ordering::Relaxed), 1);
+}
+
+#[test]
+fn test_try_cast_from_any_view_preserves_runtime_subtype() {
+    let delete_counter = Arc::new(AtomicU32::new(0));
+    let base: TestBase = new_derived(3, 4, delete_counter.clone())
+        .try_cast()
+        .unwrap();
+    let raw = unsafe { Any::into_raw_ffi_any(Any::from(base)) };
+
+    // Array::get calls this method directly, without check_any_strict first.
+    let base = unsafe { TestBase::try_cast_from_any_view(&raw) }.unwrap();
+    let derived: TestDerived = base.try_cast().unwrap();
+    assert_eq!(derived.data.base.value, 3);
+    assert_eq!(derived.data.extra, 4);
+    assert_eq!(ObjectArc::strong_count(&derived.data), 2);
+    drop(derived);
+    assert_eq!(delete_counter.load(Ordering::Relaxed), 0);
+    drop(unsafe { Any::from_raw_ffi_any(raw) });
+    assert_eq!(delete_counter.load(Ordering::Relaxed), 1);
+}
+
+#[test]
+fn test_any_conversion_preserves_runtime_subtype() {
+    let delete_counter = Arc::new(AtomicU32::new(0));
+    let base: TestBase = new_derived(5, 6, delete_counter.clone())
+        .try_cast()
+        .unwrap();
+    let derived_from_view: TestDerived = 
AnyView::from(&base).try_into().unwrap();
+    assert_eq!(derived_from_view.data.extra, 6);
+    drop(derived_from_view);
+    let derived_from_any: TestDerived = Any::from(base).try_into().unwrap();
+    assert_eq!(derived_from_any.data.extra, 6);
+    drop(derived_from_any);
+    assert_eq!(delete_counter.load(Ordering::Relaxed), 1);
+}
diff --git a/rust/tvm-ffi/tests/test_object.rs 
b/rust/tvm-ffi/tests/test_object.rs
index 60378c2a..e4accba2 100644
--- a/rust/tvm-ffi/tests/test_object.rs
+++ b/rust/tvm-ffi/tests/test_object.rs
@@ -49,6 +49,7 @@ impl Drop for TestIntObj {
 
 unsafe impl ObjectCore for TestIntObj {
     const TYPE_KEY: &'static str = Object::TYPE_KEY;
+    const TYPE_DEPTH: i32 = Object::TYPE_DEPTH;
     #[inline]
     fn type_index() -> i32 {
         Object::type_index()

Reply via email to