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()