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 744cf65c [Rust][DOCS] Add std-style examples to the core API docs
(#926)
744cf65c is described below
commit 744cf65c2d0186894ea23afcbafa9e1cfb943671
Author: Tianqi Chen <[email protected]>
AuthorDate: Wed Nov 4 19:44:54 2026 -0500
[Rust][DOCS] Add std-style examples to the core API docs (#926)
This PR adds std-style rustdoc to the core Rust APIs. The key top-level
APIs (types, entry functions and macros) carry runnable examples.
Second-level methods get a one-line summary, a short usage where it
helps, and a see-also link to the related top-level API.
- **Macros.** `#[dispatch]`, `match_any!`, `rs_object` and
`def_object_ref!` are documented on their `tvm_ffi` re-exports, so their
examples run as tvm-ffi doctests and the macro crate keeps no dependency
on tvm-ffi. The `dispatch` and `match_any!` examples follow the Rust
guide. The `rs_object` and `def_object_ref!` examples, previously
`ignore`, now compile and run.
- **Values.** `Any` and `AnyView` (with `try_as` and `downcast_ref`),
`Unchanged` and `UnchangedOr`, and `ObjectRefCast::try_cast`, the Rust
counterpart of C++ `ObjectRef::as<T>`.
- **Traversal.** The `structural_mutate` example shows a handler that
rewrites one field with `MutationView::field`. `MutationView`,
`InplaceMode`, the visitor and mutator views, and `Dispatcher` point to
the traversal entry points.
- **Functions, errors and containers.** `Function` (`from_typed`,
`from_packed`, `call_tuple`, `call_packed`, `get_global`,
`register_global_typed`), `Error` and `ErrorKind`, `bail!`, `ensure!`,
`attach_context!`, `Array`, `List`, `Map`, `String`, `Bytes`, `Shape`
and `Tensor`.
- **Macro paths.** The macros always emit `::tvm_ffi` paths, which
`extern crate self as tvm_ffi` resolves inside tvm-ffi, so the same
expansion works in the crate, its doctests and dependent crates.
`attach_context!` no longer needs `Error` in scope.
---
rust/tvm-ffi-macros/src/lib.rs | 160 +---------------
rust/tvm-ffi-macros/src/utils.rs | 10 +-
rust/tvm-ffi/src/any.rs | 161 ++++++++++++----
rust/tvm-ffi/src/collections/array.rs | 62 ++++++-
rust/tvm-ffi/src/collections/list.rs | 21 ++-
rust/tvm-ffi/src/collections/map.rs | 20 +-
rust/tvm-ffi/src/collections/shape.rs | 13 +-
rust/tvm-ffi/src/collections/tensor.rs | 15 +-
rust/tvm-ffi/src/error.rs | 122 ++++++++++--
rust/tvm-ffi/src/extra/dispatch.rs | 18 +-
rust/tvm-ffi/src/extra/structural_mutate.rs | 277 +++++++++++++++++++++++----
rust/tvm-ffi/src/extra/structural_visit.rs | 85 ++++++++-
rust/tvm-ffi/src/extra/unchanged.rs | 177 +++++++++++++++---
rust/tvm-ffi/src/function.rs | 278 ++++++++++++++++++++++------
rust/tvm-ffi/src/lib.rs | 221 +++++++++++++++++++++-
rust/tvm-ffi/src/macros.rs | 96 ++++++++--
rust/tvm-ffi/src/object.rs | 28 ++-
rust/tvm-ffi/src/string.rs | 29 ++-
18 files changed, 1406 insertions(+), 387 deletions(-)
diff --git a/rust/tvm-ffi-macros/src/lib.rs b/rust/tvm-ffi-macros/src/lib.rs
index 5acdbd98..d6533795 100644
--- a/rust/tvm-ffi-macros/src/lib.rs
+++ b/rust/tvm-ffi-macros/src/lib.rs
@@ -27,71 +27,16 @@ mod object_macros;
mod rs_object;
mod utils;
-/// Generate source-ordered runtime type dispatch from `prefix_*` handlers.
-///
-/// `#[dispatch(prefix, (Carrier, Arg, ...), Ret)]` declares the complete input
-/// tuple and result type. The first input is `AnyView<'_>` or
`MutationView<'_>`
-/// (including aliases); subsequent inputs are passed to matching handlers.
-/// The macro implements `Dispatcher<(Carrier, Arg, ...), Option<Ret>>` for the
-/// receiver reference (`&T` or `&mut T`). The inherent `.dispatch(value, ...)`
-/// method owns matching; `.dispatch_tuple((value, ...))` unpacks and forwards.
-/// Both methods always inline. A matched result is converted with `Into<Ret>`
-/// and wrapped in `Some`. When both the handler's return type and `Ret` are
-/// written as `Result<..>`, the success value is converted with `Into`
instead,
-/// so a `Result<UnchangedOr<Expr>>` or `Result<i64>` handler fits a
-/// `Result<UnchangedOr<Any>>` dispatcher. Only the outer `None` means no
match;
-/// matched errors and inner `None` are final. Conversion uses ordinary trait
-/// implementations, without recursively converting results or wrapping values
-/// in `Ok`.
-///
-/// Handlers match an owned FFI value, a borrowed object node, or an optional
-/// by-value `AnyView` catch-all, which must be last. Each handler receives its
-/// typed first value and may consume an initial prefix of the remaining
declared
-/// arguments without cloning. Neither arguments nor results are deduced from a
-/// handler signature, and no handler needs to consume every declared argument.
-///
-/// Handlers share the same receiver and method generic declarations and
bounds.
-/// Pattern-only, unbounded lifetimes may differ between handlers. Shared
method
-/// generics must occur in the receiver, declared tuple or declared return
type.
-/// Named lifetimes come from those shared declarations or the enclosing impl;
-/// use the same name in input and output to return a borrow of that input.
-/// Elided input lifetimes are independent; elided output lifetimes borrow the
-/// receiver. `Self` in the declared types denotes the original implementor.
-/// Handler-level `cfg`/`cfg_attr` attributes are unsupported; configure the
whole
-/// impl instead. A consuming `MutationView` retains mutation permission; its
-/// typed misses recover the same handle, and `AnyView` cannot grant it.
-///
-/// Matching invokes the public `match_any!` macro and its exact-final-type
-/// lookup for eligible larger handler sets. Dispatch also matches scalar
payloads
-/// and keeps owned argument conversions strict. Parameterized containers and
-/// other non-exact matchers retain ordered, fully checked conversion.
-///
-/// The macro operates on inherent impls only. Traversal engines explicitly
-/// call the dispatcher and configure their own default recursion;
-/// no method prefix has a reserved traversal meaning.
-///
-/// Generate one dispatcher per type. Method generics can select different
-/// argument and return types; use an expected result or explicit type
arguments
-/// when inference is ambiguous.
+// Documented on the `tvm_ffi::dispatch` re-export, whose examples run as
+// tvm-ffi doctests.
#[proc_macro_error]
#[proc_macro_attribute]
pub fn dispatch(attr: TokenStream, item: TokenStream) -> TokenStream {
dispatch::dispatch(attr, item)
}
-/// Match payloads in `Any`, `AnyView`, object references or `MutationView`.
-///
-/// Write `Type(value)` to bind an owned or scalar result, `NodeObj(ref node)`
-/// to borrow an object node, or `MutationView<Type>(capability)` to transfer a
-/// mutation capability. Rust type aliases and qualified types are accepted.
-/// Arms are tried in order; end with an unguarded `_ => ...` fallback.
-///
-/// The input is evaluated and taken by value once. Use `&value` to retain its
-/// owner. Matching is strict: it does not coerce between value categories or
-/// truncate stored numeric values. Guards may reject a match and try the next
arm.
-///
-/// Typed mutation capabilities do not support Map payloads, including nested
-/// Maps; use `MutationView<Any>` for those. Ordinary `Map<K, V>` arms are
supported.
+// Documented on the `tvm_ffi::match_any` re-export, whose examples run as
+// tvm-ffi doctests.
#[proc_macro_error]
#[proc_macro]
pub fn match_any(input: TokenStream) -> TokenStream {
@@ -166,105 +111,16 @@ pub fn derive_object_ref(input: TokenStream) ->
TokenStream {
object_macros::derive_object_ref(input)
}
-/// Define an object type in Rust from its object struct, as Python's
-/// `py_class` does: the type registers its key with the reflection of every
-/// field, so that C++ structural equality and hashing, `repr`, deep copy and
-/// Python see the fields.
-///
-/// ```ignore
-/// use tvm_ffi::{def_object_ref, rs_object};
-///
-/// /// A pair of values under a `Base`.
-/// #[rs_object("my_ext.Pair", final)]
-/// pub struct PairObj {
-/// base: BaseObj,
-/// pub first: tvm_ffi::String,
-/// pub second: tvm_ffi::String,
-/// #[reflect(seq_hash_ignore)]
-/// pub note: tvm_ffi::String,
-/// }
-///
-/// def_object_ref!(pub Pair, Base, PairObj);
-///
-/// let pair = Pair::from(PairObj::new(BaseObj::new(/* ... */), "a", "b", ""));
-/// let base = Base::from(pair);
-/// ```
-///
-/// 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`. The parent's
object
-/// must implement `ShallowCopy` and [`UnsafeInit`], as `rs_object` types and
-/// non-final C++ types bound by `tvm-ffi-stubgen` do. Every other field is
-/// reflected; `#[reflect(writable, seq_hash_ignore, ...)]` gives a field the
-/// [`FieldFlags`] of those names, and `name` and `doc` as for
-/// `derive(Object)`. The macro adds to the struct:
-///
-/// - `#[repr(C)]` and `Deref` to the parent's object, so `obj.field` reads the
-/// fields of both.
-/// - `PairObj::new(base, ...)`, which builds the object from the parent's
-/// object and the fields in order: 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` takes them.
-///
-/// [`def_object_ref!`] defines the reference to it and its conversion into the
-/// reference to its parent.
-///
-/// After the type key:
-///
-/// - `final`, `s_eq_hash_kind = "TreeNode"`, `child_slots = N`,
-/// `child_slots_can_overflow = bool` and `reflection = path` are
-/// `derive(Object)`'s `#[type_final]`, `#[type_s_eq_hash_kind]`,
-/// `#[type_child_slots]`, `#[type_child_slots_can_overflow]` and
-/// `#[type_reflection]`.
-/// - `crate = path` is where the generated code finds tvm-ffi
-/// (`crate = ::my_lib::tvm_ffi`), for a crate that depends on it only
through
-/// a re-export; by default `tvm_ffi`.
-///
-/// The type is a `derive(Object)` type with `#[type_register]`: it registers
on
-/// its first `type_index()`, with the fields, the methods,
-/// `__ffi_shallow_copy__` and `__ffi_type_final__`, and a creator that makes
-/// the object with every field undefined for reflection to set, as C++ makes
one
-/// with `UnsafeInit`, from which it registers `__ffi_new__` and, unless the
-/// methods define it, `__ffi_init__`; the macro implements [`UnsafeInit`] for
-/// the object. A field's type must implement `AnyCompatible` and `Clone`; an
-/// `init_off` field's type must take all-zero bytes as a value, as
-/// `__ffi_init__` leaves the field so.
-///
-/// [`FieldFlags`]: ../tvm_ffi/reflection/struct.FieldFlags.html
-/// [`UnsafeInit`]: ../tvm_ffi/reflection/trait.UnsafeInit.html
+// Documented on the `tvm_ffi::rs_object` re-export, whose examples run as
+// tvm-ffi doctests.
#[proc_macro_error]
#[proc_macro_attribute]
pub fn rs_object(args: TokenStream, item: TokenStream) -> TokenStream {
rs_object::rs_object(args, item)
}
-/// Define the reference to an object type, as C++
-/// `TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE` does:
-/// `def_object_ref!(TypeName, ParentType, ObjectName)`.
-///
-/// ```ignore
-/// use tvm_ffi::def_object_ref;
-///
-/// def_object_ref!(
-/// /// A pair of values.
-/// pub Pair,
-/// Base,
-/// PairObj
-/// );
-/// ```
-///
-/// `Pair` holds a `PairObj` and is a `derive(ObjectRef)` type. It dereferences
-/// to `PairObj`, and a `PairObj` converts into it.
-///
-/// `ParentType` is the reference to the parent's object: `Base` here, and
-/// `tvm_ffi::object::ObjectRef` for a type under `ffi.Object`. `Pair` converts
-/// into it by `impl_object_upcast!`, so its object must be a registered base
of
-/// `PairObj`, as that macro requires. A conversion into the reference to a
-/// further ancestor takes its own `impl_object_upcast!`.
-///
-/// Attributes and visibility before the name go on the struct. `crate = path`
-/// after the object type is where the generated code finds tvm-ffi, as for
-/// [`rs_object`](macro@rs_object).
+// 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 {
diff --git a/rust/tvm-ffi-macros/src/utils.rs b/rust/tvm-ffi-macros/src/utils.rs
index 76fdf3fb..5b2abfe2 100644
--- a/rust/tvm-ffi-macros/src/utils.rs
+++ b/rust/tvm-ffi-macros/src/utils.rs
@@ -18,15 +18,13 @@
*/
use proc_macro2::TokenStream;
use quote::quote;
-use std::env;
/// Return the path used to reference the `tvm-ffi` crate in generated code.
+///
+/// tvm-ffi declares `extern crate self as tvm_ffi`, so the same path works in
+/// the crate itself, in its doctests and in dependent crates.
pub(crate) fn get_tvm_ffi_crate() -> TokenStream {
- if env::var("CARGO_CRATE_NAME").as_deref() == Ok("tvm_ffi") {
- quote!(crate)
- } else {
- quote!(::tvm_ffi)
- }
+ quote!(::tvm_ffi)
}
/// Get an attribute by name from a derive input
diff --git a/rust/tvm-ffi/src/any.rs b/rust/tvm-ffi/src/any.rs
index 47c97b79..f9b24672 100644
--- a/rust/tvm-ffi/src/any.rs
+++ b/rust/tvm-ffi/src/any.rs
@@ -22,7 +22,25 @@ use crate::type_traits::AnyCompatible;
use tvm_ffi_sys::TVMFFITypeIndex as TypeIndex;
use tvm_ffi_sys::{TVMFFIAny, TVMFFIAnyViewToOwnedAny};
-/// Unmanaged Any that can hold reference to values
+/// A borrowed, type-erased FFI value.
+///
+/// `AnyView` holds any FFI value, such as an integer, a string or an object,
+/// without owning it: it does not retain objects, and its lifetime `'a` keeps
+/// it from outliving the value it borrows. It is `Copy` and is the argument
+/// type of FFI calls. Convert it into an [`Any`] to own the value.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::{Any, AnyView, Array};
+///
+/// let array = Array::new(vec![1_i64, 2, 3]);
+/// let view = AnyView::from(&array);
+/// assert_eq!(view.try_as::<Array<i64>>().unwrap().len(), 3);
+///
+/// let owned = Any::from(view);
+/// assert_eq!(owned.try_as::<Array<i64>>().unwrap().len(), 3);
+/// ```
#[derive(Copy, Clone)]
#[repr(C)]
pub struct AnyView<'a> {
@@ -31,7 +49,26 @@ pub struct AnyView<'a> {
_phantom: std::marker::PhantomData<&'a ()>,
}
-/// Managed Any that can hold reference to values
+/// An owned, type-erased FFI value.
+///
+/// `Any` holds any FFI value, such as an integer, a string or an object, and
+/// owns it: cloning an `Any` that holds an object retains the object, and
+/// dropping it releases the object. Any [`AnyCompatible`] value converts into
+/// it with `From`; [`try_as`](Self::try_as) and
[`match_any!`](crate::match_any)
+/// read it back. Borrow it as an [`AnyView`] to pass it without retaining.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::Any;
+///
+/// let value = Any::from(42_i64);
+/// assert_eq!(value.try_as::<i64>(), Some(42));
+/// assert_eq!(value.try_as::<tvm_ffi::String>(), None);
+///
+/// let text = Any::from(tvm_ffi::String::from("hello"));
+/// assert_eq!(text.try_as::<tvm_ffi::String>().unwrap().as_str(), "hello");
+/// ```
#[repr(C)]
pub struct Any {
data: TVMFFIAny,
@@ -41,6 +78,11 @@ pub struct Any {
// AnyView
//---------------------
impl<'a> AnyView<'a> {
+ /// Create a view of `None`, the empty FFI value.
+ ///
+ /// # Returns
+ ///
+ /// A view holding `None`.
pub fn new() -> Self {
Self {
data: TVMFFIAny::new(),
@@ -74,6 +116,14 @@ impl<'a> AnyView<'a> {
unsafe { Self::from_raw_ffi_any(data) }
}
+ /// Return the runtime type index of the viewed value.
+ ///
+ /// Indices below `TypeIndex::kTVMFFIStaticObjectBegin` are the built-in
+ /// non-object types; the others are object types.
+ ///
+ /// # Returns
+ ///
+ /// The type index.
#[inline(always)]
pub fn type_index(&self) -> i32 {
self.data.type_index
@@ -108,10 +158,16 @@ impl<'a> AnyView<'a> {
/// Borrow the viewed object as `T` when its runtime type is `T` or a
subtype.
///
- /// `T` is an object representation type such as `MyPairObj`. Non-object
- /// values and unrelated object types return `None`. This does not change
- /// the object's reference count; the borrow cannot outlive this view or
- /// the owner it borrows. See [`Any::downcast_ref`] for an example.
+ /// `T` is an object representation type such as `ArrayObj`. Non-object
values
+ /// and unrelated object types return `None`. This does not change the
object's
+ /// reference count; the borrow cannot outlive this view or the owner it
+ /// borrows.
+ ///
+ /// # Returns
+ ///
+ /// The borrowed object, or `None`.
+ ///
+ /// See also [`Any::downcast_ref`].
#[inline(always)]
pub fn downcast_ref<T: object::ObjectCore>(&self) -> Option<&T> {
if self.data.type_index < TypeIndex::kTVMFFIStaticObjectBegin as i32
@@ -124,14 +180,23 @@ impl<'a> AnyView<'a> {
Some(unsafe { &*self.data.data_union.v_obj.cast::<T>() })
}
- /// More strict version than try_from/try_into
+ /// Return the viewed value as a `T` when it is stored as exactly `T`.
+ ///
+ /// The check is strict and never converts: a stored integer is not
returned as
+ /// `f64`, and an object is returned only as a reference type it is an
instance
+ /// of. On success, `Any::from` of the result holds the same value.
///
- /// This function will not try to cast the type
- /// and ensures invariance that the return value is only Some(T)
- /// Any::from(T) contains exactly the same value value
+ /// # Returns
///
- /// will return Some(T) if the type is exactly compatible with T
- /// will return None if the type is not compatible with T
+ /// The value, or `None` when it is not stored as `T`.
+ ///
+ /// ```
+ /// use tvm_ffi::AnyView;
+ /// assert_eq!(AnyView::from(&7_i64).try_as::<i64>(), Some(7));
+ /// assert_eq!(AnyView::from(&7_i64).try_as::<f64>(), None);
+ /// ```
+ ///
+ /// See also [`Any::try_as`].
#[inline(always)]
pub fn try_as<T>(&self) -> Option<T>
where
@@ -220,46 +285,53 @@ impl<T> TryFromTemp<T> {
// Any
//---------------------
impl Any {
+ /// Create an `Any` holding `None`, the empty FFI value.
+ ///
+ /// # Returns
+ ///
+ /// An `Any` holding `None`.
#[inline(always)]
pub fn new() -> Self {
Self {
data: TVMFFIAny::new(),
}
}
+
+ /// Return the runtime type index of the stored value.
+ ///
+ /// Indices below `TypeIndex::kTVMFFIStaticObjectBegin` are the built-in
+ /// non-object types; the others are object types.
+ ///
+ /// # Returns
+ ///
+ /// The type index.
#[inline(always)]
pub fn type_index(&self) -> i32 {
self.data.type_index
}
+
#[inline(always)]
pub(crate) fn as_raw_ffi_any(&self) -> &TVMFFIAny {
&self.data
}
/// Borrow the stored object as `T` when its runtime type is `T` or a
subtype.
///
- /// `T` is an object representation type such as `FooObj`, rather than its
- /// managed reference `Foo`. Non-object values and unrelated object types
+ /// `T` is an object representation type such as `ArrayObj`, rather than
its
+ /// managed reference `Array`. Non-object values and unrelated object types
/// return `None`. This neither consumes `self` nor changes the object's
/// reference count, and the returned reference cannot outlive `self`.
///
+ /// # Returns
+ ///
+ /// The borrowed object, or `None`.
+ ///
/// ```
- /// use tvm_ffi::{def_object_ref, rs_object, Any, AnyView, Object,
ObjectRefCore};
- /// use tvm_ffi::object::ObjectRef;
- ///
- /// #[rs_object("my.MyPair", crate = tvm_ffi)]
- /// pub struct MyPairObj {
- /// base: Object,
- /// pub first: i64,
- /// pub second: i64,
- /// }
- /// def_object_ref!(pub MyPair, ObjectRef, MyPairObj, crate = tvm_ffi);
- ///
- /// let pair = MyPair::from(MyPairObj::new(Object::new(), 3, 4));
- /// assert_eq!(pair.downcast_ref::<MyPairObj>().unwrap().first, 3);
- /// let value = Any::from(pair);
- /// assert_eq!(value.downcast_ref::<MyPairObj>().unwrap().second, 4);
- /// let view = AnyView::from(&value);
- /// assert_eq!(view.downcast_ref::<MyPairObj>().unwrap().first, 3);
+ /// use tvm_ffi::{collections::array::ArrayObj, Any, Array};
+ /// let value = Any::from(Array::new(vec![1_i64, 2]));
+ /// assert_eq!(value.downcast_ref::<ArrayObj>().unwrap().size, 2);
/// ```
+ ///
+ /// See also [`try_as`](Self::try_as).
#[inline]
pub fn downcast_ref<T: object::ObjectCore>(&self) -> Option<&T> {
if self.data.type_index < TypeIndex::kTVMFFIStaticObjectBegin as i32
@@ -272,18 +344,27 @@ impl Any {
Some(unsafe { &*self.data.data_union.v_obj.cast::<T>() })
}
- /// Try to query if stored typed in Any exactly matches the type T
+ /// Return the stored value as a `T` when it is stored as exactly `T`.
///
- /// This function is fast in the case of failure and can be used to check
- /// if the type is compatible with T
+ /// The check is strict and never converts: a stored integer is not
returned as
+ /// `f64`, and an object is returned only as a reference type it is an
instance
+ /// of. On success, `Any::from` of the result holds the same value. A
failed
+ /// check is cheap, so `try_as` also serves as a type test. A returned
object
+ /// reference is retained; use [`downcast_ref`](Self::downcast_ref) to
borrow the
+ /// object instead.
///
- /// This function will not try to cast the type
- /// and ensures invariance that the return value is only Some(T)
- /// `Any::from(T)` contains exactly the same value value
- /// `Any::try_as<T>()` contains exactly the same value value
+ /// # Returns
+ ///
+ /// The value, or `None` when it is not stored as `T`.
+ ///
+ /// ```
+ /// use tvm_ffi::Any;
+ /// let value = Any::from(7_i64);
+ /// assert_eq!(value.try_as::<i64>(), Some(7));
+ /// assert_eq!(value.try_as::<f64>(), None);
+ /// ```
///
- /// will return Some(T) if the type is exactly compatible with T
- /// will return None if the type is not compatible with T
+ /// See also [`match_any!`](crate::match_any).
#[inline]
pub fn try_as<T>(&self) -> Option<T>
where
diff --git a/rust/tvm-ffi/src/collections/array.rs
b/rust/tvm-ffi/src/collections/array.rs
index d4d60c7a..f7fb613b 100644
--- a/rust/tvm-ffi/src/collections/array.rs
+++ b/rust/tvm-ffi/src/collections/array.rs
@@ -50,6 +50,28 @@ unsafe impl ObjectCoreWithExtraItems for ArrayObj {
}
}
+/// An immutable, reference-counted sequence of `T`, shared with C++ and
+/// Python as `ffi.Array`.
+///
+/// Cloning an `Array` is cheap: it shares the same elements. Use [`List`]
+/// for a sequence that changes in place.
+///
+/// [`List`]: crate::List
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::Array;
+///
+/// let array = Array::new(vec![1_i64, 2, 3]);
+/// assert_eq!(array.len(), 3);
+/// assert_eq!(array.get(1)?, 2);
+/// assert!(array.get(3).is_err());
+///
+/// let doubled: Array<i64> = array.iter().map(|x| x * 2).collect();
+/// assert_eq!(doubled.iter().collect::<Vec<_>>(), vec![2, 4, 6]);
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
#[repr(C)]
#[derive(Clone)]
pub struct Array<T: ContainerElement + Clone> {
@@ -91,7 +113,15 @@ unsafe impl<T: ContainerElement + Clone> ObjectRefCore for
Array<T> {
}
impl<T: ContainerElement + Clone> Array<T> {
- /// Creates a new Array from a vector of items.
+ /// Create an array holding `items`.
+ ///
+ /// # Arguments
+ ///
+ /// * `items` - The elements.
+ ///
+ /// # Returns
+ ///
+ /// The array.
pub fn new(items: Vec<T>) -> Self {
let capacity = items.len();
Self::new_with_capacity(items, capacity)
@@ -127,15 +157,38 @@ impl<T: ContainerElement + Clone> Array<T> {
unsafe { Self::from_data(arc) }
}
+ /// Return the number of elements.
+ ///
+ /// # Returns
+ ///
+ /// The number of elements.
pub fn len(&self) -> usize {
self.data.size as usize
}
+ /// Return `true` if the array has no elements.
+ ///
+ /// # Returns
+ ///
+ /// `true` if the array has no elements.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
- /// Retrieves an item at the given index.
+ /// Return the element at `index`.
+ ///
+ /// # Arguments
+ ///
+ /// * `index` - The position of the element.
+ ///
+ /// # Returns
+ ///
+ /// The element at `index`.
+ ///
+ /// # Errors
+ ///
+ /// Returns an `IndexError` if `index` is out of bounds, or a `TypeError`
+ /// if the element is not a `T`.
pub fn get(&self, index: usize) -> Result<T, crate::Error> {
if index >= self.len() {
crate::bail!(crate::error::INDEX_ERROR, "Array get index out of
bound");
@@ -157,6 +210,11 @@ impl<T: ContainerElement + Clone> Array<T> {
}
}
+ /// Return an iterator over the elements.
+ ///
+ /// # Returns
+ ///
+ /// An iterator over the elements.
pub fn iter(&'_ self) -> ArrayIterator<'_, T> {
ArrayIterator {
array: self,
diff --git a/rust/tvm-ffi/src/collections/list.rs
b/rust/tvm-ffi/src/collections/list.rs
index a0151a80..40a0c8b4 100644
--- a/rust/tvm-ffi/src/collections/list.rs
+++ b/rust/tvm-ffi/src/collections/list.rs
@@ -120,9 +120,24 @@ fn type_error<T: ContainerElement>(index: usize) ->
Result<T> {
)
}
-/// Mutable, reference-counted sequence of `T`, sharing its underlying
-/// `ListObj` with C++. Cloning is cheap (it bumps the refcount) and yields a
-/// handle to the same list.
+/// A mutable, reference-counted sequence of `T`, shared with C++ and Python
+/// as `ffi.List`.
+///
+/// Cloning a `List` is cheap and yields a handle to the same list, so a change
+/// through one handle is seen through every other. Methods take `&self`.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::List;
+///
+/// let list = List::<i64>::new();
+/// list.push(1)?;
+/// list.push(2)?;
+/// assert_eq!(list.get(1)?, 2);
+/// assert_eq!(list.len(), 2);
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
#[repr(C)]
pub struct List<T> {
data: ObjectArc<ListObj>,
diff --git a/rust/tvm-ffi/src/collections/map.rs
b/rust/tvm-ffi/src/collections/map.rs
index 5df08c64..a82664fa 100644
--- a/rust/tvm-ffi/src/collections/map.rs
+++ b/rust/tvm-ffi/src/collections/map.rs
@@ -96,8 +96,24 @@ pub struct MapObj {
pub slots: u64,
}
-/// Immutable, reference-counted map from `K` to `V`, sharing its underlying
-/// `MapObj` with C++. Cloning is cheap (it bumps the refcount).
+/// An immutable, reference-counted map from `K` to `V`, shared with C++ and
+/// Python as `ffi.Map`.
+///
+/// Cloning a `Map` is cheap: it shares the same entries.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::{Map, String};
+///
+/// let map: Map<String, i64> = [(String::from("a"), 1), (String::from("b"),
2)]
+/// .into_iter()
+/// .collect();
+/// assert_eq!(map.len(), 2);
+/// assert_eq!(map.get(&String::from("b"))?, Some(2));
+/// assert_eq!(map.get(&String::from("c"))?, None);
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
#[repr(C)]
pub struct Map<K, V> {
data: ObjectArc<MapObj>,
diff --git a/rust/tvm-ffi/src/collections/shape.rs
b/rust/tvm-ffi/src/collections/shape.rs
index 8f305334..fa1a7af4 100644
--- a/rust/tvm-ffi/src/collections/shape.rs
+++ b/rust/tvm-ffi/src/collections/shape.rs
@@ -36,7 +36,18 @@ pub struct ShapeObj {
data: TVMFFIShapeCell,
}
-/// ABI stable owned Shape for ffi
+/// An immutable sequence of `i64` dimensions, shared with C++ as
+/// `ffi.Shape`.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::Shape;
+///
+/// let shape = Shape::from([2_i64, 3]);
+/// assert_eq!(shape.as_slice(), &[2, 3]);
+/// assert_eq!(shape.len(), 2);
+/// ```
#[repr(C)]
#[derive(ObjectRef, Clone)]
pub struct Shape {
diff --git a/rust/tvm-ffi/src/collections/tensor.rs
b/rust/tvm-ffi/src/collections/tensor.rs
index 601cd361..32cbf488 100644
--- a/rust/tvm-ffi/src/collections/tensor.rs
+++ b/rust/tvm-ffi/src/collections/tensor.rs
@@ -87,7 +87,20 @@ pub struct TensorObj {
dltensor: DLTensor,
}
-/// ABI stable owned Shape for ffi
+/// A reference-counted n-dimensional array, shared with C++ and Python and
+/// exchanged with other frameworks through DLPack.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::Tensor;
+///
+/// let tensor = Tensor::from_slice(&[1.0_f32, 2.0, 3.0, 4.0, 5.0, 6.0], &[2,
3])?;
+/// assert_eq!(tensor.shape(), &[2, 3]);
+/// assert_eq!(tensor.numel(), 6);
+/// assert_eq!(tensor.data_as_slice::<f32>()?[4], 5.0);
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
#[repr(C)]
#[derive(ObjectRef, Clone)]
pub struct Tensor {
diff --git a/rust/tvm-ffi/src/error.rs b/rust/tvm-ffi/src/error.rs
index 2823ba71..0f2c79be 100644
--- a/rust/tvm-ffi/src/error.rs
+++ b/rust/tvm-ffi/src/error.rs
@@ -25,11 +25,20 @@ use tvm_ffi_sys::{
TVMFFIErrorMoveFromRaised, TVMFFIErrorSetRaised, TVMFFIObjectHandle,
TVMFFITypeIndex,
};
-/// Error kind, wraps in a struct to be explicit
+/// The kind of an [`Error`], such as `ValueError`, matching the exception
+/// type that Python raises.
+///
+/// Use the constants such as [`VALUE_ERROR`] and [`TYPE_ERROR`], or
+/// `ErrorKind("MyError")` for a custom kind.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ErrorKind<'a>(&'a str);
impl<'a> ErrorKind<'a> {
+ /// Get the name of the kind.
+ ///
+ /// # Returns
+ ///
+ /// The name, such as `"ValueError"`.
pub fn as_str(&self) -> &str {
self.0
}
@@ -61,21 +70,69 @@ pub struct ErrorObj {
cell: TVMFFIErrorCell,
}
-/// Error reference class
+/// An error that crosses the FFI boundary, with a kind, a message and a
+/// backtrace.
+///
+/// An `Error` raised in C++ or Python arrives in Rust as an `Err`, and one
+/// returned from a Rust [`Function`](crate::Function) is raised in the
caller's
+/// language. Create one with [`bail!`](crate::bail) or
+/// [`ensure!`](crate::ensure), which also record the source location.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::{bail, Result, VALUE_ERROR};
+///
+/// fn parse_digit(c: char) -> Result<u32> {
+/// match c.to_digit(10) {
+/// Some(digit) => Ok(digit),
+/// None => bail!(VALUE_ERROR, "not a digit: {:?}", c),
+/// }
+/// }
+///
+/// let error = parse_digit('x').unwrap_err();
+/// assert_eq!(error.kind(), VALUE_ERROR);
+/// assert_eq!(error.message(), "not a digit: 'x'");
+/// ```
#[derive(Clone, ObjectRef)]
pub struct Error {
data: ObjectArc<ErrorObj>,
}
-/// Default result that uses Error as the error type
+/// A `Result` whose error type defaults to [`Error`].
pub type Result<T, E = Error> = std::result::Result<T, E>;
impl Error {
+ /// Create an error.
+ ///
+ /// Prefer [`bail!`](crate::bail), which also records the source location.
+ ///
+ /// # Arguments
+ ///
+ /// * `kind` - The kind of the error, such as [`VALUE_ERROR`].
+ /// * `message` - The message of the error.
+ /// * `traceback` - The backtrace of the error, which may be empty.
+ ///
+ /// # Returns
+ ///
+ /// The error.
pub fn new(kind: ErrorKind<'_>, message: &str, traceback: &str) -> Self {
Self::new_with_cause_and_extra_context(kind, message, traceback, None,
None)
}
/// Create an error retaining its cause and any application-specific
context.
+ ///
+ /// # Arguments
+ ///
+ /// * `kind` - The kind of the error.
+ /// * `message` - The message of the error.
+ /// * `traceback` - The backtrace of the error, which may be empty.
+ /// * `cause_chain` - The error that caused this one, if any.
+ /// * `extra_context` - An object of application-specific context, if any.
+ ///
+ /// # Returns
+ ///
+ /// The error.
pub fn new_with_cause_and_extra_context(
kind: ErrorKind<'_>,
message: &str,
@@ -106,10 +163,16 @@ impl Error {
}
}
- /// Create a new error by moving from raised error
+ /// Create a new error by moving from the error that the last failed FFI
call
+ /// raised on this thread.
///
/// # Returns
- /// The error from the raised error
+ ///
+ /// The raised error.
+ ///
+ /// # Panics
+ ///
+ /// Panics if no error was raised.
pub fn from_raised() -> Self {
unsafe {
let mut error_handle: TVMFFIObjectHandle = std::ptr::null_mut();
@@ -123,41 +186,50 @@ impl Error {
}
}
- /// Set the error as raised
+ /// Set the error as raised on this thread, for the caller of an FFI
function to
+ /// take.
///
/// # Arguments
- /// * `error` - The error to set as raised
+ ///
+ /// * `error` - The error to set as raised.
pub fn set_raised(error: &Self) {
unsafe {
TVMFFIErrorSetRaised(ObjectArc::as_raw(&error.data) as
TVMFFIObjectHandle);
}
}
- /// Get the kind of the error
+ /// Get the kind of the error.
///
/// # Returns
- /// The kind of the error
+ ///
+ /// The kind of the error, such as [`VALUE_ERROR`].
pub fn kind(&self) -> ErrorKind<'_> {
ErrorKind(&self.data.cell.kind.as_str())
}
- /// Get the message of the error
+ /// Get the message of the error.
///
/// # Returns
- /// The message of the error
+ ///
+ /// The message of the error.
pub fn message(&self) -> &str {
self.data.cell.message.as_str()
}
- /// Get the backtrace of the error
+ /// Get the backtrace of the error.
///
/// # Returns
- /// The backtrace of the error
+ ///
+ /// The backtrace, most recent call first.
pub fn backtrace(&self) -> &str {
self.data.cell.backtrace.as_str()
}
/// Return the cause, if one was attached to this error.
+ ///
+ /// # Returns
+ ///
+ /// The error that caused this one, or `None`.
pub fn cause_chain(&self) -> Option<Error> {
let handle = self.data.cell.cause_chain;
if handle.is_null() {
@@ -173,6 +245,10 @@ impl Error {
}
/// Return the application-specific context, if present.
+ ///
+ /// # Returns
+ ///
+ /// The context object, or `None`.
pub fn extra_context(&self) -> Option<object::ObjectRef> {
let handle = self.data.cell.extra_context;
if handle.is_null() {
@@ -186,10 +262,12 @@ impl Error {
}
}
- /// Get the traceback of the error in the order of most recent call last
+ /// Get the traceback of the error in the order of most recent call last,
as
+ /// Python prints it.
///
/// # Returns
- /// The traceback of the error
+ ///
+ /// The traceback of the error.
pub fn traceback_most_recent_call_last(&self) -> String {
let backtrace = self.backtrace();
let backtrace_lines = backtrace.split('\n');
@@ -201,14 +279,20 @@ impl Error {
traceback
}
- /// Append the backtrace to the error
+ /// Append the backtrace to the error.
+ ///
+ /// [`attach_context!`](crate::attach_context) appends the current source
+ /// location this way. The error is updated in place when it is not
shared, and
+ /// copied otherwise.
///
/// # Arguments
- /// * `this` - The error to append the backtrace to
- /// * `backtrace` - The backtrace to append
+ ///
+ /// * `this` - The error to append the backtrace to.
+ /// * `backtrace` - The backtrace to append.
///
/// # Returns
- /// The error with the appended backtrace
+ ///
+ /// The error with the appended backtrace.
pub fn with_appended_backtrace(this: Self, backtrace: &str) -> Self {
if ObjectArc::strong_count(&this.data) == 1 {
// this is the only reference to the error
diff --git a/rust/tvm-ffi/src/extra/dispatch.rs
b/rust/tvm-ffi/src/extra/dispatch.rs
index cca63484..cb702594 100644
--- a/rust/tvm-ffi/src/extra/dispatch.rs
+++ b/rust/tvm-ffi/src/extra/dispatch.rs
@@ -19,19 +19,23 @@
//! Runtime type dispatch indexed by its tuple of arguments.
-/// Runtime type dispatch with a complete handler outcome.
+/// A handler that the structural traversals call with a tuple of arguments.
///
-/// Implementations generated by `#[dispatch(prefix, Args, R)]` use a borrowed
receiver
-/// (`&T` or `&mut T`) as `Self` and use `Ret = Option<R>`. Only the outer
-/// `None` denotes no match: a matched error or inner `None` remains
`Some(result)`
-/// and does not try another handler. Consuming the receiver reference
preserves
-/// results borrowing it while ordinary method calls automatically borrow it.
-/// The argument tuple and complete return type select an implementation.
+/// See also [`#[dispatch]`](crate::dispatch).
#[diagnostic::on_unimplemented(
message = "this receiver does not implement Dispatcher for the requested
argument tuple",
note = "use #[dispatch(prefix, (Carrier, ...), Ret)] with compatible typed
handlers"
)]
pub trait Dispatcher<Args, Ret> {
+ /// Dispatch on `args.0` with the complete argument tuple `args`.
+ ///
+ /// # Arguments
+ ///
+ /// * `args` - The argument tuple, whose first element is the value to
dispatch on.
+ ///
+ /// # Returns
+ ///
+ /// `Some` of the matched handler's result, or `None` when no handler
matches.
fn dispatch_tuple(self, args: Args) -> Ret;
}
diff --git a/rust/tvm-ffi/src/extra/structural_mutate.rs
b/rust/tvm-ffi/src/extra/structural_mutate.rs
index 99434c65..3b3e4c18 100644
--- a/rust/tvm-ffi/src/extra/structural_mutate.rs
+++ b/rust/tvm-ffi/src/extra/structural_mutate.rs
@@ -342,10 +342,13 @@ impl<T: Into<Any>> IntoMutateResult for Result<T> {
}
}
-/// Permission to attempt in-place structural mutation along an owned path.
+/// Whether a [`MutationView`] may reuse its value's storage in place.
///
-/// `Allow` still requires unique ownership. A [`MutationView`] from an
exclusive
-/// borrow of an owning `Any` may allow reuse; a shared borrow never grants it.
+/// `Allow` is an upper bound: a value is reused in place only when it is also
+/// uniquely owned. A view made from `&mut Any` starts as `Allow`, and a view
+/// from [`MutationView::borrowed`] is always `Disallow`.
+///
+/// See also [`structural_mutate`].
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum InplaceMode {
/// Use ordinary mutation, including for uniquely owned values.
@@ -367,20 +370,22 @@ impl InplaceMode {
}
}
-/// A non-owning, move-only view carrying in-place permission.
+/// A borrowed value that a structural mutation may rewrite in place.
+///
+/// Mutation handlers receive a `MutationView` instead of an owned value, so
+/// matching a node does not retain it. Inspect the node through the view,
+/// then pass the view to [`StructuralMutatorView::default_mutate`] to rewrite
+/// its children, or rewrite one field with [`field`](MutationView::field).
+/// When the node is uniquely owned and the view carries
+/// [`InplaceMode::Allow`], its storage is reused instead of copied.
///
-/// This view cannot be copied or cloned. Consuming it transfers the same
-/// permission; narrowing its matched type does not acquire ownership.
+/// `T` is the matched type: `MutationView<'_, Array<Any>>` derefs to the
+/// array's node, and the default `MutationView<'_>` matches every value. The
+/// view is move-only: it cannot be copied or cloned, and consuming it passes
+/// on the same permission. A view made from `&mut Any` may reuse storage;
+/// one made with [`borrowed`](MutationView::borrowed) never does.
///
-/// Unlike an owning typed argument, this handle does not increment the
reference
-/// count. Borrow through it to inspect the node, then consume it in
-/// `default_mutate` to continue recursion. A surviving owning alias
-/// still forces copying. Node borrows come from this handle, not from the
-/// callback context, so the handle can safely be passed to another context.
-/// `T` selects the callback's matched FFI type; `Any` matches every value.
-/// The same handle is passed to consuming [`Dispatcher`] handlers. A publicly
-/// shared-borrow handle always disallows reuse. An exclusive `&mut Any` borrow
-/// grants permission to attempt reuse while keeping its owner borrowed.
+/// See also [`structural_mutate`].
///
/// A node borrowed through a copied view cannot survive consumption of the
handle:
/// ```compile_fail,E0505
@@ -448,7 +453,18 @@ impl<'a> MutationView<'a> {
}
}
- /// Wrap a borrowed value without granting in-place permission.
+ /// Create a view of a borrowed value that never reuses its storage.
+ ///
+ /// The mutation result is always a new value or [`Unchanged`]. To allow
reuse,
+ /// make the view from `&mut Any` instead.
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The value to view.
+ ///
+ /// # Returns
+ ///
+ /// A view with [`InplaceMode::Disallow`].
#[inline(always)]
pub fn borrowed(value: AnyView<'a>) -> Self {
Self::new(value, InplaceMode::Disallow)
@@ -457,6 +473,7 @@ impl<'a> MutationView<'a> {
impl<'a> From<&'a mut Any> for MutationView<'a> {
/// Borrow an owner exclusively without acquiring a reference count.
+ ///
/// Reuse still requires the runtime's uniqueness and alias checks.
#[inline(always)]
fn from(value: &'a mut Any) -> Self {
@@ -478,13 +495,28 @@ impl<'a, T> MutationView<'a, T> {
}
}
- /// Permission carried independently of temporary owning aliases.
+ /// Return the permission this view carries.
+ ///
+ /// # Returns
+ ///
+ /// Whether this view may reuse its value's storage.
#[inline(always)]
pub fn inplace_mode(&self) -> InplaceMode {
self.mode
}
- /// Restrict permission without upgrading a shared-borrow capability.
+ /// Restrict the permission, without ever upgrading it.
+ ///
+ /// `Disallow` turns off reuse. `Allow` keeps the current mode, so it never
+ /// grants reuse to a view that does not have it, such as a borrowed one.
+ ///
+ /// # Arguments
+ ///
+ /// * `requested` - The permission to restrict to.
+ ///
+ /// # Returns
+ ///
+ /// The view with the restricted permission.
#[inline(always)]
pub fn with_inplace_mode(mut self, requested: InplaceMode) -> Self {
if requested == InplaceMode::Disallow {
@@ -493,8 +525,14 @@ impl<'a, T> MutationView<'a, T> {
self
}
- /// Borrow the current value. The view and its copies borrow this handle,
- /// so their last use must precede consuming it for default mutation.
+ /// Borrow the current value.
+ ///
+ /// The view and its copies borrow this handle, so their last use must
precede
+ /// consuming it for default mutation.
+ ///
+ /// # Returns
+ ///
+ /// The value as an [`AnyView`].
#[inline(always)]
pub fn as_any_view(&self) -> AnyView<'_> {
// SAFETY: the input owner keeps this payload live while the
capability is
@@ -503,7 +541,13 @@ impl<'a, T> MutationView<'a, T> {
}
/// Borrow an object or subtype as `U` without changing ownership or
permission.
- /// The reference borrows this capability and cannot survive its
consumption.
+ ///
+ /// `U` is an object type such as `ArrayObj`. The reference borrows this
view and
+ /// cannot survive its consumption.
+ ///
+ /// # Returns
+ ///
+ /// The borrowed object, or `None` when the value is not a `U`.
#[inline(always)]
pub fn downcast_ref<U: ObjectCore>(&self) -> Option<&U> {
if self.value.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin
as i32
@@ -517,6 +561,18 @@ impl<'a, T> MutationView<'a, T> {
}
/// Narrow the matched type without acquiring an owning reference.
+ ///
+ /// The check is the strict one of [`Any::try_as`], and the view keeps its
+ /// permission.
+ ///
+ /// # Returns
+ ///
+ /// The view typed as `MutationView<'_, U>`.
+ ///
+ /// # Errors
+ ///
+ /// Returns the view unchanged when the value is not a `U`, so another
type can
+ /// be tried.
#[inline(always)]
pub fn try_cast<U: crate::type_traits::ContainerElement>(
self,
@@ -537,6 +593,10 @@ impl<'a, T> MutationView<'a, T> {
}
/// Erase the matched type while preserving the carried permission.
+ ///
+ /// # Returns
+ ///
+ /// The view as `MutationView<'_>`.
#[inline(always)]
pub fn into_untyped(self) -> MutationView<'a> {
MutationView {
@@ -574,11 +634,47 @@ impl<T: object::ObjectRefCore> std::ops::Deref for
MutationView<'_, T> {
}
impl<T: object::ObjectRefCore> MutationView<'_, T> {
- /// Borrow a direct owning field while exclusively borrowing this
capability.
- /// Permission also requires the parent to remain unique after selection.
- /// This does not grant writeback to the parent field.
+ /// Borrow a direct owning field of the node as a view of its own, to
rewrite it
+ /// with [`StructuralMutatorView::mutate`].
+ ///
+ /// The field view exclusively borrows this view. It may reuse the field's
+ /// storage only when this view may and the node remains uniquely owned.
This
+ /// does not write the result back into the node: keep the node
[`Unchanged`] or
+ /// rebuild it from the result.
+ ///
+ /// # Arguments
///
- /// Panics if the selector returns storage outside the parent
representation.
+ /// * `selector` - Returns a reference to one of the node's own owning
fields,
+ /// an [`Any`] or a managed reference.
+ ///
+ /// # Returns
+ ///
+ /// A view of the field.
+ ///
+ /// # Panics
+ ///
+ /// Panics if `selector` returns a reference outside the node.
+ ///
+ /// ```
+ /// # use tvm_ffi::object::ObjectRef;
+ /// # 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);
+ /// # struct Pass;
+ /// # #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_,
Self>), Result<UnchangedOr<Any>>)]
+ /// # impl Pass {
+ /// # fn mutate_pair(&mut self, mut pair: MutationView<'_, MyPair>,
mutator: &mut StructuralMutatorView<'_, Self>) -> Result<UnchangedOr<Any>> {
+ /// let second = mutator.mutate(self, pair.field(|pair| &pair.second))?;
+ /// # Ok(second)
+ /// # }
+ /// # }
+ /// # let pair = MyPairObj::new(Object::new(), Array::new(vec![1_i64]),
Array::new(vec![2_i64]));
+ /// # structural_mutate(MyPair::from(pair), Pass)?;
+ /// # Ok::<(), tvm_ffi::Error>(())
+ /// ```
+ ///
+ /// See also [`structural_mutate`].
#[inline(always)]
pub fn field<U: MutationField>(
&mut self,
@@ -613,8 +709,15 @@ impl<T: object::ObjectRefCore> MutationView<'_, T> {
}
}
-/// Recursive mutation and default descent for a dispatcher.
-/// Pass the handler's `self` to recursive methods with the same dispatcher
identity.
+/// The running structural mutation, passed to each handler.
+///
+/// A handler that matches a node decides how its children are rewritten: call
+/// [`default_mutate`](Self::default_mutate) to rewrite every child through the
+/// dispatcher, [`mutate`](Self::mutate) to rewrite one value, or neither to
+/// leave the children as they are. Pass the handler's own `self` as `dispatch`
+/// to every method.
+///
+/// See also [`structural_mutate`].
pub struct StructuralMutatorView<'a, D, Base = StructuralMutatorObj> {
runtime: StructuralMutatorHandle,
_scope: PhantomData<&'a mut ()>,
@@ -624,6 +727,10 @@ pub struct StructuralMutatorView<'a, D, Base =
StructuralMutatorObj> {
impl<D, Base: StructuralMutatorBase> StructuralMutatorView<'_, D, Base> {
/// Inspect the base state without retaining the scoped mutator runtime.
+ ///
+ /// # Returns
+ ///
+ /// The base state, borrowed for the lifetime of this view.
#[inline(always)]
pub fn base(&self) -> &Base {
// SAFETY: the unsafe base contract permits shared inspection,
including
@@ -641,12 +748,33 @@ impl<D, Base: StructuralMutatorBase>
StructuralMutatorView<'_, D, Base> {
}
}
+ /// Return the definition region the current value is in.
+ ///
+ /// Reflected fields marked as definitions set it while their values are
+ /// mutated; it is [`DefRegionKind::None`] elsewhere.
+ ///
+ /// # Returns
+ ///
+ /// The current definition region.
#[inline(always)]
pub fn def_region_kind(&self) -> DefRegionKind {
unsafe { std::mem::transmute(*(*self.runtime).def_region_mode.get()) }
}
- /// Scope definition-region state; an enclosing Pattern keeps precedence.
+ /// Run `callback` under a scoped definition region.
+ ///
+ /// An enclosing [`DefRegionKind::Pattern`] region keeps precedence, since
a
+ /// pattern applies to everything inside it. The previous region is
restored
+ /// afterwards.
+ ///
+ /// # Arguments
+ ///
+ /// * `kind` - The definition region to enter.
+ /// * `callback` - Runs inside the region, with this view.
+ ///
+ /// # Returns
+ ///
+ /// The result of `callback`.
#[inline(always)]
pub fn with_def_region_kind<R>(
&mut self,
@@ -658,7 +786,24 @@ impl<D, Base: StructuralMutatorBase>
StructuralMutatorView<'_, D, Base> {
})
}
- /// Re-enter dispatch with the capability's carried permission.
+ /// Mutate `value` through the dispatcher, as if the traversal had reached
it.
+ ///
+ /// `value` keeps the permission it carries. Use it to rewrite a value
other
+ /// than the current node, such as a field selected with
+ /// [`MutationView::field`].
+ ///
+ /// # Arguments
+ ///
+ /// * `dispatch` - The handler's own `self`.
+ /// * `value` - The value to mutate.
+ ///
+ /// # Returns
+ ///
+ /// The replacement, or [`Unchanged`].
+ ///
+ /// # Errors
+ ///
+ /// Returns the error of the handler that `value` reaches.
#[inline(always)]
pub fn mutate<'input, T>(
&mut self,
@@ -675,7 +820,27 @@ impl<D, Base: StructuralMutatorBase>
StructuralMutatorView<'_, D, Base> {
.and_then(UnchangedOr::from_carrier)
}
- /// Descend through the native hook or reflected fields without
redispatching the root.
+ /// Rewrite the children of `value` through the dispatcher, without
dispatching
+ /// `value` itself again.
+ ///
+ /// Children are found by the type's registered mutation hook or its
reflected
+ /// fields. The result is unchanged when no child changed; otherwise it is
a
+ /// node with the new children, made in place when `value` allows it and is
+ /// uniquely owned.
+ ///
+ /// # Arguments
+ ///
+ /// * `dispatch` - The handler's own `self`.
+ /// * `value` - The value whose children to mutate, usually the view the
+ /// handler received.
+ ///
+ /// # Returns
+ ///
+ /// The rewritten node, or [`Unchanged`].
+ ///
+ /// # Errors
+ ///
+ /// Returns the first error of a child's handler.
#[inline(always)]
pub fn default_mutate<'input, T>(
&mut self,
@@ -696,6 +861,23 @@ impl<D, Base: StructuralMutatorBase>
StructuralMutatorView<'_, D, Base> {
.and_then(UnchangedOr::from_carrier)
}
+ /// Look up the replacement recorded for a variable in this mutation.
+ ///
+ /// Default mutation records the result of every free variable and DAG
node it
+ /// rewrites, and replaces each later occurrence with it.
+ ///
+ /// # Arguments
+ ///
+ /// * `dispatch` - The handler's own `self`.
+ /// * `var` - The variable, an object.
+ ///
+ /// # Returns
+ ///
+ /// The recorded replacement, or `None`.
+ ///
+ /// # Errors
+ ///
+ /// Returns a `TypeError` if `var` is not an object.
#[inline(always)]
pub fn var_remap_get(&mut self, dispatch: &mut D, var: AnyView<'_>) ->
Result<Option<Any>> {
unsafe {
@@ -707,6 +889,24 @@ impl<D, Base: StructuralMutatorBase>
StructuralMutatorView<'_, D, Base> {
}
}
+ /// Record a replacement for a variable for the rest of this mutation.
+ ///
+ /// Default mutation then replaces each later occurrence of `var`, a free
+ /// variable or DAG node, with `value`.
+ ///
+ /// # Arguments
+ ///
+ /// * `dispatch` - The handler's own `self`.
+ /// * `var` - The variable, an object.
+ /// * `value` - The replacement.
+ ///
+ /// # Returns
+ ///
+ /// `Ok(())` once the replacement is recorded.
+ ///
+ /// # Errors
+ ///
+ /// Returns a `TypeError` if `var` is not an object.
#[inline(always)]
pub fn var_remap_set(&mut self, dispatch: &mut D, var: AnyView<'_>, value:
&Any) -> Result<()> {
unsafe {
@@ -1832,20 +2032,20 @@ impl StructuralMutatorObj {
///
/// # Examples
///
-/// Double integer leaves with a typed handler; default mutation visits the
array:
+/// Double the integers in nested arrays; default mutation descends into the
+/// arrays:
///
/// ```
-/// # pub use tvm_ffi::{extra, match_any, match_any_internal, Dispatcher};
-/// # fn main() -> tvm_ffi::Result<()> {
/// use tvm_ffi::{
/// dispatch, structural_mutate, Any, Array, MutationView, Result,
StructuralMutatorView,
/// UnchangedOr,
/// };
///
/// struct Double;
+///
/// #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_,
Self>), Result<UnchangedOr<Any>>)]
/// impl Double {
-/// fn mutate_integer(
+/// fn mutate_int(
/// &mut self,
/// value: i64,
/// _: &mut StructuralMutatorView<'_, Self>,
@@ -1854,11 +2054,10 @@ impl StructuralMutatorObj {
/// }
/// }
///
-/// let result = structural_mutate(Array::new(vec![1_i64, 2, 3]), Double)?;
-/// let result = Array::<i64>::try_from(result)?;
-/// assert_eq!(result.iter().collect::<Vec<_>>(), vec![2, 4, 6]);
-/// # Ok(())
-/// # }
+/// let root = Array::new(vec![Array::new(vec![1_i64, 2]),
Array::new(vec![3_i64])]);
+/// let result = Array::<Array<i64>>::try_from(structural_mutate(root,
Double)?)?;
+/// assert_eq!(result.get(0)?.iter().collect::<Vec<_>>(), vec![2, 4]);
+/// # Ok::<(), tvm_ffi::Error>(())
/// ```
pub fn structural_mutate<R, H, M, D, O>(root: R, dispatcher: H) -> Result<Any>
where
diff --git a/rust/tvm-ffi/src/extra/structural_visit.rs
b/rust/tvm-ffi/src/extra/structural_visit.rs
index 82d05f4e..a1eed071 100644
--- a/rust/tvm-ffi/src/extra/structural_visit.rs
+++ b/rust/tvm-ffi/src/extra/structural_visit.rs
@@ -287,7 +287,16 @@ crate::def_object_ref! {
}
impl VisitInterrupt {
- /// Interrupt carrying an FFI-compatible payload.
+ /// Create an interrupt carrying an FFI-compatible payload back to the
caller of
+ /// the visit.
+ ///
+ /// # Arguments
+ ///
+ /// * `payload` - The payload, such as the value that was found.
+ ///
+ /// # Returns
+ ///
+ /// The interrupt.
#[inline(always)]
pub fn with<T: Into<Any>>(payload: T) -> Self {
VisitInterruptObj {
@@ -306,9 +315,16 @@ impl VisitInterrupt {
// continue; VisitInterrupt (including None/Error payloads) and Error stop
descent.
// Each ABI ingress/import and egress/export transfers ownership exactly once.
// Recursive drivers move this carrier unchanged; typed public APIs decode it.
-/// Recursive visiting and default child descent for a dispatcher.
+/// The running structural visit, passed to each handler.
///
-/// Pass the handler's `self` to visit methods to recurse with the same state.
+/// A handler that matches a node decides which children are visited: call
+/// [`default_visit`](Self::default_visit) to visit every child,
+/// [`visit`](Self::visit) to visit one value, or neither to skip the children.
+/// Pass the handler's own `self` as `dispatch` to every method. A handler
+/// returns `Ok(None)` to continue, or `Ok(Some(interrupt))` to stop the whole
+/// visit with a [`VisitInterrupt`].
+///
+/// See also [`structural_visit`].
pub struct StructuralVisitorView<'a, D, Base = StructuralVisitorObj> {
runtime: StructuralVisitorHandle,
_scope: PhantomData<(&'a mut (), fn(&mut D) -> Base, Rc<()>)>,
@@ -319,6 +335,10 @@ impl<D, Base: StructuralVisitorBase>
StructuralVisitorView<'_, D, Base> {
///
/// The reference cannot outlive its view:
///
+ /// # Returns
+ ///
+ /// The base state, borrowed for the lifetime of this view.
+ ///
/// ```compile_fail
/// use tvm_ffi::{StructuralVisitorObj, StructuralVisitorView};
/// fn retain<D>(view: &StructuralVisitorView<'_, D>) -> &'static
StructuralVisitorObj {
@@ -341,14 +361,33 @@ impl<D, Base: StructuralVisitorBase>
StructuralVisitorView<'_, D, Base> {
unsafe { &*self.runtime.cast::<Base>() }
}
+ /// Return the definition region the current value is in.
+ ///
+ /// Reflected fields marked as definitions set it while their values are
+ /// visited; it is [`DefRegionKind::None`] elsewhere.
+ ///
+ /// # Returns
+ ///
+ /// The current definition region.
#[inline(always)]
pub fn def_region_kind(&self) -> DefRegionKind {
// SAFETY: engine scopes and trusted ABI hooks store valid enum values.
unsafe { std::mem::transmute(*(*self.runtime).def_region_mode.get()) }
}
- /// Run operations under a scoped definition region.
- /// An enclosing Pattern wins; the previous region is restored on all
exits.
+ /// Run `callback` under a scoped definition region.
+ ///
+ /// An enclosing [`DefRegionKind::Pattern`] region keeps precedence; the
+ /// previous region is restored on all exits.
+ ///
+ /// # Arguments
+ ///
+ /// * `kind` - The definition region to enter.
+ /// * `callback` - Runs inside the region, with this view.
+ ///
+ /// # Returns
+ ///
+ /// The result of `callback`.
#[inline(always)]
pub fn with_def_region_kind<R>(
&mut self,
@@ -358,7 +397,20 @@ impl<D, Base: StructuralVisitorBase>
StructuralVisitorView<'_, D, Base> {
StructuralVisitorObj::with_def_region_kind(self.runtime, kind, ||
callback(self))
}
- /// Visit a value through the dispatcher.
+ /// Visit `value` through the dispatcher, as if the traversal had reached
it.
+ ///
+ /// # Arguments
+ ///
+ /// * `dispatch` - The handler's own `self`.
+ /// * `value` - The value to visit.
+ ///
+ /// # Returns
+ ///
+ /// `None` to continue, or the interrupt that stopped the visit.
+ ///
+ /// # Errors
+ ///
+ /// Returns the error of the handler that `value` reaches.
#[inline(always)]
pub fn visit<'input, T>(&mut self, dispatch: &mut D, value: T) ->
Result<Option<VisitInterrupt>>
where
@@ -378,8 +430,24 @@ impl<D, Base: StructuralVisitorBase>
StructuralVisitorView<'_, D, Base> {
}
}
- /// Run default descent through registered hooks or reflected fields.
- /// Children re-enter the dispatcher; the current value does not.
+ /// Visit the children of `value` through the dispatcher, without
dispatching
+ /// `value` itself again.
+ ///
+ /// Children are found by the type's registered visit hook or its reflected
+ /// fields. The visit stops at the first child that returns an interrupt.
+ ///
+ /// # Arguments
+ ///
+ /// * `dispatch` - The handler's own `self`.
+ /// * `value` - The value whose children to visit.
+ ///
+ /// # Returns
+ ///
+ /// `None` to continue, or the interrupt that stopped the visit.
+ ///
+ /// # Errors
+ ///
+ /// Returns the first error of a child's handler.
#[inline(always)]
pub fn default_visit<'input, T>(
&self,
@@ -1148,7 +1216,6 @@ impl<Base: StructuralVisitorBase>
StructuralVisitEngine<Base> {
/// Sum integer leaves while default traversal descends into the array:
///
/// ```
-/// # pub use tvm_ffi::{extra, match_any, match_any_internal, Dispatcher};
/// # fn main() -> tvm_ffi::Result<()> {
/// use tvm_ffi::{
/// dispatch, structural_visit, AnyView, Array, Result,
StructuralVisitorView,
diff --git a/rust/tvm-ffi/src/extra/unchanged.rs
b/rust/tvm-ffi/src/extra/unchanged.rs
index e7b9cd4a..94260680 100644
--- a/rust/tvm-ffi/src/extra/unchanged.rs
+++ b/rust/tvm-ffi/src/extra/unchanged.rs
@@ -28,21 +28,51 @@ use crate::error::{Error, Result, TYPE_ERROR};
use crate::tvm_ffi_sys::{TVMFFIAny, TVMFFITypeIndex};
use crate::type_traits::{AnyCompatible, ContainerElement};
-/// A structural mutation that keeps its input without acquiring another owner.
+/// A marker result that keeps the original value.
///
-/// This marker may be returned directly from a map or mutation callback. A
-/// pre-order map still descends into the original value's children.
+/// A map or mutation callback returns `Unchanged`, usually as
+/// `Unchanged.into()`, to keep the value it was given without retaining or
+/// cloning it. A pre-order map still descends into the original value's
+/// children.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::{Any, Unchanged, UnchangedOr};
+///
+/// let result: UnchangedOr<Any> = Unchanged.into();
+/// assert!(result.is_unchanged());
+/// ```
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Unchanged;
-/// A typed replacement or an [`Unchanged`] marker, stored in one FFI Any cell.
+/// Either a replacement value of type `T`, or [`Unchanged`] to keep the
+/// original.
+///
+/// Structural map and mutation callbacks return `Result<UnchangedOr<Any>>`.
+/// It is like `Option<T>` whose `None` means "keep what was there", stored in
+/// one FFI value. `UnchangedOr<Any>` converts with `.into()` from
+/// [`Unchanged`], from a managed reference or scalar, and from a typed
+/// `UnchangedOr<T>`. `T` is an FFI value type or [`Any`]. Converting the
+/// wrapper into `Any` keeps the marker; use [`value_or`](Self::value_or) or
+/// [`value_or_else`](Self::value_or_else) when an actual value is needed.
///
-/// `T` may be an FFI value type or [`Any`]. Use `Result<UnchangedOr<T>>` to
-/// propagate errors. Callbacks can return this wrapper, [`Unchanged`], a
-/// replacement value, or a `Result` containing any of these.
+/// # Examples
///
-/// Converting this wrapper to `Any` preserves the marker. Use
-/// [`Self::value_or`] or [`Self::value_or_else`] when an actual value is
needed.
+/// ```
+/// use tvm_ffi::UnchangedOr;
+///
+/// fn double_even(value: i64) -> UnchangedOr<i64> {
+/// if value % 2 == 0 {
+/// UnchangedOr::changed(value * 2)
+/// } else {
+/// UnchangedOr::unchanged()
+/// }
+/// }
+///
+/// assert_eq!(double_even(4).value_or(4), 8);
+/// assert_eq!(double_even(3).value_or(3), 3);
+/// ```
#[repr(transparent)]
pub struct UnchangedOr<T: ContainerElement = Any> {
data: Any,
@@ -50,7 +80,12 @@ pub struct UnchangedOr<T: ContainerElement = Any> {
}
impl<T: ContainerElement> UnchangedOr<T> {
- /// Keep the original value without borrowing or cloning it.
+ /// Create a result that keeps the original value, without borrowing or
cloning
+ /// it.
+ ///
+ /// # Returns
+ ///
+ /// An unchanged result.
#[inline]
pub fn unchanged() -> Self {
Self {
@@ -59,7 +94,15 @@ impl<T: ContainerElement> UnchangedOr<T> {
}
}
- /// Supply an owning replacement value.
+ /// Create a result that replaces the original value.
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The replacement.
+ ///
+ /// # Returns
+ ///
+ /// A result holding `value`.
#[inline]
pub fn changed(value: T) -> Self {
let mut raw = TVMFFIAny::new();
@@ -73,13 +116,36 @@ impl<T: ContainerElement> UnchangedOr<T> {
}
}
- /// Whether this result asks its caller to keep the original value.
+ /// Return whether this result keeps the original value.
+ ///
+ /// # Returns
+ ///
+ /// `true` if the result is unchanged.
#[inline]
pub fn is_unchanged(&self) -> bool {
is_unchanged(&self.data)
}
- /// Whether the result is unchanged or has the original shallow identity.
+ /// Return whether this result is unchanged or holds `original` itself.
+ ///
+ /// An object counts as the same only when it is the same object, not an
equal
+ /// copy. Use it to tell whether anything was rewritten.
+ ///
+ /// # Arguments
+ ///
+ /// * `original` - The original value.
+ ///
+ /// # Returns
+ ///
+ /// `true` if the result is unchanged or holds `original`.
+ ///
+ /// ```
+ /// use tvm_ffi::{Array, UnchangedOr};
+ /// let original = Array::new(vec![1_i64]);
+ ///
assert!(UnchangedOr::changed(original.clone()).unchanged_or_same_as(&original));
+ /// ```
+ ///
+ /// See also [`UnchangedOr`].
#[inline]
pub fn unchanged_or_same_as(&self, original: &T) -> bool {
if self.is_unchanged() {
@@ -93,7 +159,11 @@ impl<T: ContainerElement> UnchangedOr<T> {
raw_ffi_any_same_as(raw, *self.data.as_raw_ffi_any())
}
- /// Move out the replacement, returning None when unchanged.
+ /// Move out the replacement.
+ ///
+ /// # Returns
+ ///
+ /// The replacement, or `None` when the result is unchanged.
#[inline]
pub fn into_option(self) -> Option<T> {
if self.is_unchanged() {
@@ -106,19 +176,47 @@ impl<T: ContainerElement> UnchangedOr<T> {
}
}
- /// Move the replacement or the supplied original value out of this result.
+ /// Move out the replacement, or `original` when the result is unchanged.
+ ///
+ /// `original` is taken even when it is not returned; use
+ /// [`value_or_else`](Self::value_or_else) to produce it only when needed.
+ ///
+ /// # Arguments
+ ///
+ /// * `original` - The original value.
+ ///
+ /// # Returns
+ ///
+ /// The replacement, or `original`.
#[inline]
pub fn value_or(self, original: T) -> T {
self.value_or_else(|| original)
}
- /// Materialize the original only if this result is unchanged.
+ /// Move out the replacement, or materialize the original only if this
result is
+ /// unchanged.
+ ///
+ /// # Arguments
+ ///
+ /// * `original` - Produces the original value; called only when unchanged.
+ ///
+ /// # Returns
+ ///
+ /// The replacement, or the result of `original`.
#[inline]
pub fn value_or_else(self, original: impl FnOnce() -> T) -> T {
self.into_option().unwrap_or_else(original)
}
- /// Convert only the replacement, preserving an unchanged marker.
+ /// Convert only the replacement, preserving an unchanged result.
+ ///
+ /// # Arguments
+ ///
+ /// * `convert` - Converts the replacement; called only when there is one.
+ ///
+ /// # Returns
+ ///
+ /// The converted result.
#[inline]
pub fn map<U: ContainerElement>(self, convert: impl FnOnce(T) -> U) ->
UnchangedOr<U> {
match self.into_option() {
@@ -127,7 +225,27 @@ impl<T: ContainerElement> UnchangedOr<T> {
}
}
- /// Fallibly convert only the replacement, propagating its error unchanged.
+ /// Fallibly convert only the replacement, preserving an unchanged result.
+ ///
+ /// # Arguments
+ ///
+ /// * `convert` - Converts the replacement; called only when there is one.
+ ///
+ /// # Returns
+ ///
+ /// The converted result.
+ ///
+ /// # Errors
+ ///
+ /// Returns the error of `convert` unchanged.
+ ///
+ /// ```
+ /// use tvm_ffi::UnchangedOr;
+ /// let result =
UnchangedOr::changed(3_i64).try_map(u32::try_from).unwrap();
+ /// assert_eq!(result.into_option(), Some(3_u32));
+ /// ```
+ ///
+ /// See also [`UnchangedOr`].
#[inline]
pub fn try_map<U: ContainerElement, E>(
self,
@@ -141,9 +259,26 @@ impl<T: ContainerElement> UnchangedOr<T> {
/// Check a replacement's FFI type without cloning it.
///
- /// An unchanged result is compatible with every replacement type. Erased
- /// and narrowing conversions retain the strict type check used by
- /// [`Any::try_as`]. For a known typed conversion, use `map(Into::into)`.
+ /// An unchanged result is compatible with every replacement type. Erased
and
+ /// narrowing conversions retain the strict type check of [`Any::try_as`].
For a
+ /// known typed conversion, use `map(Into::into)`.
+ ///
+ /// # Returns
+ ///
+ /// The result typed as `UnchangedOr<U>`.
+ ///
+ /// # Errors
+ ///
+ /// Returns a `TypeError` if the replacement is not stored as `U`.
+ ///
+ /// ```
+ /// use tvm_ffi::{Any, UnchangedOr};
+ /// let erased: UnchangedOr<Any> = 7_i64.into();
+ /// assert_eq!(erased.try_cast::<i64>()?.into_option(), Some(7));
+ /// # Ok::<(), tvm_ffi::Error>(())
+ /// ```
+ ///
+ /// See also [`UnchangedOr`].
#[inline]
pub fn try_cast<U: ContainerElement>(self) -> Result<UnchangedOr<U>> {
if unsafe {
UnchangedOr::<U>::check_any_strict(self.data.as_raw_ffi_any()) } {
diff --git a/rust/tvm-ffi/src/function.rs b/rust/tvm-ffi/src/function.rs
index f375ba7e..4093b113 100644
--- a/rust/tvm-ffi/src/function.rs
+++ b/rust/tvm-ffi/src/function.rs
@@ -38,7 +38,24 @@ pub struct FunctionObj {
cell: TVMFFIFunctionCell,
}
-/// Error reference class
+/// A type-erased callable that works across languages.
+///
+/// A `Function` takes [`AnyView`] arguments and returns an [`Any`]. Make one
+/// from a Rust closure with [`from_typed`](Self::from_typed), look one up by
+/// name with [`get_global`](Self::get_global), and call it with
+/// [`call_tuple`](Self::call_tuple) or [`call_packed`](Self::call_packed).
+/// [`into_typed_fn!`](crate::into_typed_fn) turns it back into a typed
closure.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::{Function, Result};
+///
+/// let add = Function::from_typed(|x: i64, y: i64| -> Result<i64> { Ok(x + y)
});
+/// let sum = i64::try_from(add.call_tuple((1_i64, 2_i64))?)?;
+/// assert_eq!(sum, 3);
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
#[derive(Clone, ObjectRef)]
pub struct Function {
data: ObjectArc<FunctionObj>,
@@ -112,7 +129,29 @@ unsafe impl<F: Fn(&[AnyView]) -> Result<Any> + 'static>
ObjectCore for CallbackF
}
impl Function {
- /// Call the function in packed format.
+ /// Call the function with the arguments `packed_args`.
+ ///
+ /// # Arguments
+ ///
+ /// * `packed_args` - The arguments, one [`AnyView`] each.
+ ///
+ /// # Returns
+ ///
+ /// The function's result.
+ ///
+ /// # Errors
+ ///
+ /// Returns the error that the function raises.
+ ///
+ /// ```
+ /// use tvm_ffi::{AnyView, Function, Result};
+ /// let add = Function::from_typed(|x: i64, y: i64| -> Result<i64> { Ok(x
+ y) });
+ /// let result = add.call_packed(&[AnyView::from(&1_i64),
AnyView::from(&2_i64)])?;
+ /// assert_eq!(result.try_as::<i64>(), Some(3));
+ /// # Ok::<(), tvm_ffi::Error>(())
+ /// ```
+ ///
+ /// See also [`Function`].
pub fn call_packed(&self, packed_args: &[AnyView]) -> Result<Any> {
unsafe {
let packed_args_ptr = packed_args.as_ptr() as *const TVMFFIAny;
@@ -131,6 +170,32 @@ impl Function {
}
}
+ /// Call the function with the arguments in the tuple `tuple_args`.
+ ///
+ /// An element may be a value or a reference to one; a reference passes an
+ /// object without retaining it.
+ ///
+ /// # Arguments
+ ///
+ /// * `tuple_args` - The arguments, as a tuple such as `(1_i64, &array)`.
+ ///
+ /// # Returns
+ ///
+ /// The function's result.
+ ///
+ /// # Errors
+ ///
+ /// Returns the error that the function raises.
+ ///
+ /// ```
+ /// use tvm_ffi::{Function, Result};
+ /// let add = Function::from_typed(|x: i64, y: i64| -> Result<i64> { Ok(x
+ y) });
+ /// let result = add.call_tuple((1_i64, 2_i64))?;
+ /// assert_eq!(result.try_as::<i64>(), Some(3));
+ /// # Ok::<(), tvm_ffi::Error>(())
+ /// ```
+ ///
+ /// See also [`Function`].
pub fn call_tuple<TupleType>(&self, tuple_args: TupleType) -> Result<Any>
where
TupleType: TupleAsPackedArgs,
@@ -161,15 +226,22 @@ impl Function {
(&tuple_args).fill_any_view(packed_args);
self.call_packed(packed_args)
}
- /// Call function with compile-time known argument count
- /// This is an optimized version of call_tuple for when the argument count
- /// is known at compile time, avoiding the small vector optimization
overhead.
+ /// Call the function with the `LEN` arguments in `tuple_args`.
+ ///
+ /// This is [`call_tuple`](Self::call_tuple) for an argument count known at
+ /// compile time, without its small-vector overhead.
///
/// # Arguments
- /// * `tuple_args` - The tuple arguments
+ ///
+ /// * `tuple_args` - The `LEN` arguments, as a tuple.
///
/// # Returns
- /// * `Any` - The result
+ ///
+ /// The function's result.
+ ///
+ /// # Errors
+ ///
+ /// Returns the error that the function raises.
pub fn call_tuple_with_len<const LEN: usize, TupleType>(
&self,
tuple_args: TupleType,
@@ -181,14 +253,30 @@ impl Function {
(&tuple_args).fill_any_view(&mut packed_args);
self.call_packed(&packed_args)
}
- /// Get global function by name
- /// This function will throw an error if the function is not found.
+ /// Look up the global function registered as `name`.
///
/// # Arguments
- /// * `name` - The name of the function
+ ///
+ /// * `name` - The name of the function, such as `"my_ext.double"`.
///
/// # Returns
- /// * `Function` - The global function
+ ///
+ /// The global function.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if no function is registered as `name`.
+ ///
+ /// ```
+ /// use tvm_ffi::Function;
+ /// let double = Function::from_typed(|x: i64| -> tvm_ffi::Result<i64> {
Ok(x * 2) });
+ /// Function::register_global("my_ext.double", double)?;
+ /// let double = Function::get_global("my_ext.double")?;
+ /// assert_eq!(double.call_tuple((21_i64,))?.try_as::<i64>(), Some(42));
+ /// # Ok::<(), tvm_ffi::Error>(())
+ /// ```
+ ///
+ /// See also [`register_global_typed`](Self::register_global_typed).
pub fn get_global(name: &str) -> Result<Function> {
unsafe {
let name_arg = TVMFFIByteArray::from_str(name);
@@ -203,24 +291,30 @@ impl Function {
}
}
- /// Look up a reflected method of a type by type index and method name
+ /// Look up a reflected method of a type by type index and method name.
///
/// Methods registered through the C++ reflection registry
- /// (`refl::ObjectDef<T>().def(...)`) live in the per-type method table
- /// rather than the global function table. Constructors registered via
- /// `refl::init` are reachable under the reserved name `__ffi_init__`.
- /// For instance methods, the first packed argument is the object itself.
+ /// (`refl::ObjectDef<T>().def(...)`) live in the per-type method table
rather
+ /// than the global function table. Constructors registered via
`refl::init` are
+ /// reachable under the reserved name `__ffi_init__`. For instance
methods, the
+ /// first argument is the object itself.
///
- /// `type_index` must be a registered type index (e.g. obtained from a
- /// live object via `Any::type_index` or from a type key); the underlying
- /// C API treats an unregistered index as a fatal error.
+ /// `type_index` must be a registered type index, obtained for example
from a
+ /// live object via `Any::type_index` or from a type key; the C API treats
an
+ /// unregistered index as a fatal error.
///
/// # Arguments
- /// * `type_index` - The type index of the type that owns the method
- /// * `method_name` - The name of the method
+ ///
+ /// * `type_index` - The type index of the type that owns the method.
+ /// * `method_name` - The name of the method.
///
/// # Returns
- /// * `Function` - The reflected method
+ ///
+ /// The reflected method.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the type has no method `method_name`.
pub fn from_type_method(type_index: i32, method_name: &str) ->
Result<Function> {
unsafe {
let type_info = TVMFFIGetTypeInfo(type_index);
@@ -260,9 +354,22 @@ impl Function {
}
}
- /// Look up a function-valued attribute for a concrete runtime type.
+ /// Look up a function-valued attribute of a concrete runtime type.
///
/// Type attributes are not inherited from base types.
+ ///
+ /// # Arguments
+ ///
+ /// * `type_index` - The type index of the type.
+ /// * `attr_name` - The name of the attribute.
+ ///
+ /// # Returns
+ ///
+ /// The function stored in the attribute.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the type has no such attribute, or it is not a
function.
pub fn from_type_attr(type_index: i32, attr_name: &str) ->
Result<Function> {
let value = crate::reflection::get_type_attr(type_index,
attr_name).ok_or_else(|| {
crate::error::Error::new(
@@ -277,17 +384,23 @@ impl Function {
Function::try_from(value)
}
- /// Look up a reflected method of a type by type key and method name
+ /// Look up a reflected method of a type by type key and method name.
///
- /// Same as [`Function::from_type_method`], but resolves `type_key` to a
- /// type index first.
+ /// Same as [`Function::from_type_method`], but resolves `type_key` to a
type
+ /// index first.
///
/// # Arguments
- /// * `type_key` - The type key of the type that owns the method
- /// * `method_name` - The name of the method
+ ///
+ /// * `type_key` - The type key of the type that owns the method.
+ /// * `method_name` - The name of the method.
///
/// # Returns
- /// * `Function` - The reflected method
+ ///
+ /// The reflected method.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the type or the method does not exist.
pub fn from_type_key_method(type_key: &str, method_name: &str) ->
Result<Function> {
unsafe {
let type_key_arg = TVMFFIByteArray::from_str(type_key);
@@ -297,37 +410,61 @@ impl Function {
}
}
- /// Register a function as a global function
+ /// Register a function as a global function, which every language can then
+ /// look up by name.
///
- /// The function's metadata records the type schema of an untyped
- /// function, as C++ `refl::GlobalDef().def_packed` does. Use
- /// [`Function::register_global_typed`] to record the parameter and return
- /// types of a typed function, and a doc string.
+ /// The function's metadata records the type schema of an untyped
function, as
+ /// C++ `refl::GlobalDef().def_packed` does. Use
+ /// [`Function::register_global_typed`] to record the parameter and return
types
+ /// of a typed function, and a doc string.
///
/// # Arguments
- /// * `name` - The name of the function
- /// * `func` - The function to register
+ ///
+ /// * `name` - The name of the function.
+ /// * `func` - The function to register.
///
/// # Returns
- /// * `Result<()>` - The result of the registration
+ ///
+ /// `Ok(())` once the function is registered.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the registration fails.
+ ///
+ /// See also [`get_global`](Self::get_global).
pub fn register_global(name: &str, func: Function) -> Result<()> {
let type_schema =
crate::type_traits::type_schema(FunctionObj::TYPE_KEY, &[]);
Self::register_global_with_info(name, &func, &type_schema, "")
}
- /// Register a typed function as a global function, with a doc string
+ /// Register a typed function as a global function, with a doc string.
///
- /// As C++ `refl::GlobalDef().def` does, the function's metadata records
- /// its type schema, which `tvm_ffi.get_global_func_metadata` and the stub
- /// generator read. An empty `doc` records no doc string.
+ /// As C++ `refl::GlobalDef().def` does, the function's metadata records
its
+ /// type schema, which `tvm_ffi.get_global_func_metadata` and the stub
generator
+ /// read. An empty `doc` records no doc string.
///
/// # Arguments
- /// * `name` - The name of the function
- /// * `func` - The typed function to register, as for
[`Function::from_typed`]
- /// * `doc` - The doc string of the function
+ ///
+ /// * `name` - The name of the function.
+ /// * `func` - The typed function to register, as for
[`Function::from_typed`].
+ /// * `doc` - The doc string of the function.
///
/// # Returns
- /// * `Result<()>` - The result of the registration
+ ///
+ /// `Ok(())` once the function is registered.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the registration fails.
+ ///
+ /// ```
+ /// use tvm_ffi::{Function, Result};
+ /// let add = |x: i64, y: i64| -> Result<i64> { Ok(x + y) };
+ /// Function::register_global_typed("my_ext.add", add, "Add two
integers.")?;
+ /// # Ok::<(), tvm_ffi::Error>(())
+ /// ```
+ ///
+ /// See also [`get_global`](Self::get_global).
pub fn register_global_typed<F, I, O>(name: &str, func: F, doc: &str) ->
Result<()>
where
F: AsPackedCallable<I, O> + 'static,
@@ -357,15 +494,32 @@ impl Function {
crate::check_safe_call!(TVMFFIFunctionSetGlobalFromMethodInfo(&info,
can_override))
}
}
- /// Construct a function from a packed function
+ /// Construct a function from a packed function, which takes its arguments
as a
+ /// slice.
+ ///
+ /// Use it for a variable number of arguments; otherwise
+ /// [`from_typed`](Self::from_typed) converts the arguments for you. Report
+ /// errors by returning them. A panic in `func` is caught and raised to the
+ /// caller as an `InternalError`, but panicking is discouraged.
+ ///
/// # Arguments
- /// * `func` - The packed function in signature of `Fn(&[AnyView]) ->
Result<Any>`
+ ///
+ /// * `func` - The packed function, of signature `Fn(&[AnyView]) ->
Result<Any>`.
///
/// # Returns
- /// * `Function` - The function
///
- /// Report errors by returning them. A panic in `func` is caught and raised
- /// to the caller as an `InternalError`, but panicking is discouraged.
+ /// The function.
+ ///
+ /// ```
+ /// use tvm_ffi::{Any, AnyView, Function, Result};
+ /// let count = Function::from_packed(|args: &[AnyView]| -> Result<Any> {
+ /// Ok(Any::from(args.len() as i64))
+ /// });
+ /// assert_eq!(count.call_tuple((1_i64, true))?.try_as::<i64>(), Some(2));
+ /// # Ok::<(), tvm_ffi::Error>(())
+ /// ```
+ ///
+ /// See also [`Function`].
pub fn from_packed<F>(func: F) -> Self
where
F: Fn(&[AnyView]) -> Result<Any> + 'static,
@@ -379,15 +533,29 @@ impl Function {
}
}
- /// Construct a function from a typed function
+ /// Construct a function from a typed function.
+ ///
+ /// Each argument is converted to its parameter type, and a mismatch
raises a
+ /// `TypeError` to the caller. Report errors by returning them. A panic in
+ /// `func` is caught and raised to the caller as an `InternalError`, but
+ /// panicking is discouraged.
+ ///
/// # Arguments
- /// * `func` - The typed function with function signature of `F(T0, T1,
...) -> Result<O>`
+ ///
+ /// * `func` - The typed function, of signature `Fn(T0, T1, ...) ->
Result<O>`.
///
/// # Returns
- /// * `Function` - The function
///
- /// Report errors by returning them. A panic in `func` is caught and raised
- /// to the caller as an `InternalError`, but panicking is discouraged.
+ /// The function.
+ ///
+ /// ```
+ /// use tvm_ffi::{Function, Result};
+ /// let scale = Function::from_typed(|x: f64, factor: f64| -> Result<f64>
{ Ok(x * factor) });
+ /// assert_eq!(scale.call_tuple((2.0_f64, 1.5_f64))?.try_as::<f64>(),
Some(3.0));
+ /// # Ok::<(), tvm_ffi::Error>(())
+ /// ```
+ ///
+ /// See also [`Function`].
pub fn from_typed<F, I, O>(func: F) -> Self
where
F: AsPackedCallable<I, O> + 'static,
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index 6f022fdd..a8d3544f 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -16,6 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
+// Lets macro-generated `::tvm_ffi` paths resolve inside this crate too.
+extern crate self as tvm_ffi;
+
pub mod any;
pub mod big_int;
pub mod collections;
@@ -79,7 +82,223 @@ pub use crate::rvalue_ref::RValueRef;
pub use crate::static_init::run_static_init_blocks;
pub use crate::string::{Bytes, String};
pub use crate::type_traits::AnyCompatible;
-pub use tvm_ffi_macros::{def_object_ref, dispatch, match_any, rs_object};
+/// Generate runtime type dispatch from the `prefix_*` methods of an impl.
+///
+/// `#[dispatch(prefix, (First, Args...), Ret)]` on an inherent impl makes its
+/// `prefix_*` methods handlers and adds a `dispatch(first, args...) ->
+/// Option<Ret>` method. `dispatch` calls the first handler, in source order,
+/// whose first parameter matches the runtime type of `first`, and returns
+/// `None` when no handler matches. The macro also implements [`Dispatcher`]
+/// for `&Self` or `&mut Self`, so the type can drive [`structural_walk`] and
+/// the other traversals, whose engines call it and run their own default
+/// recursion; no prefix has a reserved meaning.
+///
+/// # Arguments
+///
+/// * `prefix` - The handler name prefix; `describe` selects `describe_int`,
+/// `describe_str` and so on.
+/// * `(First, Args...)` - The complete input tuple. `First`, the value to
+/// match, is `AnyView<'_>` or [`MutationView<'_>`], including aliases.
+/// `Args` are passed through: each handler takes a leading prefix of them
+/// without cloning, and need not take all of them.
+/// * `Ret` - The result type. A handler's result is converted with
+/// `Into<Ret>`. When both the handler's result and `Ret` are written
+/// `Result<..>`, the success value is converted instead, so a
+/// `Result<UnchangedOr<Expr>>` handler fits a `Result<UnchangedOr<Any>>`
+/// dispatcher.
+///
+/// # Handlers
+///
+/// A handler's first parameter selects what it matches, as for
+/// [`match_any!`]: an owned FFI value such as `i64` or `Array<Any>`, converted
+/// strictly; `&NodeObj`, which borrows the object node; `MutationView<'_, T>`,
+/// which keeps the permission to mutate in place; or `AnyView<'_>`, which
+/// matches every value and must be the last handler.
+///
+/// Only the outer `None` means that no handler matched: a handler error, or a
+/// `None` inside `Ret`, is final. Handlers share one receiver, `&self` or
+/// `&mut self`, and the same method generics and bounds; shared generics must
+/// occur in the receiver, the input tuple or `Ret`. Use the same lifetime name
+/// in an input and `Ret` to return a borrow of that input; elided output
+/// lifetimes borrow the receiver. Handler-level `cfg` attributes are not
+/// supported; configure the whole impl instead. Generate one dispatcher per
+/// type; when inference is ambiguous, give the expected result type.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::{dispatch, AnyView};
+///
+/// struct Describer;
+///
+/// #[dispatch(describe, (AnyView<'_>,), String)]
+/// impl Describer {
+/// fn describe_int(&self, value: i64) -> String {
+/// format!("integer {value}")
+/// }
+///
+/// fn describe_str(&self, value: tvm_ffi::String) -> String {
+/// format!("string {}", value.as_str())
+/// }
+/// }
+///
+/// let text = tvm_ffi::String::from("hi");
+/// assert_eq!(Describer.dispatch(AnyView::from(&1_i64)).as_deref(),
Some("integer 1"));
+/// assert_eq!(Describer.dispatch(AnyView::from(&text)).as_deref(),
Some("string hi"));
+/// assert_eq!(Describer.dispatch(AnyView::from(&1.5_f64)), None);
+/// ```
+///
+/// [`MutationView<'_>`]: crate::MutationView
+/// [`Dispatcher`]: crate::Dispatcher
+/// [`structural_walk`]: crate::structural_walk
+pub use tvm_ffi_macros::dispatch;
+
+/// Match the runtime type of a value, the way `match` matches an enum.
+///
+/// The value is an [`Any`], an [`AnyView`], a managed reference or a
+/// [`MutationView`]. It is evaluated once and taken by value; write `&value`
+/// to keep it. Arms are tried in order, and the first arm whose type matches
+/// runs; a guard that is false moves on to the next arm. The last arm must be
+/// an unguarded `_ => ...`, since new types can be registered at any time.
+///
+/// * `Type(name)` binds an owned value of `Type`, such as `i64` or
+/// `Array<Any>`.
+/// * `Type(ref name)` borrows a managed reference, and `NodeObj(ref name)`
+/// borrows the object node, without changing its reference count.
+/// * `MutationView<Type>(name)` moves the permission to mutate in place into a
+/// typed view.
+///
+/// Matching is strict: an `i64` arm does not match a stored `f64`, and a value
+/// is never truncated. Type aliases and qualified paths may name `Type`. Typed
+/// `MutationView` arms do not match a `Map`, including a nested one; use
+/// `MutationView<Any>` for those. Ordinary `Map<K, V>` arms do match.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::{match_any, Any, Array};
+///
+/// fn kind(value: &Any) -> String {
+/// match_any! {
+/// value {
+/// i64(n) => format!("integer {n}"),
+/// tvm_ffi::String(s) => format!("string {}", s.as_str()),
+/// Array<Any>(arr) => format!("array of {} items", arr.len()),
+/// _ => "something else".to_string(),
+/// }
+/// }
+/// }
+///
+/// assert_eq!(kind(&Any::from(42_i64)), "integer 42");
+/// assert_eq!(kind(&Any::from(Array::new(vec![1_i64, 2, 3]))), "array of 3
items");
+/// assert_eq!(kind(&Any::from(1.5_f64)), "something else");
+/// ```
+pub use tvm_ffi_macros::match_any;
+
+/// Define an object type in Rust, as Python's `py_class` does.
+///
+/// `#[rs_object("type.Key", options...)]` registers the struct as an object
+/// 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!`].
+///
+/// The macro adds to the struct:
+///
+/// * `#[repr(C)]` and `Deref` to the parent's object, so `obj.field` reads the
+/// fields of both.
+/// * `MyPairObj::new(base, fields...)`, which takes the fields in order: 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.
+///
+/// # Arguments
+///
+/// * `"type.Key"` - The type key.
+/// * `final` - The type has no subtypes.
+/// * `s_eq_hash_kind = "TreeNode"` - The [`SEqHashKind`] of structural
+/// equality; by default the parent's.
+/// * `child_slots = N`, `child_slots_can_overflow = bool` - Type indices kept
+/// for subtypes; 0 and `true` by default.
+/// * `reflection = path` - A `fn(&mut ObjectDef<Self>)` that registers the
+/// type's methods.
+/// * `crate = path` - Where the generated code finds tvm-ffi, for a crate that
+/// depends on it only through a re-export; by default `tvm_ffi`.
+///
+/// A field takes `#[reflect(writable, seq_hash_ignore, ...)]` for the
+/// [`FieldFlags`] of those names, and `name = "..."` or `doc = "..."` to
+/// register it under another name or with a doc string, by default its `///`
+/// comment.
+///
+/// # Requirements
+///
+/// A field's type must implement `AnyCompatible` and `Clone`; an `init_off`
+/// field's type must take all-zero bytes as a value. The parent's object must
+/// implement `ShallowCopy` and `UnsafeInit`, as `rs_object` types and
non-final
+/// C++ types bound by `tvm-ffi-stubgen` do. The type registers on its first
+/// `type_index()`, with its fields, methods, `__ffi_new__` and, unless the
+/// methods define it, `__ffi_init__`.
+///
+/// # 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,
+/// #[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"));
+/// 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
+/// `impl_object_upcast!`; a conversion into a further ancestor takes its own
+/// `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 _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::{
diff --git a/rust/tvm-ffi/src/macros.rs b/rust/tvm-ffi/src/macros.rs
index d53f4b0c..36bbe615 100644
--- a/rust/tvm-ffi/src/macros.rs
+++ b/rust/tvm-ffi/src/macros.rs
@@ -128,17 +128,38 @@ macro_rules! check_safe_call {
}};
}
-/// Create a new error with file/line info attached
+/// Return an [`Error`](crate::Error) of a kind with a formatted message from
+/// the current function.
///
-/// This macro automatically appends file/line info to the traceback
+/// `bail!(kind, fmt, args...)` formats the message as `format!` does and
+/// records the source location in the backtrace.
///
/// # Arguments
-/// * `error_kind` - The kind of the error
-/// * `msg` - The message of the error
-/// * `args` - The posisble format arguments
+///
+/// * `kind` - The kind of the error, such as
[`VALUE_ERROR`](crate::VALUE_ERROR).
+/// * `fmt` - The format string of the message.
+/// * `args` - The format arguments.
///
/// # Returns
-/// * `Result<(), Error>` - The result of the safe call
+///
+/// Returns `Err` of the error from the enclosing function.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::{bail, Result, INDEX_ERROR};
+///
+/// fn get(items: &[i64], index: usize) -> Result<i64> {
+/// if index >= items.len() {
+/// bail!(INDEX_ERROR, "index {} out of range for length {}", index,
items.len());
+/// }
+/// Ok(items[index])
+/// }
+///
+/// assert_eq!(get(&[1, 2], 1)?, 2);
+/// assert_eq!(get(&[1, 2], 5).unwrap_err().message(), "index 5 out of range
for length 2");
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
#[macro_export]
macro_rules! bail {
($error_kind:expr, $fmt:expr $(, $args:expr)* $(,)?) => {{
@@ -152,17 +173,38 @@ macro_rules! bail {
}};
}
-/// Create a new error with file/line info attached
+/// Return an [`Error`](crate::Error) from the current function unless a
+/// condition holds.
///
-/// This macro automatically appends file/line info to the traceback
+/// `ensure!(cond, kind, fmt, args...)` is [`bail!`](crate::bail)`(kind, fmt,
+/// args...)` when `cond` is false.
///
/// # Arguments
-/// * `kind` - The kind of the error
-/// * `msg` - The message of the error
-/// * `args` - The posisble format arguments
+///
+/// * `cond` - The condition that must hold.
+/// * `kind` - The kind of the error.
+/// * `fmt` - The format string of the message.
+/// * `args` - The format arguments.
///
/// # Returns
-/// * `Result<(), Error>` - The result of the safe call
+///
+/// Returns `Err` of the error from the enclosing function when `cond` is
+/// false, and does nothing otherwise.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::{ensure, Result, VALUE_ERROR};
+///
+/// fn sqrt(x: f64) -> Result<f64> {
+/// ensure!(x >= 0.0, VALUE_ERROR, "sqrt of negative number {}", x);
+/// Ok(x.sqrt())
+/// }
+///
+/// assert_eq!(sqrt(4.0)?, 2.0);
+/// assert!(sqrt(-1.0).is_err());
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
#[macro_export]
macro_rules! ensure {
($cond:expr, $error_kind:expr, $fmt:expr $(, $args:expr)* $(,)?) => {{
@@ -172,16 +214,32 @@ macro_rules! ensure {
}};
}
-/// Attach a context to a result if it is error
-///
-/// This macro automatically appends file/line info to the traceback
+/// Append the current source location to the backtrace of an `Err` result,
+/// and pass an `Ok` through.
///
/// # Arguments
-/// * `error` - The error to attach the context to
-/// * `msg` - The message of the error
+///
+/// * `result` - The result to attach the context to.
///
/// # Returns
-/// * `Result<(), Error>` - The result of the safe call
+///
+/// The result, with the location appended to an error's backtrace.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::{attach_context, bail, Result, RUNTIME_ERROR};
+///
+/// fn inner() -> Result<()> {
+/// bail!(RUNTIME_ERROR, "failed");
+/// }
+///
+/// fn outer() -> Result<()> {
+/// attach_context!(inner())
+/// }
+///
+/// assert!(outer().unwrap_err().backtrace().contains("outer"));
+/// ```
#[macro_export]
macro_rules! attach_context {
($error:expr) => {{
@@ -194,7 +252,7 @@ macro_rules! attach_context {
line!(),
$crate::function_name!()
);
- Err(Error::with_appended_backtrace(error, &context))
+ Err($crate::error::Error::with_appended_backtrace(error,
&context))
}
}
}};
diff --git a/rust/tvm-ffi/src/object.rs b/rust/tvm-ffi/src/object.rs
index 7ca7ced5..73086ed4 100644
--- a/rust/tvm-ffi/src/object.rs
+++ b/rust/tvm-ffi/src/object.rs
@@ -288,16 +288,28 @@ pub fn is_instance_of<Target:
ObjectCore>(object_type_index: i32) -> bool {
}
}
-/// Runtime-checked casting between arbitrary `ObjectRef` types.
+/// Runtime-checked casts between managed reference types, like C++
+/// `ObjectRef::as<T>`.
///
-/// 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`].
+/// Every managed reference implements this trait, so any reference can be
+/// cast to its parent, back to its own type, or to a parameterized container
+/// such as `Array<i64>`.
pub trait ObjectRefCast: ObjectRefCore + AnyCompatible {
- /// Consume `self` and rewrap the underlying object as `B` without copying.
+ /// Cast `self` to the reference type `B`, without copying the object.
+ ///
+ /// # Errors
+ ///
+ /// Returns a `TypeError` when the object is not a `B`.
+ ///
+ /// ```
+ /// use tvm_ffi::{object::ObjectRef, Array, ObjectRefCast};
+ /// let object: ObjectRef = Array::new(vec![1_i64]).try_cast()?;
+ /// let array: Array<i64> = object.try_cast()?;
+ /// assert_eq!(array.len(), 1);
+ /// # Ok::<(), tvm_ffi::Error>(())
+ /// ```
+ ///
+ /// See also [`Any::try_as`](crate::Any::try_as).
#[inline(always)]
fn try_cast<B>(self) -> crate::error::Result<B>
where
diff --git a/rust/tvm-ffi/src/string.rs b/rust/tvm-ffi/src/string.rs
index d6aa82b5..352bb2d0 100644
--- a/rust/tvm-ffi/src/string.rs
+++ b/rust/tvm-ffi/src/string.rs
@@ -29,7 +29,17 @@ use tvm_ffi_sys::{TVMFFIAny, TVMFFIAnyDataUnion,
TVMFFIByteArray, TVMFFIObject};
//-----------------------------------------------------
// Bytes
//-----------------------------------------------------
-/// ABI stable Bytes container for ffi
+/// An immutable byte string, shared with C++ as `ffi.Bytes` and with Python
+/// as `bytes`.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::Bytes;
+///
+/// let bytes = Bytes::from(&b"abc"[..]);
+/// assert_eq!(bytes.as_slice(), b"abc");
+/// ```
#[repr(C)]
pub struct Bytes {
data: TVMFFIAny,
@@ -224,7 +234,22 @@ impl Debug for Bytes {
// String
//-----------------------------------------------------
-/// ABI stable String container for ffi
+/// An immutable UTF-8 string, shared with C++ as `ffi.String` and with
+/// Python as `str`.
+///
+/// Short strings are stored inline in an [`Any`](crate::Any) without an
+/// allocation.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::String;
+///
+/// let text = String::from("hello");
+/// assert_eq!(text.as_str(), "hello");
+/// assert_eq!(text.len(), 5);
+/// assert_eq!(text, "hello");
+/// ```
#[repr(C)]
pub struct String {
data: TVMFFIAny,