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 fc6835de [Rust] NativeFunction typed hooks, Display through the 
runtime printer, reflection::type_key, EnumDef size check (#954)
fc6835de is described below

commit fc6835de6da9876841448337f55a5abc4e791888
Author: Tianqi Chen <[email protected]>
AuthorDate: Fri Nov 6 22:23:28 2026 -0500

    [Rust] NativeFunction typed hooks, Display through the runtime printer, 
reflection::type_key, EnumDef size check (#954)
    
    Registries hold typed hooks (operator validators, type inference, type
    attributes) as either an opaque pointer to a native C ABI function or a
    packed `Function` registered in its place.
    `tvm_ffi::reflection::NativeFunction<Args, R>` is such a hook.
    
    - Reading: `unsafe fn from_any(Any)`. A native function carries its
    signature only by declaration, so the caller vouches that it takes
    `Args` and returns `R`, as its registering code declares. A value that
    is neither an opaque pointer nor a `Function` is a `TypeError`.
    - Calling is safe: `call` takes a tuple of borrowed objects, managed
    references or `Any` values and returns `Result<R>`; a native function is
    called directly with the argument addresses and a packed one through
    `call_packed`, and an error returned by either becomes `Err`.
    - Building: `From<F>` for a function item or a closure that captures
    nothing, `Fn(A0, ...) -> Result<R>`, generates the C ABI adapter
    (zero-sized and `Copy`, checked at compile time). As with C++ `noexcept`
    hooks, failure is reported through the `Result`; a panic aborts rather
    than unwind into C++.
    - Storing: `Any::from(hook)` gives the opaque pointer (or the
    `Function`) for a registry slot such as `TypeAttrDef::attr`; store it
    only in a slot whose callers use exactly this signature.
    
    `Any`, `AnyView`, `Array`, `List`, `Map` and every
    `#[derive(ObjectRef)]` type now implement `Display` as their runtime
    `repr` (`ffi.ReprPrint`), so values format with `{}`. `Debug` is
    unchanged. This is a source-breaking change: a downstream
    `#[derive(ObjectRef)]` type that already implements `Display` must add
    `#[object_ref(skip_display)]` to keep its own implementation, as `Error`
    does.
    
    `reflection::type_key(type_index)` returns the registered type key of an
    index, or `None` when no type has it.
    
    `reflection::field_default::<T>(name)` returns the default value `T`
    registers for its own field `name`, or `None` when the field is missing,
    has no default, or its default comes from a factory.
    
    `EnumDef::new` allocates each value as an `EnumObj`; it now also checks
    the registered object size of the enum type, so an opaque Rust mirror of
    a type whose objects have fields beyond `EnumObj` is a `TypeError`
    instead of an undersized allocation.
---
 rust/tvm-ffi-macros/src/object_macros.rs       |  17 +-
 rust/tvm-ffi/src/any.rs                        |  33 +++
 rust/tvm-ffi/src/collections/array.rs          |   7 +
 rust/tvm-ffi/src/collections/list.rs           |   7 +
 rust/tvm-ffi/src/collections/map.rs            |   7 +
 rust/tvm-ffi/src/derive.rs                     |   4 +-
 rust/tvm-ffi/src/error.rs                      |   2 +-
 rust/tvm-ffi/src/object.rs                     |  10 +-
 rust/tvm-ffi/src/reflection.rs                 |   9 +-
 rust/tvm-ffi/src/reflection/accessor.rs        |  89 +++++-
 rust/tvm-ffi/src/reflection/enum_def.rs        |  26 +-
 rust/tvm-ffi/src/reflection/native_function.rs | 359 +++++++++++++++++++++++++
 rust/tvm-ffi/src/type_traits.rs                |  14 +
 rust/tvm-ffi/tests/test_cast.rs                |  13 +
 rust/tvm-ffi/tests/test_object_reflection.rs   |  51 +++-
 15 files changed, 629 insertions(+), 19 deletions(-)

diff --git a/rust/tvm-ffi-macros/src/object_macros.rs 
b/rust/tvm-ffi-macros/src/object_macros.rs
index 2a7cdacf..8911e4c9 100644
--- a/rust/tvm-ffi-macros/src/object_macros.rs
+++ b/rust/tvm-ffi-macros/src/object_macros.rs
@@ -651,6 +651,17 @@ pub(crate) fn expand_object_ref(
             }
         });
     }
+    // The runtime `repr`, as `AnyView` formats it.
+    if !options.skip_display {
+        expanded.extend(quote! {
+            impl ::core::fmt::Display for #struct_name {
+                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> 
::core::fmt::Result {
+                    let object = 
&**#tvm_ffi_crate::object::ObjectRefCore::data(self);
+                    
::core::fmt::Display::fmt(&#tvm_ffi_crate::AnyView::from_object(object), f)
+                }
+            }
+        });
+    }
     // `ObjectRef` itself skips these: `any.rs` implements them for it.
     if struct_name != "ObjectRef" {
         expanded.extend(quote! {
@@ -705,6 +716,7 @@ struct ObjectRefOptions {
     parent: Option<syn::Type>,
     skip_deref: bool,
     skip_debug: bool,
+    skip_display: bool,
     skip_any_compatible: bool,
 }
 
@@ -748,13 +760,16 @@ fn object_ref_options(derive_input: &DeriveInput) -> 
ObjectRefOptions {
                 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_display" => {
+                    options.skip_display = 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 = ...`"
+                     `skip_debug`, `skip_display`, `skip_any_compatible` or 
`crate = ...`"
                 ),
             }
         }
diff --git a/rust/tvm-ffi/src/any.rs b/rust/tvm-ffi/src/any.rs
index 35afcd28..ac30bea5 100644
--- a/rust/tvm-ffi/src/any.rs
+++ b/rust/tvm-ffi/src/any.rs
@@ -393,6 +393,39 @@ impl<'a> From<&'a Any> for AnyView<'a> {
     }
 }
 
+/// Formats the value as its `repr`, the text the runtime printer gives in
+/// every language, such as `(1, 2)` for an array or `"hi"` for a string.
+/// Object references format the same way.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::{AnyView, Array};
+///
+/// let xs = Array::from_vec(vec![1_i64, 2]);
+/// assert_eq!(AnyView::from(&xs).to_string(), "(1, 2)");
+/// assert_eq!(xs.to_string(), "(1, 2)");
+/// ```
+impl std::fmt::Display for AnyView<'_> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let repr = crate::cached_global_func!("ffi.ReprPrint")
+            .call_packed(&[*self])
+            .and_then(crate::String::try_from);
+        match repr {
+            Ok(text) => f.write_str(text.as_str()),
+            // Formatting cannot fail, so a printer error falls back to the 
type key.
+            Err(_) => 
f.write_str(&object::type_key_of_index(self.type_index())),
+        }
+    }
+}
+
+/// Formats the value as its `repr`; see [`AnyView`].
+impl std::fmt::Display for Any {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(&AnyView::from(self), f)
+    }
+}
+
 /// Whether a `TVMFFIAny` cell owns everything it holds, so its owning form is 
a
 /// bitwise copy.
 #[inline]
diff --git a/rust/tvm-ffi/src/collections/array.rs 
b/rust/tvm-ffi/src/collections/array.rs
index 344ebeeb..ddadee64 100644
--- a/rust/tvm-ffi/src/collections/array.rs
+++ b/rust/tvm-ffi/src/collections/array.rs
@@ -124,6 +124,13 @@ impl<T: ContainerElement + Clone> Debug for Array<T> {
     }
 }
 
+/// Formats the array as its `repr`; see [`AnyView`].
+impl<T: ContainerElement + Clone> std::fmt::Display for Array<T> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        
std::fmt::Display::fmt(&AnyView::from_object(&**ObjectRefCore::data(self)), f)
+    }
+}
+
 impl<T: ContainerElement + Clone> Default for Array<T> {
     fn default() -> Self {
         Self::new()
diff --git a/rust/tvm-ffi/src/collections/list.rs 
b/rust/tvm-ffi/src/collections/list.rs
index c65a2ebc..e1650779 100644
--- a/rust/tvm-ffi/src/collections/list.rs
+++ b/rust/tvm-ffi/src/collections/list.rs
@@ -552,6 +552,13 @@ impl<T> Debug for List<T> {
     }
 }
 
+/// Formats the list as its `repr`; see [`AnyView`].
+impl<T: ContainerElement> std::fmt::Display for List<T> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        
std::fmt::Display::fmt(&AnyView::from_object(&**ObjectRefCore::data(self)), f)
+    }
+}
+
 impl<T: ContainerElement> FromIterator<T> for List<T> {
     fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
         let items: Vec<T> = iter.into_iter().collect();
diff --git a/rust/tvm-ffi/src/collections/map.rs 
b/rust/tvm-ffi/src/collections/map.rs
index 2da14c0d..94278fdb 100644
--- a/rust/tvm-ffi/src/collections/map.rs
+++ b/rust/tvm-ffi/src/collections/map.rs
@@ -400,6 +400,13 @@ where
     }
 }
 
+/// Formats the map as its `repr`; see [`AnyView`].
+impl<K: ContainerElement, V: ContainerElement> std::fmt::Display for Map<K, V> 
{
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        
std::fmt::Display::fmt(&AnyView::from_object(&**ObjectRefCore::data(self)), f)
+    }
+}
+
 impl<K, V> FromIterator<(K, V)> for Map<K, V>
 where
     K: ContainerElement,
diff --git a/rust/tvm-ffi/src/derive.rs b/rust/tvm-ffi/src/derive.rs
index 7f89db37..38a837ea 100644
--- a/rust/tvm-ffi/src/derive.rs
+++ b/rust/tvm-ffi/src/derive.rs
@@ -28,7 +28,8 @@ pub use tvm_ffi_macros::Object;
 /// [`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
+/// `Deref<Target = FooObj>` for field reads, `Debug`, `Display` (the runtime
+/// `repr`, as [`AnyView`](crate::AnyView) formats it), and a private
 /// `fn from_object(object: FooObj) -> Self` for the type's constructors.
 ///
 /// # Options
@@ -41,6 +42,7 @@ pub use tvm_ffi_macros::Object;
 ///   `unsafe_impl_object_ref_upcast!`.
 /// * `skip_deref` - No `Deref`, for a reference whose object is internal.
 /// * `skip_debug` - `Debug` is written by hand.
+/// * `skip_display` - `Display` 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
diff --git a/rust/tvm-ffi/src/error.rs b/rust/tvm-ffi/src/error.rs
index 25936be2..48f6cf58 100644
--- a/rust/tvm-ffi/src/error.rs
+++ b/rust/tvm-ffi/src/error.rs
@@ -66,7 +66,7 @@ pub struct ErrorObj {
 /// An `Error` is `Send + Sync`, so a `Result` can cross threads.
 #[repr(C)]
 #[derive(Clone, ObjectRef)]
-#[object_ref(skip_deref, skip_debug)]
+#[object_ref(skip_deref, skip_debug, skip_display)]
 pub struct Error {
     data: ObjectArc<ErrorObj>,
 }
diff --git a/rust/tvm-ffi/src/object.rs b/rust/tvm-ffi/src/object.rs
index 91d8f3e6..37f9fd0c 100644
--- a/rust/tvm-ffi/src/object.rs
+++ b/rust/tvm-ffi/src/object.rs
@@ -897,13 +897,9 @@ pub(crate) fn into_root_object_ref<S: 
ObjectRefCore>(value: S) -> ObjectRef {
 
 /// The registered type key of `type_index`, or `<type_index N>` when it has 
none.
 pub(crate) fn type_key_of_index(type_index: i32) -> String {
-    unsafe {
-        let info = TVMFFIGetTypeInfo(type_index);
-        if info.is_null() {
-            format!("<type_index {type_index}>")
-        } else {
-            (*info).type_key.as_str().to_string()
-        }
+    match crate::reflection::type_key(type_index) {
+        Some(key) => key.to_string(),
+        None => format!("<type_index {type_index}>"),
     }
 }
 
diff --git a/rust/tvm-ffi/src/reflection.rs b/rust/tvm-ffi/src/reflection.rs
index 1483a577..10a95a9a 100644
--- a/rust/tvm-ffi/src/reflection.rs
+++ b/rust/tvm-ffi/src/reflection.rs
@@ -19,16 +19,21 @@
 
 //! Object reflection: look up the methods, fields and type attributes of
 //! registered types; register Rust object types with [`ObjectDef`], type
-//! attributes with [`TypeAttrDef`] and enum values with [`EnumDef`].
+//! attributes with [`TypeAttrDef`] and enum values with [`EnumDef`]; call the
+//! typed hooks that attributes hold with [`NativeFunction`].
 
 mod access_path;
 mod accessor;
 pub(crate) mod enum_def;
+mod native_function;
 mod registry;
 mod unsafe_init;
 pub use access_path::{AccessKind, AccessPath, AccessPathObj, AccessStep, 
AccessStepObj};
-pub use accessor::{get_method, get_method_by_index, FieldGetter, 
TypeAttrColumn};
+pub use accessor::{
+    field_default, get_method, get_method_by_index, type_key, FieldGetter, 
TypeAttrColumn,
+};
 pub use enum_def::{EnumAttrMap, EnumDef};
+pub use native_function::{NativeArgs, NativeFunction};
 #[doc(hidden)]
 pub use registry::register_object;
 pub use registry::{FieldFlags, ObjectDef, SEqHashKind, ShallowCopy, 
TypeAttrDef};
diff --git a/rust/tvm-ffi/src/reflection/accessor.rs 
b/rust/tvm-ffi/src/reflection/accessor.rs
index fe72c130..2b1f111b 100644
--- a/rust/tvm-ffi/src/reflection/accessor.rs
+++ b/rust/tvm-ffi/src/reflection/accessor.rs
@@ -21,13 +21,15 @@
 
 use std::ffi::c_void;
 use std::ptr::NonNull;
+use std::sync::{PoisonError, RwLock};
 
 use crate::tvm_ffi_sys::{
-    TVMFFIAny, TVMFFIByteArray, TVMFFIFieldGetter, TVMFFIFieldInfo, 
TVMFFIGetTypeAttrColumn,
-    TVMFFIGetTypeInfo, TVMFFIObject, TVMFFITypeAttrColumn, TVMFFITypeIndex, 
TVMFFITypeKeyToIndex,
+    TVMFFIAny, TVMFFIByteArray, TVMFFIFieldFlagBitMask, TVMFFIFieldGetter, 
TVMFFIFieldInfo,
+    TVMFFIGetTypeAttrColumn, TVMFFIGetTypeInfo, TVMFFIObject, 
TVMFFITypeAttrColumn,
+    TVMFFITypeIndex, TVMFFITypeInfo, TVMFFITypeKeyToIndex,
 };
 use crate::type_traits::AnyCompatible;
-use crate::{Any, AnyView, Error, Function, ObjectCore, Result, TYPE_ERROR};
+use crate::{Any, AnyView, Array, Error, Function, ObjectCore, Result, 
TYPE_ERROR};
 
 /// A registry-owned type-attribute column indexed by runtime type.
 ///
@@ -82,6 +84,87 @@ impl TypeAttrColumn {
     }
 }
 
+/// The type key registered for `type_index`, such as `"ffi.Array"`, or `None`
+/// if no type has that index.
+///
+/// ```
+/// use tvm_ffi::collections::array::ArrayObj;
+/// use tvm_ffi::{reflection, ObjectCore};
+///
+/// assert_eq!(reflection::type_key(ArrayObj::type_index()), 
Some("ffi.Array"));
+/// assert_eq!(reflection::type_key(-1), None);
+/// ```
+pub fn type_key(type_index: i32) -> Option<&'static str> {
+    if !is_registered(type_index) {
+        return None;
+    }
+    // SAFETY: the index is registered, and type information lives as long as
+    // the process.
+    let info: &'static TVMFFITypeInfo = unsafe { 
&*TVMFFIGetTypeInfo(type_index) };
+    Some(info.type_key.as_str())
+}
+
+/// The default value `T` registers for its field `name`, or `None` if `T`
+/// registers no such field, the field has no default, or its default comes
+/// from a factory.
+///
+/// Only fields `T` registers itself are searched, not those of its ancestors.
+pub fn field_default<T: ObjectCore>(name: &str) -> Option<Any> {
+    // SAFETY: `ObjectCore` types are registered, and type information lives
+    // as long as the process.
+    let info: &'static TVMFFITypeInfo = unsafe { 
&*TVMFFIGetTypeInfo(T::type_index()) };
+    if info.fields.is_null() {
+        return None;
+    }
+    // SAFETY: a registered type lists `num_fields` initialized fields.
+    let fields = unsafe { std::slice::from_raw_parts(info.fields, 
info.num_fields as usize) };
+    let field = fields.iter().find(|field| field.name.as_str() == name)?;
+    let has_default = 
TVMFFIFieldFlagBitMask::kTVMFFIFieldFlagBitMaskHasDefault as i64;
+    let from_factory = 
TVMFFIFieldFlagBitMask::kTVMFFIFieldFlagBitMaskDefaultFromFactory as i64;
+    if field.flags & has_default == 0 || field.flags & from_factory != 0 {
+        return None;
+    }
+    // SAFETY: the registry keeps the default alive; the view only borrows it
+    // for the copy.
+    Some(Any::from(unsafe {
+        AnyView::from_raw_ffi_any(field.default_value_or_factory)
+    }))
+}
+
+/// Whether a type is registered with index `type_index`. The registry aborts
+/// the process on a lookup of any other index, so the registered indices are
+/// listed once and again whenever an index is missing, as types register over
+/// time.
+fn is_registered(type_index: i32) -> bool {
+    static REGISTERED: RwLock<Vec<bool>> = RwLock::new(Vec::new());
+    let Ok(index) = usize::try_from(type_index) else {
+        return false;
+    };
+    let read = REGISTERED.read().unwrap_or_else(PoisonError::into_inner);
+    if read.get(index).copied().unwrap_or(false) {
+        return true;
+    }
+    drop(read);
+    let keys = crate::cached_global_func!("ffi.GetRegisteredTypeKeys")
+        .call_tuple(())
+        .and_then(Array::<crate::String>::try_from);
+    let mut registered = 
REGISTERED.write().unwrap_or_else(PoisonError::into_inner);
+    for key in keys.iter().flat_map(|keys| keys.iter()) {
+        let mut key_index = 0;
+        // SAFETY: the byte array borrows `key` for the call.
+        let found = unsafe {
+            TVMFFITypeKeyToIndex(&TVMFFIByteArray::from_str(key.as_str()), 
&mut key_index) == 0
+        };
+        if let (true, Ok(key_index)) = (found, usize::try_from(key_index)) {
+            if registered.len() <= key_index {
+                registered.resize(key_index + 1, false);
+            }
+            registered[key_index] = true;
+        }
+    }
+    registered.get(index).copied().unwrap_or(false)
+}
+
 /// Look up a reflected method of a type by type key and method name.
 ///
 /// Same as [`get_method_by_index`], but resolves `type_key` to a type index
diff --git a/rust/tvm-ffi/src/reflection/enum_def.rs 
b/rust/tvm-ffi/src/reflection/enum_def.rs
index 9b39d056..d67049ab 100644
--- a/rust/tvm-ffi/src/reflection/enum_def.rs
+++ b/rust/tvm-ffi/src/reflection/enum_def.rs
@@ -85,8 +85,10 @@ impl<E: EnumRef> EnumDef<E> {
     /// # Errors
     ///
     /// Returns a `ValueError` if `E` already has a value named `name`, or a
-    /// `TypeError` if `E` is not an enum type or is an integer enum
-    /// (`ffi.IntEnum`), whose values name their own indices.
+    /// `TypeError` if `E` is not an enum type, is an integer enum
+    /// (`ffi.IntEnum`), whose values name their own indices, or registers an
+    /// object size other than that of [`EnumObj`]: an enum type whose objects
+    /// have fields of their own.
     pub fn new(name: &str) -> Result<Self> {
         const {
             assert!(
@@ -104,6 +106,24 @@ impl<E: EnumRef> EnumDef<E> {
                 "",
             ));
         }
+        // The assert above sees only the Rust mirror, which may be opaque: a
+        // registered object size must be exactly that of the `EnumObj`
+        // allocated below. A type registered without one (such as by
+        // `#[derive(Object)]`) is laid out as its Rust struct.
+        // SAFETY: the type is registered, and its information is static.
+        let metadata = unsafe { (*TVMFFIGetTypeInfo(type_index)).metadata };
+        // SAFETY: registered metadata is static.
+        let total_size = unsafe { metadata.as_ref() }.map_or(0, |metadata| 
metadata.total_size);
+        if total_size != 0 && total_size as usize != size_of::<EnumObj>() {
+            return Err(Error::new(
+                TYPE_ERROR,
+                &format!(
+                    "`{type_key}` objects have fields beyond `ffi.Enum`, so 
Rust cannot \
+                     register its values"
+                ),
+                "",
+            ));
+        }
         let state = ensure_state::<E>()?;
         let index = state.entries.len() as i64;
         let key = String::from(name);
@@ -118,7 +138,7 @@ impl<E: EnumRef> EnumDef<E> {
             ));
         }
         let object = ObjectArc::into_raw(ObjectArc::new(EnumObj::new(index, 
name)));
-        // SAFETY: `E`'s object is an `EnumObj` with `E`'s type index (asserted
+        // SAFETY: `E`'s object is an `EnumObj` with `E`'s type index (checked
         // above), and the object is not shared yet. The deleter stays that of
         // `EnumObj`, which drops the same fields.
         let value = unsafe {
diff --git a/rust/tvm-ffi/src/reflection/native_function.rs 
b/rust/tvm-ffi/src/reflection/native_function.rs
new file mode 100644
index 00000000..baca00ba
--- /dev/null
+++ b/rust/tvm-ffi/src/reflection/native_function.rs
@@ -0,0 +1,359 @@
+/*
+ * 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.
+ */
+//! Typed hooks that registries hold: native functions with a packed fallback.
+
+use std::ffi::c_void;
+use std::marker::PhantomData;
+
+use crate::object::type_key_of_index;
+use crate::tvm_ffi_sys::{TVMFFIAny, TVMFFITypeIndex};
+use crate::type_traits::DowncastRefFromObject;
+use crate::{Any, AnyView, Error, Function, Result, TYPE_ERROR};
+
+/// A typed hook that a type attribute, an enum attribute or another registry
+/// slot holds: a native function, called directly through the C ABI, or a
+/// packed [`Function`] registered in its place.
+///
+/// `Args` is the tuple of argument types and `R` the result type, as in
+/// `Fn(Args) -> R`: a hook that checks a pair is a
+/// `NativeFunction<(&MyPairObj,)>`, and one that measures it a
+/// `NativeFunction<(&MyPairObj,), i64>`. An argument is a borrowed object
+/// (`&MyPairObj`), a borrowed managed reference (`&Array<T>`) or `&Any`, and a
+/// native function receives its address, which C++ declares as
+/// `const MyPairObj*`, `const Array<T>&` or `const Any&`.
+///
+/// A native function carries no signature at run time, so the slot declares
+/// it. Reading a hook with [`from_any`](Self::from_any) is `unsafe`, the
+/// caller vouching for the signature, and then calling it is safe. Building a
+/// hook from a Rust function with `into()` is safe; store it, through
+/// `Any::from`, only in a slot whose callers use exactly this signature.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::derive::Object;
+/// use tvm_ffi::reflection::{NativeFunction, TypeAttrColumn, TypeAttrDef};
+/// use tvm_ffi::{Object, ObjectArc, ObjectCore, Result};
+///
+/// #[repr(C)]
+/// #[derive(Object)]
+/// #[type_key = "my_ext.MyPair"]
+/// #[type_register]
+/// pub struct MyPairObj {
+///     base_: Object,
+///     first: i64,
+///     second: i64,
+/// }
+///
+/// fn sum(pair: &MyPairObj) -> Result<i64> {
+///     Ok(pair.first + pair.second)
+/// }
+///
+/// let hook: NativeFunction<(&MyPairObj,), i64> = sum.into();
+/// TypeAttrDef::<MyPairObj>::new().attr("my_ext.sum", hook)?;
+///
+/// let stored = 
TypeAttrColumn::new("my_ext.sum").unwrap().get(MyPairObj::type_index()).unwrap();
+/// // SAFETY: `my_ext.sum` holds `i64(const MyPairObj*)` hooks.
+/// let sum: NativeFunction<(&MyPairObj,), i64> = unsafe { 
NativeFunction::from_any(stored)? };
+/// let pair = ObjectArc::new(MyPairObj { base_: Object::new(), first: 1, 
second: 2 });
+/// assert_eq!(sum.call((&*pair,))?, 3);
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
+pub struct NativeFunction<Args, R = ()> {
+    // A non-null opaque pointer to the native function, or a `Function`.
+    data: Any,
+    // Covariant in `Args`: a hook that takes `&'a T` takes any shorter borrow.
+    signature: PhantomData<fn() -> (Args, R)>,
+}
+
+impl<Args: NativeArgs, R: TryFrom<Any>> NativeFunction<Args, R> {
+    /// Call the hook with `args`.
+    ///
+    /// # Arguments
+    ///
+    /// * `args` - The arguments, as a tuple such as `(&array,)`.
+    ///
+    /// # Returns
+    ///
+    /// The hook's result.
+    ///
+    /// # Errors
+    ///
+    /// Returns the error the hook raises, or a `TypeError` when a packed
+    /// function returns a value that does not convert to `R`.
+    ///
+    /// # Panics
+    ///
+    /// Panics if an object argument is not managed, such as an object built
+    /// on the stack: the hook may retain it.
+    ///
+    /// See also [`NativeFunction`].
+    pub fn call(&self, args: Args) -> Result<R> {
+        let raw = self.data.as_raw_ffi_any();
+        let result = if raw.type_index == TVMFFITypeIndex::kTVMFFIOpaquePtr as 
i32 {
+            // SAFETY: `from_any`'s caller vouches that the native function
+            // takes `Args` and returns `R`.
+            unsafe { args.call_native(raw.data_union.v_ptr) }
+        } else {
+            let function = self.data.downcast_ref::<Function>();
+            args.call_packed(function.expect("a hook is a native or packed 
function"))?
+        };
+        // A native function returns its error as its result.
+        if result.type_index() == TVMFFITypeIndex::kTVMFFIError as i32 {
+            return Err(result.try_into()?);
+        }
+        R::try_from(result)
+            .map_err(|_| Error::new(TYPE_ERROR, "Packed hook result type 
mismatch", ""))
+    }
+}
+
+impl<Args, R> Clone for NativeFunction<Args, R> {
+    fn clone(&self) -> Self {
+        Self {
+            data: self.data.clone(),
+            signature: PhantomData,
+        }
+    }
+}
+
+impl<Args, R> NativeFunction<Args, R> {
+    /// Read the hook that `value` holds: a non-null opaque pointer to a native
+    /// function, or a packed [`Function`].
+    ///
+    /// # Arguments
+    ///
+    /// * `value` - The hook, as its registry slot holds it.
+    ///
+    /// # Returns
+    ///
+    /// The hook, typed with `Args` and `R`.
+    ///
+    /// # Errors
+    ///
+    /// Returns a `TypeError` if `value` is neither a non-null opaque pointer
+    /// nor a [`Function`].
+    ///
+    /// # Safety
+    ///
+    /// A native function in `value` must take `Args` and return `R`, as the
+    /// code that registers it in the slot declares. A packed [`Function`] is
+    /// called through checked conversions and has no such requirement.
+    ///
+    /// See also [`NativeFunction`].
+    pub unsafe fn from_any(value: Any) -> Result<Self> {
+        let raw = value.as_raw_ffi_any();
+        // SAFETY: `v_ptr` is the payload of an opaque pointer.
+        let native = raw.type_index == TVMFFITypeIndex::kTVMFFIOpaquePtr as i32
+            && !unsafe { raw.data_union.v_ptr }.is_null();
+        if !native && raw.type_index != TVMFFITypeIndex::kTVMFFIFunction as 
i32 {
+            let message = format!(
+                "Cannot convert from type `{}` to `NativeFunction`",
+                type_key_of_index(raw.type_index)
+            );
+            return Err(Error::new(TYPE_ERROR, &message, ""));
+        }
+        Ok(Self {
+            data: value,
+            signature: PhantomData,
+        })
+    }
+}
+
+/// Stores the hook in a registry slot, such as with
+/// [`TypeAttrDef::attr`](super::TypeAttrDef::attr): the opaque pointer of a
+/// native function, or the packed [`Function`]. Store it only in a slot whose
+/// callers call it with exactly `Args` and `R`, which native callers do not
+/// check.
+impl<Args, R> From<NativeFunction<Args, R>> for Any {
+    fn from(hook: NativeFunction<Args, R>) -> Self {
+        hook.data
+    }
+}
+
+/// The argument tuples a [`NativeFunction`] takes: up to eight borrowed
+/// objects, managed references or [`Any`] values.
+///
+/// This trait is sealed.
+pub trait NativeArgs: sealed::NativeArgs {}
+
+impl<T: sealed::NativeArgs> NativeArgs for T {}
+
+mod sealed {
+    use super::*;
+
+    pub trait NativeArgs {
+        /// Call the native function `function` with these arguments.
+        ///
+        /// # Safety
+        ///
+        /// `function` is an `extern "C" fn(Args...) -> TVMFFIAny`.
+        unsafe fn call_native(self, function: *mut c_void) -> Any;
+
+        fn call_packed(self, function: &Function) -> Result<Any>;
+    }
+
+    /// An argument passed to a native function as itself: a pointer.
+    pub trait NativeArg: Copy {
+        /// The pointee, which a native function receives the address of.
+        type Target: 'static;
+
+        /// The argument as a packed function receives it.
+        fn to_any_view(&self) -> AnyView<'_>;
+    }
+
+    impl<T: DowncastRefFromObject + 'static> NativeArg for &T {
+        type Target = T;
+
+        #[inline(always)]
+        fn to_any_view(&self) -> AnyView<'_> {
+            // Panics on an unmanaged object.
+            AnyView::from_object(self.as_object())
+        }
+    }
+
+    impl NativeArg for &Any {
+        type Target = Any;
+
+        #[inline(always)]
+        fn to_any_view(&self) -> AnyView<'_> {
+            AnyView::from(*self)
+        }
+    }
+
+    /// The only value of the zero-sized `F`, for the adapter, which cannot
+    /// receive it.
+    ///
+    /// # Safety
+    ///
+    /// A value of `F` exists. `F` is `Copy`, so it has no drop and may be
+    /// duplicated: a zero-sized value that must stay unique, such as a token,
+    /// is not `Copy`.
+    #[inline(always)]
+    unsafe fn conjure<F: Copy>() -> F {
+        const {
+            assert!(
+                size_of::<F>() == 0,
+                "a native hook is built from a function or a closure that 
captures nothing"
+            )
+        };
+        // SAFETY: a zero-sized type has no bytes, so any value is initialized,
+        // and duplicating it is a copy (see above).
+        unsafe { std::mem::zeroed() }
+    }
+
+    /// The result of a native function as its caller owns it: the value, or
+    /// the error.
+    #[inline(always)]
+    fn into_raw<R: Into<Any>>(result: Result<R>) -> TVMFFIAny {
+        Any::into_raw_ffi_any(match result {
+            Ok(value) => value.into(),
+            Err(error) => Any::from(error),
+        })
+    }
+
+    macro_rules! impl_native_args {
+        ($($arg:ident $index:tt),*) => {
+            impl<$($arg: NativeArg),*> NativeArgs for ($($arg,)*) {
+                #[inline(always)]
+                unsafe fn call_native(self, function: *mut c_void) -> Any {
+                    // The function may retain an object argument, which must 
be managed.
+                    $(let _ = self.$index.to_any_view();)*
+                    // SAFETY: the caller passes a function of this signature,
+                    // which returns an owned value.
+                    unsafe {
+                        let function = std::mem::transmute::<
+                            *mut c_void,
+                            unsafe extern "C" fn($($arg),*) -> TVMFFIAny,
+                        >(function);
+                        Any::from_raw_ffi_any(function($(self.$index),*))
+                    }
+                }
+
+                #[inline(always)]
+                fn call_packed(self, function: &Function) -> Result<Any> {
+                    function.call_packed(&[$(self.$index.to_any_view()),*])
+                }
+            }
+        };
+    }
+
+    macro_rules! impl_native_fn {
+        ($($arg:ident $raw:ident),*) => {
+            /// Build a native hook from a function item or a closure that
+            /// captures nothing, with the C ABI adapter that native callers
+            /// call.
+            ///
+            /// The function takes the arguments as `call` passes them and
+            /// returns `Result<R>`; its error is the error the hook raises. A
+            /// hook reports failure through its `Result` and must not panic: a
+            /// panic aborts the process. A closure that captures a value does
+            /// not compile.
+            ///
+            /// See also [`NativeFunction`].
+            // The function takes borrows of any lifetime, so it cannot keep an
+            // argument past the call.
+            impl<F, $($arg: NativeArg,)* R: Into<Any>> From<F> for 
NativeFunction<($($arg,)*), R>
+            where
+                F: for<'a> Fn($(&'a $arg::Target),*) -> Result<R> + Copy + 
'static,
+            {
+                fn from(_function: F) -> Self {
+                    extern "C" fn adapter<F, $($arg: NativeArg,)* R: 
Into<Any>>(
+                        $($raw: *const $arg::Target),*
+                    ) -> TVMFFIAny
+                    where
+                        F: for<'a> Fn($(&'a $arg::Target),*) -> Result<R> + 
Copy + 'static,
+                    {
+                        // SAFETY: `from` received a value of `F`.
+                        let function = unsafe { conjure::<F>() };
+                        // SAFETY: a native caller passes the address of a live
+                        // argument for the duration of the call, or null.
+                        $(let Some($raw) = (unsafe { $raw.as_ref() }) else {
+                            let message = "A native hook argument is null";
+                            return into_raw::<R>(Err(Error::new(TYPE_ERROR, 
message, "")));
+                        };)*
+                        // A panic aborts here rather than unwind into the 
caller.
+                        into_raw(function($($raw),*))
+                    }
+                    let adapter = adapter::<F, $($arg,)* R> as *mut c_void;
+                    Self { data: Any::from(adapter), signature: PhantomData }
+                }
+            }
+        };
+    }
+
+    impl_native_fn!();
+    impl_native_fn!(A0 a0);
+    impl_native_fn!(A0 a0, A1 a1);
+    impl_native_fn!(A0 a0, A1 a1, A2 a2);
+    impl_native_fn!(A0 a0, A1 a1, A2 a2, A3 a3);
+    impl_native_fn!(A0 a0, A1 a1, A2 a2, A3 a3, A4 a4);
+    impl_native_fn!(A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5);
+    impl_native_fn!(A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6);
+    impl_native_fn!(A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7);
+
+    impl_native_args!();
+    impl_native_args!(A0 0);
+    impl_native_args!(A0 0, A1 1);
+    impl_native_args!(A0 0, A1 1, A2 2);
+    impl_native_args!(A0 0, A1 1, A2 2, A3 3);
+    impl_native_args!(A0 0, A1 1, A2 2, A3 3, A4 4);
+    impl_native_args!(A0 0, A1 1, A2 2, A3 3, A4 4, A5 5);
+    impl_native_args!(A0 0, A1 1, A2 2, A3 3, A4 4, A5 5, A6 6);
+    impl_native_args!(A0 0, A1 1, A2 2, A3 3, A4 4, A5 5, A6 6, A7 7);
+}
diff --git a/rust/tvm-ffi/src/type_traits.rs b/rust/tvm-ffi/src/type_traits.rs
index 30ebf504..4495a103 100644
--- a/rust/tvm-ffi/src/type_traits.rs
+++ b/rust/tvm-ffi/src/type_traits.rs
@@ -440,6 +440,7 @@ pub unsafe fn downcast_ref_from_any_in_place<T: 
RefFromAnyUnchecked>(
 ///
 /// # Safety
 ///
+/// `Self` must be an object type or exactly one pointer to a live object.
 /// `downcast_ref_from_object` must return `Some` only when the object at
 /// `*place` is a `Self`, with a reference that stays valid while `place` is
 /// borrowed.
@@ -451,10 +452,23 @@ pub unsafe trait DowncastRefFromObject {
     ///
     /// `place` must hold a pointer to a live object.
     unsafe fn downcast_ref_from_object(place: &*mut TVMFFIObject) -> 
Option<&Self>;
+
+    /// The object that `self` is or refers to.
+    #[inline(always)]
+    fn as_object(&self) -> &crate::object::Object {
+        // SAFETY: a reference type is one pointer to a live object (trait 
contract).
+        unsafe { &**std::ptr::from_ref(self).cast::<*const 
crate::object::Object>() }
+    }
 }
 
 // An object type borrows the object itself.
 unsafe impl<T: crate::object::ObjectCore> DowncastRefFromObject for T {
+    #[inline(always)]
+    fn as_object(&self) -> &crate::object::Object {
+        // SAFETY: an object type starts with its object header.
+        unsafe { &*std::ptr::from_ref(self).cast::<crate::object::Object>() }
+    }
+
     #[inline(always)]
     unsafe fn downcast_ref_from_object(place: &*mut TVMFFIObject) -> 
Option<&Self> {
         let object = *place;
diff --git a/rust/tvm-ffi/tests/test_cast.rs b/rust/tvm-ffi/tests/test_cast.rs
index 228f3198..379b8d18 100644
--- a/rust/tvm-ffi/tests/test_cast.rs
+++ b/rust/tvm-ffi/tests/test_cast.rs
@@ -123,6 +123,19 @@ fn test_is_instance_of() {
     assert!(!is_instance_of::<Object>(TypeIndex::kTVMFFIInt as i32));
 }
 
+#[test]
+fn test_field_default() {
+    assert_eq!(unsafe { tvm_ffi_sys::TVMFFITestingDummyTarget() }, 0);
+    let v_i64 = reflection::field_default::<TestBaseObj>("v_i64").unwrap();
+    assert_eq!(i64::try_from(v_i64).unwrap(), 10);
+    let v_str = reflection::field_default::<TestBaseObj>("v_str").unwrap();
+    assert_eq!(String::try_from(v_str).unwrap().as_str(), "hello");
+    // no default, unknown, and inherited fields
+    assert!(reflection::field_default::<TestDerivedObj>("v_map").is_none());
+    assert!(reflection::field_default::<TestBaseObj>("missing").is_none());
+    assert!(reflection::field_default::<TestDerivedObj>("v_i64").is_none());
+}
+
 #[test]
 fn test_upcast_downcast_roundtrip() {
     let delete_counter = Arc::new(AtomicU32::new(0));
diff --git a/rust/tvm-ffi/tests/test_object_reflection.rs 
b/rust/tvm-ffi/tests/test_object_reflection.rs
index 6071039a..a7dbf137 100644
--- a/rust/tvm-ffi/tests/test_object_reflection.rs
+++ b/rust/tvm-ffi/tests/test_object_reflection.rs
@@ -17,8 +17,8 @@
  * under the License.
  */
 use tvm_ffi::derive::{Object, ObjectRef};
-use tvm_ffi::reflection::TypeAttrColumn;
 use tvm_ffi::reflection::{FieldFlags, FieldGetter, ObjectDef};
+use tvm_ffi::reflection::{NativeFunction, TypeAttrColumn};
 use tvm_ffi::*;
 use tvm_ffi_sys::{
     TVMFFIFieldFlagBitMask, TVMFFIFieldInfo, TVMFFIFieldSetter, 
TVMFFIGetTypeInfo,
@@ -581,3 +581,52 @@ impl DuplicateMethodObj {
 fn test_duplicate_method_names_fail_the_registration() {
     DuplicateMethodObj::type_index();
 }
+
+#[test]
+fn test_native_function() -> Result<()> {
+    // A big integer object registers a native `__any_equal__` hook.
+    let big = |value: i128| Any::from(BigInt::from(value));
+    let column = TypeAttrColumn::new("__any_equal__").expect("the column is 
registered");
+    let hook = column
+        .get(big(1 << 100).type_index())
+        .expect("the hook is registered");
+    // SAFETY: C++ registers `__any_equal__` as `bool(const Any&, const Any&)`.
+    let equal: NativeFunction<(&Any, &Any), bool> = unsafe { 
NativeFunction::from_any(hook)? };
+    assert!(equal.call((&big(1 << 100), &big(1 << 100)))?);
+    assert!(!equal.call((&big(1 << 100), &big(1 << 101)))?);
+    let error = equal
+        .call((&big(1 << 100), &Any::from(String::from("1"))))
+        .unwrap_err();
+    assert_eq!(error.kind(), TYPE_ERROR);
+
+    // A packed function in place of a native hook.
+    let get_x = Function::from_typed(|p: Point| -> Result<i64> { Ok(p.data.x) 
});
+    // SAFETY: a packed function is called through checked conversions.
+    let hook: NativeFunction<(&PointObj,), i64> =
+        unsafe { NativeFunction::from_any(get_x.clone().into())? };
+    let p = point(3, 0.5, "p");
+    assert_eq!(hook.call((&*p.data,))?, 3);
+    // SAFETY: as above.
+    let mismatch: NativeFunction<(&Point,), String> =
+        unsafe { NativeFunction::from_any(get_x.into())? };
+    let error = mismatch.call((&p,)).unwrap_err();
+    assert_eq!(error.message(), "Packed hook result type mismatch");
+
+    // A Rust closure as a native hook, whose error is the hook's.
+    let x_of = |p: &PointObj| -> Result<i64> {
+        match p.x {
+            0 => Err(Error::new(VALUE_ERROR, "zero", "")),
+            x => Ok(x),
+        }
+    };
+    let stored = Any::from(NativeFunction::<(&PointObj,), i64>::from(x_of));
+    // SAFETY: the hook was built with this signature.
+    let x_of: NativeFunction<(&PointObj,), i64> = unsafe { 
NativeFunction::from_any(stored)? };
+    assert_eq!(x_of.call((&*p.data,))?, 3);
+    let error = x_of.call((&*point(0, 0.5, "p").data,)).unwrap_err();
+    assert_eq!(error.kind(), VALUE_ERROR);
+
+    // SAFETY: an integer is rejected before any call.
+    assert!(unsafe { NativeFunction::<()>::from_any(Any::from(1_i64)) 
}.is_err());
+    Ok(())
+}

Reply via email to