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 f135303b [Rust] Extend match_any with scalars and typed mutation views
(#899)
f135303b is described below
commit f135303be857de59a3acac84abb0dc85c192c0ca
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Nov 2 20:58:34 2026 -0500
[Rust] Extend match_any with scalars and typed mutation views (#899)
Match payloads in `Any`, `AnyView`, object references and `MutationView`
with explicit result types: `Type(value)`, borrowed `NodeObj(ref node)`,
and `MutationView<Type>(capability)`. Inputs are taken by value once;
use `&value` to retain the owner.
Typed arms match strict runtime categories and preserve stored numeric
values, with ordinary Rust aliases, qualified types, guards and an
ordered fallback. Ordinary `Map<K, V>` arms are supported; typed
mutation capabilities containing Maps remain outside the supported
scope.
---
rust/tvm-ffi-macros/src/lib.rs | 21 +-
rust/tvm-ffi-macros/src/match_any.rs | 565 +++++-------------------
rust/tvm-ffi-macros/src/object_macros.rs | 2 +
rust/tvm-ffi/src/any.rs | 25 --
rust/tvm-ffi/src/collections/array.rs | 12 +
rust/tvm-ffi/src/collections/list.rs | 6 +
rust/tvm-ffi/src/collections/map.rs | 13 +-
rust/tvm-ffi/src/extra/unchanged.rs | 4 +
rust/tvm-ffi/src/match_any_internal.rs | 385 ++++++++++++----
rust/tvm-ffi/src/type_traits.rs | 40 ++
rust/tvm-ffi/tests/test_match_any.rs | 307 ++++++++++++-
rust/tvm-ffi/tests/test_match_any_ast_lookup.rs | 67 +--
rust/tvm-ffi/tests/test_structural_mutate.rs | 12 +
13 files changed, 854 insertions(+), 605 deletions(-)
diff --git a/rust/tvm-ffi-macros/src/lib.rs b/rust/tvm-ffi-macros/src/lib.rs
index 44cabbdc..ce81c24d 100644
--- a/rust/tvm-ffi-macros/src/lib.rs
+++ b/rust/tvm-ffi-macros/src/lib.rs
@@ -39,20 +39,19 @@ pub fn dispatch(attr: TokenStream, item: TokenStream) ->
TokenStream {
dispatch::dispatch(attr, item)
}
-/// Match object-backed values carried by an Any-compatible scrutinee.
+/// Match payloads in `Any`, `AnyView`, object references or `MutationView`.
///
-/// The scrutinee may be an owned object handle, `Any`, or `AnyView`. Convert
an
-/// already-borrowed object handle to `AnyView` before invoking the macro.
+/// 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.
///
-/// Typed arms are checked conversions, not native Rust destructuring.
-/// `Handle(value)` converts to an owning handle. `NodeType(ref node)` borrows
-/// an `ObjectCore` node without acquiring ownership; the binding is
`&NodeType`.
-/// For owning or custom matcher types, `Matcher(ref value)` retains the
existing
-/// conversion and borrows its local result. Shared node matching cannot grant
-/// mutable access, and bindings remain bounded by the scrutinee/view borrow.
-/// Guards are evaluated in source order before selecting an arm.
+/// 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.
///
-/// Non-object values skip the typed patterns and use the `_` fallback.
+/// Typed mutation capabilities do not support Map payloads, including nested
+/// Maps; use `MutationView<Any>` for those. Ordinary `Map<K, V>` arms are
supported.
#[proc_macro_error]
#[proc_macro]
pub fn match_any(input: TokenStream) -> TokenStream {
diff --git a/rust/tvm-ffi-macros/src/match_any.rs
b/rust/tvm-ffi-macros/src/match_any.rs
index 07640948..3522af8d 100644
--- a/rust/tvm-ffi-macros/src/match_any.rs
+++ b/rust/tvm-ffi-macros/src/match_any.rs
@@ -17,10 +17,10 @@
* under the License.
*/
-use proc_macro2::{Ident, Span, TokenStream};
+use proc_macro2::{Delimiter, Ident, Span, TokenStream, TokenTree};
use quote::quote;
use syn::parse::{Parse, ParseStream};
-use syn::{braced, parenthesized, Expr, Pat, Path, Result, Token};
+use syn::{braced, Expr, Pat, Result, Token, Type};
use crate::utils::get_tvm_ffi_crate;
@@ -34,7 +34,7 @@ struct MatchAnyInput {
}
struct TypedArm {
- matcher: Path,
+ matcher: Type,
binding: Pat,
guard: Option<Expr>,
body: Expr,
@@ -61,13 +61,22 @@ impl Parse for MatchAnyInput {
content.parse::<Token![=>]>()?;
fallback = Some(content.parse::<Expr>()?);
} else {
- let matcher = content.parse::<Path>()?;
- let binding_content;
- parenthesized!(binding_content in content);
- let binding = binding_content.parse::<Pat>()?;
- if !binding_content.is_empty() {
- return Err(binding_content.error("expected one binding
pattern"));
+ // The last parenthesized group is the binding, so unit,
pointer,
+ // qualified and generic Rust types retain their ordinary
syntax.
+ let mut pattern = Vec::new();
+ while !content.is_empty() && !content.peek(Token![if]) &&
!content.peek(Token![=>])
+ {
+ pattern.push(content.parse::<TokenTree>()?);
}
+ let binding = match pattern.pop() {
+ Some(TokenTree::Group(group))
+ if group.delimiter() == Delimiter::Parenthesis =>
+ {
+ syn::parse2::<Pat>(group.stream())?
+ }
+ _ => return Err(content.error("expected `Type(binding)`")),
+ };
+ let matcher =
syn::parse2::<Type>(pattern.into_iter().collect())?;
let guard = if content.peek(Token![if]) {
content.parse::<Token![if]>()?;
Some(content.parse::<Expr>()?)
@@ -108,113 +117,100 @@ pub fn expand(input: proc_macro::TokenStream) ->
proc_macro::TokenStream {
fn expand_match_any(input: MatchAnyInput) -> TokenStream {
let tvm_ffi = get_tvm_ffi_crate();
+ let span = Span::mixed_site();
+ let source = Ident::new("__tvm_ffi_match_any_source", span);
+ let probe = Ident::new("__tvm_ffi_match_any_source_probe", span);
+ let view = Ident::new("__tvm_ffi_match_any_view", span);
+ let lookup = Ident::new("__tvm_ffi_match_any_lookup", span);
let scrutinee = input.scrutinee;
let fallback = input.fallback;
let arms = input.arms;
- let can_attempt_leaf_lookup = arms.len() >= MIN_LOOKUP_TABLE_ARMS
+ let can_lookup = arms.len() >= MIN_LOOKUP_TABLE_ARMS
&& arms
.iter()
.all(|arm| arm.guard.is_none() && is_simple_binding(&arm.binding));
-
- if can_attempt_leaf_lookup {
- expand_leaf_lookup_match(&tvm_ffi, &scrutinee, &arms, &fallback)
- } else {
- expand_ordered_match(&tvm_ffi, &scrutinee, &arms, &fallback)
- }
-}
-
-fn expand_ordered_match(
- tvm_ffi: &TokenStream,
- scrutinee: &Expr,
- arms: &[TypedArm],
- fallback: &Expr,
-) -> TokenStream {
- let span = Span::mixed_site();
- let source = Ident::new("__tvm_ffi_match_any_source", span);
- let converted = Ident::new("__tvm_ffi_match_any_converted", span);
- let view = Ident::new("__tvm_ffi_match_any_view", span);
- let rejected = Ident::new("__tvm_ffi_match_any_rejected", span);
- let dispatch = expand_ordered_dispatch(tvm_ffi, arms, fallback, &view,
&rejected);
-
- quote! {
- {
- let #source = &(#scrutinee);
- let #converted: ::core::result::Result<
- #tvm_ffi::AnyView<'_>,
- ::core::convert::Infallible,
- > =
::core::convert::TryInto::<#tvm_ffi::AnyView<'_>>::try_into(#source);
- let #view = match #converted {
- ::core::result::Result::Ok(view) => view,
- ::core::result::Result::Err(error) => match error {},
+ let lookup_setup = if can_lookup {
+ let table = expand_leaf_table_lookup(&tvm_ffi, &arms, &view);
+ quote! {
+ let #lookup = {
+ let #view =
#tvm_ffi::match_any_internal::SourceView::view(&#source);
+ if #view.type_index() >=
#tvm_ffi::TypeIndex::kTVMFFIStaticObjectBegin as i32 {
+ #table
+ } else { ::core::result::Result::Err(()) }
};
- if #view.type_index()
- >= #tvm_ffi::TypeIndex::kTVMFFIStaticObjectBegin as i32
- {
- #dispatch
- } else {
- #fallback
- }
}
- }
-}
-
-fn expand_ordered_dispatch(
- tvm_ffi: &TokenStream,
- arms: &[TypedArm],
- fallback: &Expr,
- view: &Ident,
- rejected: &Ident,
-) -> TokenStream {
- expand_ordered_try_into_chain(
- tvm_ffi,
- arms,
- quote!({ #fallback }),
- view,
- rejected,
- |_, arm| {
- let binding = &arm.binding;
- let body = &arm.body;
- if let Some(guard) = &arm.guard {
- quote!(::core::result::Result::Ok(#binding) if #guard => {
#body })
- } else {
- quote!(::core::result::Result::Ok(#binding) => { #body })
- }
- },
- )
-}
-
-fn expand_ordered_try_into_chain<F>(
- tvm_ffi: &TokenStream,
- arms: &[TypedArm],
- fallback: TokenStream,
- view: &Ident,
- rejected: &Ident,
- mut matched_arm: F,
-) -> TokenStream
-where
- F: FnMut(usize, &TypedArm) -> TokenStream,
-{
- arms.iter()
- .enumerate()
- .rev()
- .fold(fallback, |next, (arm_id, arm)| {
- if let Some(binding) = shared_ref_binding(&arm.binding) {
- return expand_ref_pattern_arm(tvm_ffi, arm, binding, view,
next);
+ } else {
+ quote!()
+ };
+ let dispatch = arms.iter().enumerate().rev().fold(quote!({ #fallback }),
|next, (id, arm)| {
+ let id = id as u32;
+ let matcher = &arm.matcher;
+ let binding = &arm.binding;
+ let body = &arm.body;
+ let guard = arm.guard.as_ref().map(|guard| quote!(if #guard));
+ let conversion = if shared_ref_binding(binding).is_some() {
+ quote!({
+ use #tvm_ffi::match_any_internal::RefPatternConversion as _;
+ let probe =
#tvm_ffi::match_any_internal::PatternConversionProbe::<#matcher>::new();
+ (&&&probe).try_convert_ref(&mut #source)
+ })
+ } else {
+ quote!(<#matcher as
#tvm_ffi::match_any_internal::MatchPattern<_>>::try_match(&mut #source))
+ };
+ let conversion = if can_lookup {
+ quote!(if #lookup.is_err() || #lookup ==
::core::result::Result::Ok(::core::option::Option::Some(#id)) {
+ #conversion
+ } else { ::core::result::Result::Err(()) })
+ } else { conversion };
+ let rejected = Ident::new("__tvm_ffi_match_any_rejected", span);
+ let recovered = Ident::new("__tvm_ffi_match_any_recovered", span);
+ let recover = quote! {
+ if let ::core::result::Result::Ok(#recovered) =
#rejected.map(#tvm_ffi::match_any_internal::Matched::recover) {
+ #tvm_ffi::match_any_internal::Restore::restore(&mut #source,
#recovered);
}
- let matcher = &arm.matcher;
- let matched = matched_arm(arm_id, arm);
- let conversion = expand_pattern_conversion(tvm_ffi, matcher, view);
-
+ };
+ if let Some(binding) = shared_ref_binding(binding) {
+ let holder = Ident::new("__tvm_ffi_match_any_ref_holder", span);
+ let converted = Ident::new("__tvm_ffi_match_any_ref_converted",
span);
+ let accepted = Ident::new("__tvm_ffi_match_any_ref_accepted",
span);
+ let test_guard = arm.guard.as_ref().map(|guard|
quote!(#guard)).unwrap_or_else(|| quote!(true));
+ quote! {{
+ let #converted = #conversion;
+ let #accepted = if let ::core::result::Result::Ok(#holder) =
&#converted {
+ #[allow(unused_variables)]
+ let #binding: &#matcher =
::core::borrow::Borrow::<#matcher>::borrow(&#holder.value);
+ #test_guard
+ } else { false };
+ if #accepted {
+ let #holder = #converted.as_ref().unwrap();
+ #[allow(unused_variables)]
+ let #binding: &#matcher =
::core::borrow::Borrow::<#matcher>::borrow(&#holder.value);
+ #body
+ } else {
+ let #rejected = #converted;
+ #recover
+ #next
+ }
+ }}
+ } else {
quote! {
match #conversion {
- #matched,
+
::core::result::Result::Ok(#tvm_ffi::match_any_internal::Matched { value:
#binding, .. }) #guard => { #body },
#rejected => {
- ::core::mem::drop(#rejected);
+ #recover
#next
}
}
}
- })
+ }
+ });
+ quote! {{
+ use #tvm_ffi::match_any_internal::PrepareSource as _;
+ let mut #probe =
#tvm_ffi::match_any_internal::SourceProbe::new(#scrutinee);
+ let mut #source = #probe.prepare();
+ #lookup_setup
+ #dispatch
+ }}
}
fn shared_ref_binding(binding: &Pat) -> Option<&Ident> {
@@ -230,108 +226,11 @@ fn shared_ref_binding(binding: &Pat) -> Option<&Ident> {
}
}
-fn expand_ref_pattern_arm(
- tvm_ffi: &TokenStream,
- arm: &TypedArm,
- binding: &Ident,
- view: &Ident,
- next: TokenStream,
-) -> TokenStream {
- let holder = Ident::new("__tvm_ffi_match_any_ref_holder",
Span::mixed_site());
- let matcher = &arm.matcher;
- let body = &arm.body;
- let borrow = quote! {
- #[allow(unused_variables)]
- let #binding: &#matcher =
::core::borrow::Borrow::<#matcher>::borrow(&#holder);
- };
- let conversion = expand_ref_pattern_conversion(tvm_ffi, matcher, view);
- if let Some(guard) = &arm.guard {
- let converted = Ident::new("__tvm_ffi_match_any_ref_converted",
Span::mixed_site());
- let accepted = Ident::new("__tvm_ffi_match_any_ref_accepted",
Span::mixed_site());
- // Keep the guard binding out of the fallthrough scope. Preserve native
- // guard control flow and emit the remaining arms only once.
- quote! {{
- let #converted = #conversion;
- let #accepted = if let ::core::result::Result::Ok(#holder) =
&#converted {
- #[allow(unused_variables)]
- let #binding: &#matcher =
::core::borrow::Borrow::<#matcher>::borrow(#holder);
- #guard
- } else {
- false
- };
- if #accepted {
- let #holder = #converted.as_ref().unwrap();
- #[allow(unused_variables)]
- let #binding: &#matcher =
::core::borrow::Borrow::<#matcher>::borrow(#holder);
- #body
- } else {
- ::core::mem::drop(#converted);
- #next
- }
- }}
- } else {
- quote! {
- if let ::core::result::Result::Ok(#holder) = #conversion {
- #borrow
- #body
- } else {
- #next
- }
- }
- }
-}
-
-fn expand_ref_pattern_conversion(
- tvm_ffi: &TokenStream,
- matcher: &Path,
- view: &Ident,
-) -> TokenStream {
- let probe = Ident::new("__tvm_ffi_match_any_ref_probe",
Span::mixed_site());
- quote! {{
- use #tvm_ffi::match_any_internal::RefPatternConversion as _;
- let #probe =
#tvm_ffi::match_any_internal::PatternConversionProbe::<#matcher>::new();
- (&&&#probe).try_convert_ref(&#view)
- }}
-}
-
-fn expand_pattern_conversion(tvm_ffi: &TokenStream, matcher: &Path, view:
&Ident) -> TokenStream {
- let span = Span::mixed_site();
- let probe = Ident::new("__tvm_ffi_match_any_conversion_probe", span);
- let converted = Ident::new("__tvm_ffi_match_any_pattern_conversion", span);
-
- quote! {
- {
- use #tvm_ffi::match_any_internal::PatternConversion as _;
-
- let #probe =
-
#tvm_ffi::match_any_internal::PatternConversionProbe::<#matcher>::new();
- let #converted: ::core::result::Result<#matcher, ()> =
- (&#probe).try_convert(#view);
- #converted
- }
- }
-}
-
-fn expand_exact_pattern_conversion(
- tvm_ffi: &TokenStream,
- matcher: &Path,
- view: &Ident,
-) -> TokenStream {
- let span = Span::mixed_site();
- let probe = Ident::new("__tvm_ffi_match_any_conversion_probe", span);
- let converted = Ident::new("__tvm_ffi_match_any_pattern_conversion", span);
-
- quote! {
- {
- use #tvm_ffi::match_any_internal::PatternConversion as _;
-
- let #probe =
-
#tvm_ffi::match_any_internal::PatternConversionProbe::<#matcher>::new();
- let #converted: ::core::result::Result<#matcher, ()> = unsafe {
- (&#probe).try_convert_after_exact_match(#view)
- };
- #converted
- }
+fn is_simple_binding(binding: &Pat) -> bool {
+ match binding {
+ Pat::Ident(binding) => binding.subpat.is_none(),
+ Pat::Wild(_) => true,
+ _ => false,
}
}
@@ -349,260 +248,30 @@ fn expand_leaf_table_lookup(tvm_ffi: &TokenStream, arms:
&[TypedArm], view: &Ide
let matcher = &arm.matcher;
let metadata = if shared_ref_binding(&arm.binding).is_some() {
quote!({
- use #tvm_ffi::match_any_internal::RefPatternConversion as
_;
+ use #tvm_ffi::match_any_internal::RefPatternMetadata as _;
let probe =
#tvm_ffi::match_any_internal::PatternConversionProbe::<#matcher>::new();
(&&&probe).leaf_pattern()
})
} else {
- quote!(::core::marker::PhantomData::<#matcher>)
+
quote!(::core::marker::PhantomData::<#tvm_ffi::match_any_internal::StrictLeaf<#matcher>>)
};
quote!(#tail.prepend(#metadata))
},
);
-
- quote! {
- {
- use #tvm_ffi::match_any_internal::LeafPatternMetadata as _;
-
- let #probe =
- #pattern_probe;
- match (&#probe).leaf_pattern_list_id() {
- ::core::option::Option::Some(#pattern_list_id) => {
- static #static_table: ::std::sync::OnceLock<
- #tvm_ffi::match_any_internal::LeafLookupTable,
- > = ::std::sync::OnceLock::new();
- let #table = #static_table.get_or_init(|| {
- let mut #type_indices = [0_i32; #arm_count];
- (&#probe).fill_leaf_type_indices(&mut #type_indices);
- #tvm_ffi::match_any_internal::LeafLookupTable::build(
- #pattern_list_id,
- &#type_indices,
- )
- });
- #table.lookup(#pattern_list_id, #view.type_index())
- }
- ::core::option::Option::None => {
- ::core::result::Result::Err(())
- }
- }
- }
- }
-}
-
-// The identifiers are the hygienic names the generated code shares with its
-// caller.
-#[allow(clippy::too_many_arguments)]
-fn expand_direct_leaf_selection(
- tvm_ffi: &TokenStream,
- arms: &[TypedArm],
- arm_constants: &[Ident],
- arm_variants: &[Ident],
- arm_id: &Ident,
- view: &Ident,
- rejected: &Ident,
- selected_enum: &Ident,
- selected_value: &Ident,
-) -> TokenStream {
- let selections = arms.iter().enumerate().map(|(arm_id, arm)| {
- let matcher = &arm.matcher;
- let variant = &arm_variants[arm_id];
- let arm_constant = &arm_constants[arm_id];
- let conversion = if shared_ref_binding(&arm.binding).is_some() {
- expand_ref_pattern_conversion(tvm_ffi, matcher, view)
- } else {
- expand_exact_pattern_conversion(tvm_ffi, matcher, view)
- };
-
- quote! {
- #arm_constant => {
- match #conversion {
- ::core::result::Result::Ok(#selected_value) => {
- #selected_enum::#variant(#selected_value)
- }
- #rejected => {
- ::core::mem::drop(#rejected);
- ::core::panic!(
- "match_any! leaf lookup selected an incompatible
arm"
- )
- }
- }
- }
- }
- });
-
- quote! {
- match #arm_id {
- #(#selections,)*
- _ => ::core::unreachable!(),
- }
- }
-}
-
-fn expand_leaf_body_dispatch(
- arms: &[TypedArm],
- arm_variants: &[Ident],
- selected_enum: &Ident,
- selected: &Ident,
- fallback_variant: &Ident,
- fallback: &Expr,
-) -> TokenStream {
- let body_arms = arms.iter().enumerate().map(|(arm_id, arm)| {
- let binding = &arm.binding;
- let body = &arm.body;
- let variant = &arm_variants[arm_id];
- if let Some(binding) = shared_ref_binding(binding) {
- let holder = Ident::new("__tvm_ffi_match_any_ref_holder",
Span::mixed_site());
- let matcher = &arm.matcher;
- return quote! {
- #selected_enum::#variant(#holder) => {
- let #binding: &#matcher =
::core::borrow::Borrow::<#matcher>::borrow(&#holder);
- #body
- }
- };
- }
-
- quote! {
- #selected_enum::#variant(#binding) => {
- #body
- }
- }
- });
-
- quote! {
- match #selected {
- #(#body_arms,)*
- #selected_enum::#fallback_variant => {
- #fallback
- }
- }
- }
-}
-
-fn expand_leaf_lookup_match(
- tvm_ffi: &TokenStream,
- scrutinee: &Expr,
- arms: &[TypedArm],
- fallback: &Expr,
-) -> TokenStream {
- let span = Span::mixed_site();
- let source = Ident::new("__tvm_ffi_match_any_source", span);
- let converted = Ident::new("__tvm_ffi_match_any_converted", span);
- let view = Ident::new("__tvm_ffi_match_any_view", span);
- let rejected = Ident::new("__tvm_ffi_match_any_rejected", span);
- let arm_id = Ident::new("__tvm_ffi_match_any_arm_id", span);
- let selected = Ident::new("__tvm_ffi_match_any_selected", span);
- let selected_value = Ident::new("__tvm_ffi_match_any_selected_value",
span);
- let selected_enum = Ident::new("__TvmFfiMatchAnyArm", span);
- let fallback_variant = Ident::new("Fallback", span);
- let arm_count = arms.len();
- let arm_types = (0..arm_count)
- .map(|arm_id| Ident::new(&format!("__TvmFfiMatchAnyType{arm_id}"),
span))
- .collect::<Vec<_>>();
- let arm_variants = (0..arm_count)
- .map(|arm_id| Ident::new(&format!("Arm{arm_id}"), span))
- .collect::<Vec<_>>();
- let arm_constants = (0..arm_count)
- .map(|arm_id| Ident::new(&format!("__TVM_FFI_MATCH_ANY_ARM_{arm_id}"),
span))
- .collect::<Vec<_>>();
- let arm_constant_definitions =
- arm_constants
- .iter()
- .enumerate()
- .map(|(arm_id, arm_constant)| {
- quote! {
- const #arm_constant: #tvm_ffi::match_any_internal::ArmId =
- #arm_id as #tvm_ffi::match_any_internal::ArmId;
- }
- });
- let lookup_arm_id = expand_leaf_table_lookup(tvm_ffi, arms, &view);
- let ordered_selection = arms.iter().enumerate().rev().fold(
- quote!(#selected_enum::#fallback_variant),
- |next, (arm_id, arm)| {
- let variant = &arm_variants[arm_id];
- let conversion = if shared_ref_binding(&arm.binding).is_some() {
- expand_ref_pattern_conversion(tvm_ffi, &arm.matcher, &view)
- } else {
- expand_pattern_conversion(tvm_ffi, &arm.matcher, &view)
- };
- quote! {
- if let ::core::result::Result::Ok(#selected_value) =
#conversion {
- #selected_enum::#variant(#selected_value)
- } else {
- #next
- }
- }
- },
- );
-
- let direct_selection = expand_direct_leaf_selection(
- tvm_ffi,
- arms,
- &arm_constants,
- &arm_variants,
- &arm_id,
- &view,
- &rejected,
- &selected_enum,
- &selected_value,
- );
- let body_dispatch = expand_leaf_body_dispatch(
- arms,
- &arm_variants,
- &selected_enum,
- &selected,
- &fallback_variant,
- fallback,
- );
-
- quote! {
- {
- enum #selected_enum<#(#arm_types),*> {
- #(#arm_variants(#arm_types),)*
- #fallback_variant,
+ quote! {{
+ use #tvm_ffi::match_any_internal::LeafPatternMetadata as _;
+ let #probe = #pattern_probe;
+ match (&#probe).leaf_pattern_list_id() {
+ ::core::option::Option::Some(#pattern_list_id) => {
+ static #static_table:
::std::sync::OnceLock<#tvm_ffi::match_any_internal::LeafLookupTable> =
::std::sync::OnceLock::new();
+ let #table = #static_table.get_or_init(|| {
+ let mut #type_indices = [0_i32; #arm_count];
+ (&#probe).fill_leaf_type_indices(&mut #type_indices);
+
#tvm_ffi::match_any_internal::LeafLookupTable::build(#pattern_list_id,
&#type_indices)
+ });
+ #table.lookup(#pattern_list_id, #view.type_index())
}
-
- #(#arm_constant_definitions)*
-
- let #source = &(#scrutinee);
- let #converted: ::core::result::Result<
- #tvm_ffi::AnyView<'_>,
- ::core::convert::Infallible,
- > =
::core::convert::TryInto::<#tvm_ffi::AnyView<'_>>::try_into(#source);
- let #view = match #converted {
- ::core::result::Result::Ok(view) => view,
- ::core::result::Result::Err(error) => match error {},
- };
- let #selected =
- if #view.type_index()
- >= #tvm_ffi::TypeIndex::kTVMFFIStaticObjectBegin as i32
- {
- match #lookup_arm_id {
- ::core::result::Result::Ok(
- ::core::option::Option::Some(#arm_id),
- ) => {
- #direct_selection
- }
- ::core::result::Result::Ok(
- ::core::option::Option::None,
- ) => {
- #selected_enum::#fallback_variant
- }
- ::core::result::Result::Err(()) => {
- #ordered_selection
- }
- }
- } else {
- #selected_enum::#fallback_variant
- };
- #body_dispatch
+ ::core::option::Option::None => ::core::result::Result::Err(()),
}
- }
-}
-
-fn is_simple_binding(binding: &Pat) -> bool {
- match binding {
- Pat::Ident(binding) => binding.subpat.is_none(),
- Pat::Wild(_) => true,
- _ => false,
- }
+ }}
}
diff --git a/rust/tvm-ffi-macros/src/object_macros.rs
b/rust/tvm-ffi-macros/src/object_macros.rs
index 408b1dcc..46c6ea34 100644
--- a/rust/tvm-ffi-macros/src/object_macros.rs
+++ b/rust/tvm-ffi-macros/src/object_macros.rs
@@ -563,6 +563,8 @@ pub(crate) fn expand_object_ref(
<ContainerType as
#tvm_ffi_crate::object::ObjectCore>::TYPE_FINAL
};
+ const MATCH_ANY_STRICT_EXACT: bool = Self::MATCH_ANY_EXACT;
+
#[inline]
fn match_any_exact_type_index() -> i32 {
type ContainerType = <#struct_name as
#tvm_ffi_crate::object::ObjectRefCore>
diff --git a/rust/tvm-ffi/src/any.rs b/rust/tvm-ffi/src/any.rs
index bb90dfd7..5f0efdcc 100644
--- a/rust/tvm-ffi/src/any.rs
+++ b/rust/tvm-ffi/src/any.rs
@@ -317,31 +317,6 @@ impl Drop for Any {
}
}
-/// Convert an [`AnyView`] without constructing a diagnostic error on mismatch.
-#[inline]
-pub(crate) fn try_cast_from_any_view<T>(value: &AnyView<'_>) -> Result<T, ()>
-where
- T: AnyCompatible,
-{
- unsafe { T::try_cast_from_any_view(&value.data) }
-}
-
-/// Copy a value after exact-leaf lookup has established compatibility.
-///
-/// # Safety
-///
-/// `T::MATCH_ANY_EXACT` must be true and `value.type_index()` must equal
-/// `T::match_any_exact_type_index()`.
-#[inline(always)]
-pub(crate) unsafe fn copy_from_any_view_after_check<T>(value: &AnyView<'_>) ->
T
-where
- T: AnyCompatible,
-{
- debug_assert!(T::MATCH_ANY_EXACT);
- debug_assert!(T::check_any_strict(&value.data));
- T::copy_from_any_view_after_check(&value.data)
-}
-
// convert Any ref to AnyView
impl<'a> From<&'a Any> for AnyView<'a> {
#[inline]
diff --git a/rust/tvm-ffi/src/collections/array.rs
b/rust/tvm-ffi/src/collections/array.rs
index 59f025ef..83bc032a 100644
--- a/rust/tvm-ffi/src/collections/array.rs
+++ b/rust/tvm-ffi/src/collections/array.rs
@@ -271,6 +271,18 @@ where
true
}
+ unsafe fn match_constraints(data: &TVMFFIAny) -> bool {
+ let container = &*(data.data_union.v_obj as *const ArrayObj);
+ let base_ptr = container.data as *const TVMFFIAny;
+ for i in 0..container.size {
+ let elem_any = &*base_ptr.add(i as usize);
+ if !T::container_match_constraints(elem_any) {
+ return false;
+ }
+ }
+ true
+ }
+
unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
data.type_index = TypeIndex::kTVMFFIArray as i32;
data.data_union.v_obj = ObjectArc::as_raw(Self::data(src)) as *mut
TVMFFIObject;
diff --git a/rust/tvm-ffi/src/collections/list.rs
b/rust/tvm-ffi/src/collections/list.rs
index 63aad17d..87c5b0f6 100644
--- a/rust/tvm-ffi/src/collections/list.rs
+++ b/rust/tvm-ffi/src/collections/list.rs
@@ -584,6 +584,12 @@ unsafe impl<T: ContainerElement> AnyCompatible for List<T>
{
.all(|cell| T::container_check_any_strict(cell))
}
+ unsafe fn match_constraints(data: &TVMFFIAny) -> bool {
+ seq_cells(data)
+ .iter()
+ .all(|cell| T::container_match_constraints(cell))
+ }
+
unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
data.type_index = TypeIndex::kTVMFFIList as i32;
data.data_union.v_obj = ObjectArc::as_raw(Self::data(src)) as *mut
TVMFFIObject;
diff --git a/rust/tvm-ffi/src/collections/map.rs
b/rust/tvm-ffi/src/collections/map.rs
index 0f320a47..f96b83f9 100644
--- a/rust/tvm-ffi/src/collections/map.rs
+++ b/rust/tvm-ffi/src/collections/map.rs
@@ -291,7 +291,7 @@ where
/// Reads all entries as raw `(Any, Any)` pairs without converting to
/// `K`/`V`, propagating FFI failures instead of panicking (unlike the
- /// iterator API): this backs `check_any_strict` and
+ /// iterator API): this backs strict checks and the converting fallback in
/// `try_cast_from_any_view`, which run during argument decoding where a
/// panic could unwind across the C ABI.
fn try_raw_entries(&self) -> Result<Vec<(Any, Any)>> {
@@ -496,6 +496,17 @@ where
}
}
+ unsafe fn match_constraints(data: &TVMFFIAny) -> bool {
+ let map = <Self as
AnyCompatible>::copy_from_any_view_after_check(data);
+ match map.try_raw_entries() {
+ Ok(entries) => entries.iter().all(|(k, v)| unsafe {
+ K::container_match_constraints(k.as_raw_ffi_any())
+ && V::container_match_constraints(v.as_raw_ffi_any())
+ }),
+ Err(_) => false,
+ }
+ }
+
unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
data.type_index = TypeIndex::kTVMFFIMap as i32;
data.data_union.v_obj = ObjectArc::as_raw(Self::data(src)) as *mut
TVMFFIObject;
diff --git a/rust/tvm-ffi/src/extra/unchanged.rs
b/rust/tvm-ffi/src/extra/unchanged.rs
index 0ebdccad..e36d6ddf 100644
--- a/rust/tvm-ffi/src/extra/unchanged.rs
+++ b/rust/tvm-ffi/src/extra/unchanged.rs
@@ -246,6 +246,10 @@ unsafe impl<T: ContainerElement> AnyCompatible for
UnchangedOr<T> {
}
}
+ unsafe fn match_constraints(data: &TVMFFIAny) -> bool {
+ Unchanged::check_any_strict(data) ||
T::container_match_constraints(data)
+ }
+
unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) -> Self {
if Unchanged::check_any_strict(data) {
return Self::unchanged();
diff --git a/rust/tvm-ffi/src/match_any_internal.rs
b/rust/tvm-ffi/src/match_any_internal.rs
index d89ad994..c5fd11c6 100644
--- a/rust/tvm-ffi/src/match_any_internal.rs
+++ b/rust/tvm-ffi/src/match_any_internal.rs
@@ -28,122 +28,268 @@ use crate::{AnyCompatible, AnyView};
#[doc(hidden)]
pub type ArmId = u32;
-/// Conversion adapter used by `match_any!` typed arms.
+impl<'a, T> From<&'a crate::MutationView<'_, T>> for AnyView<'a> {
+ #[inline(always)]
+ fn from(value: &'a crate::MutationView<'_, T>) -> Self {
+ value.as_any_view()
+ }
+}
+
+/// Own the scrutinee before selecting its source adapter.
#[doc(hidden)]
-pub struct PatternConversionProbe<T>(PhantomData<fn() -> T>);
+pub struct SourceProbe<T>(Option<T>);
+impl<T> SourceProbe<T> {
+ #[inline(always)]
+ pub fn new(value: T) -> Self {
+ Self(Some(value))
+ }
+}
-impl<T> PatternConversionProbe<T> {
- #[doc(hidden)]
- pub const fn new() -> Self {
- Self(PhantomData)
+#[doc(hidden)]
+pub struct OwnedSource(crate::Any);
+#[doc(hidden)]
+pub struct ViewSource<'a>(AnyView<'a>);
+#[doc(hidden)]
+pub struct CapabilitySource<'a>(Option<crate::MutationView<'a>>);
+
+/// Object references move into an owning Any; inherent adapters preserve view
and
+/// capability lifetimes independently of the local holder's lifetime.
+#[doc(hidden)]
+pub trait PrepareSource {
+ type Source;
+ fn prepare(&mut self) -> Self::Source;
+}
+impl<T: crate::ObjectRefCore + AnyCompatible> PrepareSource for SourceProbe<T>
{
+ type Source = OwnedSource;
+ #[inline(always)]
+ fn prepare(&mut self) -> Self::Source {
+ OwnedSource(self.0.take().unwrap().into())
+ }
+}
+impl SourceProbe<crate::Any> {
+ #[inline(always)]
+ pub fn prepare(&mut self) -> OwnedSource {
+ OwnedSource(self.0.take().unwrap())
+ }
+}
+impl<'a> SourceProbe<AnyView<'a>> {
+ #[inline(always)]
+ pub fn prepare(&mut self) -> ViewSource<'a> {
+ ViewSource(self.0.take().unwrap())
+ }
+}
+impl<'a, T: ?Sized> SourceProbe<&'a T> {
+ #[inline(always)]
+ pub fn prepare<'out>(&mut self) -> ViewSource<'out>
+ where
+ AnyView<'out>: From<&'a T>,
+ {
+ ViewSource(self.0.take().unwrap().into())
+ }
+}
+impl<'a, T> SourceProbe<crate::MutationView<'a, T>> {
+ #[inline(always)]
+ pub fn prepare(&mut self) -> CapabilitySource<'a> {
+ CapabilitySource(Some(self.0.take().unwrap().into_untyped()))
}
}
-/// Prefer the lightweight `AnyCompatible` conversion while retaining a
-/// `TryInto` fallback for custom matcher types.
#[doc(hidden)]
-pub trait PatternConversion<'a, T> {
- #[doc(hidden)]
- fn try_convert(&self, view: AnyView<'a>) -> Result<T, ()>;
-
- /// Convert after exact-leaf lookup has established that `view` matches
- /// `T`. Custom `TryInto` matchers retain their checked conversion.
- ///
- /// # Safety
- ///
- /// The caller must establish the `AnyCompatible::MATCH_ANY_EXACT`
- /// contract before using the unchecked implementation.
- #[doc(hidden)]
- unsafe fn try_convert_after_exact_match(&self, view: AnyView<'a>) ->
Result<T, ()> {
- self.try_convert(view)
+pub trait SourceView {
+ fn view(&self) -> AnyView<'_>;
+}
+impl SourceView for OwnedSource {
+ #[inline(always)]
+ fn view(&self) -> AnyView<'_> {
+ (&self.0).into()
+ }
+}
+impl SourceView for ViewSource<'_> {
+ #[inline(always)]
+ fn view(&self) -> AnyView<'_> {
+ self.0
+ }
+}
+impl SourceView for CapabilitySource<'_> {
+ #[inline(always)]
+ fn view(&self) -> AnyView<'_> {
+ self.0.as_ref().unwrap().as_any_view()
}
}
-impl<'a, T: AnyCompatible> PatternConversion<'a, T> for
PatternConversionProbe<T> {
+/// A rejected guard drops an ordinary result or returns the original
capability.
+/// There is no Drop implementation, so native match guards retain Rust's usual
+/// restriction against moving their bindings before the guard succeeds.
+#[doc(hidden)]
+pub struct Matched<T, R> {
+ pub value: T,
+ recover: fn(T) -> R,
+}
+impl<T, R> Matched<T, R> {
+ #[inline(always)]
+ pub fn recover(self) -> R {
+ (self.recover)(self.value)
+ }
+}
+#[doc(hidden)]
+pub trait Restore<R> {
+ fn restore(&mut self, recovery: R);
+}
+impl<S> Restore<()> for S {
+ #[inline(always)]
+ fn restore(&mut self, _: ()) {}
+}
+impl<'a> Restore<crate::MutationView<'a>> for CapabilitySource<'a> {
#[inline(always)]
- fn try_convert(&self, view: AnyView<'a>) -> Result<T, ()> {
- if T::MATCH_ANY_EXACT {
- if view.type_index() == T::match_any_exact_type_index() {
- Ok(unsafe {
crate::any::copy_from_any_view_after_check::<T>(&view) })
+ fn restore(&mut self, recovery: crate::MutationView<'a>) {
+ debug_assert!(self.0.is_none());
+ self.0 = Some(recovery);
+ }
+}
+
+/// Type-directed matching. The source parameter also expresses whether an
+/// aliased result can retain the input lifetime or transfer its capability.
+#[doc(hidden)]
+pub trait MatchPattern<S>: Sized {
+ type Recovery;
+ fn try_match(source: S) -> Result<Matched<Self, Self::Recovery>, ()>;
+}
+impl<'b, T: crate::type_traits::ContainerElement, S: SourceView>
MatchPattern<&'b mut S> for T {
+ type Recovery = ();
+ #[inline(always)]
+ fn try_match(source: &'b mut S) -> Result<Matched<Self, ()>, ()> {
+ let view = source.view();
+ let data = view.as_raw_ffi_any();
+ unsafe {
+ if T::container_check_any_strict(data) &&
T::container_match_constraints(data) {
+ Ok(Matched {
+ value: T::container_copy_from_any_view_after_check(data),
+ recover: drop,
+ })
} else {
Err(())
}
- } else {
- crate::any::try_cast_from_any_view::<T>(&view)
}
}
-
+}
+impl<'a, 'b, 'out> MatchPattern<&'b mut ViewSource<'a>> for AnyView<'out>
+where
+ 'a: 'out,
+{
+ type Recovery = ();
#[inline(always)]
- unsafe fn try_convert_after_exact_match(&self, view: AnyView<'a>) ->
Result<T, ()> {
- Ok(crate::any::copy_from_any_view_after_check::<T>(&view))
+ fn try_match(source: &'b mut ViewSource<'a>) -> Result<Matched<Self, ()>,
()> {
+ Ok(Matched {
+ value: source.0,
+ recover: drop,
+ })
}
}
-
-impl<'a, T> PatternConversion<'a, T> for &PatternConversionProbe<T>
-where
- AnyView<'a>: TryInto<T>,
+impl<'b> MatchPattern<&'b mut OwnedSource> for AnyView<'b> {
+ type Recovery = ();
+ #[inline(always)]
+ fn try_match(source: &'b mut OwnedSource) -> Result<Matched<Self, ()>, ()>
{
+ Ok(Matched {
+ value: source.view(),
+ recover: drop,
+ })
+ }
+}
+impl<'a, 'b> MatchPattern<&'b mut CapabilitySource<'a>> for AnyView<'b> {
+ type Recovery = ();
+ #[inline(always)]
+ fn try_match(source: &'b mut CapabilitySource<'a>) -> Result<Matched<Self,
()>, ()> {
+ Ok(Matched {
+ value: source.view(),
+ recover: drop,
+ })
+ }
+}
+impl<'a, 'b, T: crate::type_traits::ContainerElement> MatchPattern<&'b mut
CapabilitySource<'a>>
+ for crate::MutationView<'a, T>
{
+ type Recovery = crate::MutationView<'a>;
#[inline(always)]
- fn try_convert(&self, view: AnyView<'a>) -> Result<T, ()> {
- view.try_into().map_err(|_| ())
+ fn try_match(
+ source: &'b mut CapabilitySource<'a>,
+ ) -> Result<Matched<Self, Self::Recovery>, ()> {
+ match source.0.take().unwrap().try_cast::<T>() {
+ Ok(value) => {
+ if unsafe {
T::container_match_constraints(value.as_any_view().as_raw_ffi_any()) } {
+ Ok(Matched {
+ value,
+ recover: crate::MutationView::into_untyped,
+ })
+ } else {
+ source.0 = Some(value.into_untyped());
+ Err(())
+ }
+ }
+ Err(value) => {
+ source.0 = Some(value);
+ Err(())
+ }
+ }
}
}
-/// Conversion for a shared `ref` binding, borrowing object nodes while
retaining
-/// the ordinary owning conversion for object handles and custom matchers.
#[doc(hidden)]
-pub trait RefPatternConversion<'view, 'value, T> {
+pub struct PatternConversionProbe<T>(PhantomData<fn() -> T>);
+impl<T> PatternConversionProbe<T> {
#[doc(hidden)]
- type LeafPattern;
+ pub const fn new() -> Self {
+ Self(PhantomData)
+ }
+}
- #[doc(hidden)]
+#[doc(hidden)]
+pub trait RefPatternMetadata {
+ type LeafPattern;
fn leaf_pattern(&self) -> PhantomData<Self::LeafPattern> {
PhantomData
}
+}
+impl<T: crate::object::ObjectCore> RefPatternMetadata for
&&PatternConversionProbe<T> {
+ type LeafPattern = BorrowedLeaf<T>;
+}
+impl<T> RefPatternMetadata for &PatternConversionProbe<T> {
+ type LeafPattern = StrictLeaf<T>;
+}
- #[doc(hidden)]
+#[doc(hidden)]
+pub trait RefPatternConversion<'b, S, T> {
type Holder: std::borrow::Borrow<T>;
-
- #[doc(hidden)]
- fn try_convert_ref(&self, view: &'view AnyView<'value>) ->
Result<Self::Holder, ()>;
+ type Recovery;
+ fn try_convert_ref(
+ &self,
+ source: &'b mut S,
+ ) -> Result<Matched<Self::Holder, Self::Recovery>, ()>;
}
-
-// The macro starts at three references. Autoderef selects the first applicable
-// implementation: borrowed ObjectCore, AnyCompatible, then custom TryInto.
-impl<'view, 'value, T: crate::object::ObjectCore> RefPatternConversion<'view,
'value, T>
+impl<'b, T: crate::object::ObjectCore, S: SourceView> RefPatternConversion<'b,
S, T>
for &&PatternConversionProbe<T>
{
- type LeafPattern = BorrowedLeaf<T>;
- type Holder = &'view T;
-
+ type Holder = &'b T;
+ type Recovery = ();
#[inline(always)]
- fn try_convert_ref(&self, view: &'view AnyView<'value>) ->
Result<Self::Holder, ()> {
- view.downcast_ref::<T>().ok_or(())
+ fn try_convert_ref(&self, source: &'b mut S) ->
Result<Matched<Self::Holder, ()>, ()> {
+ let view = source.view();
+ let node = view.downcast_ref::<T>().ok_or(())? as *const T;
+ // SAFETY: the checked node is borrowed from source for 'b. No owning
+ // conversion or capability transfer can occur while this borrow lives.
+ Ok(Matched {
+ value: unsafe { &*node },
+ recover: drop,
+ })
}
}
-
-impl<'view, 'value, T: AnyCompatible> RefPatternConversion<'view, 'value, T>
+impl<'b, S: 'b, T: MatchPattern<&'b mut S>> RefPatternConversion<'b, S, T>
for &PatternConversionProbe<T>
{
- type LeafPattern = T;
type Holder = T;
-
+ type Recovery = T::Recovery;
#[inline(always)]
- fn try_convert_ref(&self, view: &'view AnyView<'value>) ->
Result<Self::Holder, ()> {
- (*self).try_convert(*view)
- }
-}
-
-impl<'view, 'value, T> RefPatternConversion<'view, 'value, T> for
PatternConversionProbe<T>
-where
- AnyView<'value>: TryInto<T>,
-{
- type LeafPattern = T;
- type Holder = T;
-
- #[inline(always)]
- fn try_convert_ref(&self, view: &'view AnyView<'value>) ->
Result<Self::Holder, ()> {
- (*view).try_into().map_err(|_| ())
+ fn try_convert_ref(&self, source: &'b mut S) -> Result<Matched<T,
Self::Recovery>, ()> {
+ T::try_match(source)
}
}
@@ -215,18 +361,33 @@ impl LeafLookupTable {
}
/// Type-level list used to collect leaf object-pattern metadata.
+/// Its cache identity uses final object representations, so matcher lifetimes
+/// never need to be static. Unknown generic/custom types use the probe
fallback.
#[doc(hidden)]
-pub trait LeafPatternList: 'static {
+pub trait LeafPatternList {
+ #[doc(hidden)]
+ type Key: 'static;
+
#[doc(hidden)]
const ALL_EXACT_LEAF: bool;
+ /// Validate that the final payload identity describes this exact matcher.
+ #[doc(hidden)]
+ fn matches_key() -> bool;
+
#[doc(hidden)]
fn fill_leaf_type_indices(out: &mut [i32]);
}
impl LeafPatternList for () {
+ type Key = ();
const ALL_EXACT_LEAF: bool = true;
+ #[inline(always)]
+ fn matches_key() -> bool {
+ true
+ }
+
#[inline(always)]
fn fill_leaf_type_indices(out: &mut [i32]) {
debug_assert!(out.is_empty());
@@ -235,10 +396,23 @@ impl LeafPatternList for () {
impl<Head, Tail> LeafPatternList for (Head, Tail)
where
- Head: AnyCompatible + 'static,
+ Head: AnyCompatible + crate::object::ObjectRefCore,
Tail: LeafPatternList,
{
- const ALL_EXACT_LEAF: bool = Head::MATCH_ANY_EXACT && Tail::ALL_EXACT_LEAF;
+ type Key = (
+ <Head as crate::object::ObjectRefCore>::ContainerType,
+ Tail::Key,
+ );
+ const ALL_EXACT_LEAF: bool = Head::MATCH_ANY_EXACT
+ && <Head::ContainerType as crate::object::ObjectCore>::TYPE_FINAL
+ && Tail::ALL_EXACT_LEAF;
+
+ #[inline(always)]
+ fn matches_key() -> bool {
+ Head::match_any_exact_type_index()
+ == <Head::ContainerType as crate::object::ObjectCore>::type_index()
+ && Tail::matches_key()
+ }
#[inline(always)]
fn fill_leaf_type_indices(out: &mut [i32]) {
@@ -250,6 +424,53 @@ where
}
}
+/// Strict matching metadata is independent of coercive conversion metadata.
+#[doc(hidden)]
+pub struct StrictLeaf<T>(PhantomData<fn() -> T>);
+
+impl<Head, Tail> LeafPatternList for (StrictLeaf<Head>, Tail)
+where
+ Head: AnyCompatible + crate::object::ObjectRefCore,
+ Tail: LeafPatternList,
+{
+ type Key = (
+ <Head as crate::object::ObjectRefCore>::ContainerType,
+ Tail::Key,
+ );
+ const ALL_EXACT_LEAF: bool = Head::MATCH_ANY_STRICT_EXACT
+ && <Head::ContainerType as crate::object::ObjectCore>::TYPE_FINAL
+ && Tail::ALL_EXACT_LEAF;
+
+ #[inline(always)]
+ fn matches_key() -> bool {
+ Head::match_any_exact_type_index()
+ == <Head::ContainerType as crate::object::ObjectCore>::type_index()
+ && Tail::matches_key()
+ }
+
+ #[inline(always)]
+ fn fill_leaf_type_indices(out: &mut [i32]) {
+ let (head, tail) = out.split_first_mut().expect("missing leaf pattern
slot");
+ *head = Head::match_any_exact_type_index();
+ Tail::fill_leaf_type_indices(tail);
+ }
+}
+
+impl<'a, Head, Tail> LeafPatternList for (StrictLeaf<crate::MutationView<'a,
Head>>, Tail)
+where
+ Head: AnyCompatible + crate::object::ObjectRefCore,
+ Tail: LeafPatternList,
+{
+ type Key = <(StrictLeaf<Head>, Tail) as LeafPatternList>::Key;
+ const ALL_EXACT_LEAF: bool = <(StrictLeaf<Head>, Tail) as
LeafPatternList>::ALL_EXACT_LEAF;
+ fn matches_key() -> bool {
+ <(StrictLeaf<Head>, Tail) as LeafPatternList>::matches_key()
+ }
+ fn fill_leaf_type_indices(out: &mut [i32]) {
+ <(StrictLeaf<Head>, Tail) as
LeafPatternList>::fill_leaf_type_indices(out);
+ }
+}
+
/// Lifetime-free metadata for a borrowed object node pattern.
#[doc(hidden)]
pub struct BorrowedLeaf<T>(PhantomData<fn() -> T>);
@@ -257,8 +478,14 @@ pub struct BorrowedLeaf<T>(PhantomData<fn() -> T>);
impl<Head: crate::object::ObjectCore, Tail: LeafPatternList> LeafPatternList
for (BorrowedLeaf<Head>, Tail)
{
+ type Key = (Head, Tail::Key);
const ALL_EXACT_LEAF: bool = Head::TYPE_FINAL && Tail::ALL_EXACT_LEAF;
+ #[inline(always)]
+ fn matches_key() -> bool {
+ Tail::matches_key()
+ }
+
#[inline(always)]
fn fill_leaf_type_indices(out: &mut [i32]) {
let (head, tail) = out.split_first_mut().expect("missing leaf pattern
slot");
@@ -300,7 +527,7 @@ impl<T> LeafPatternMetadata for &LeafPatternProbe<T> {}
impl<T: LeafPatternList> LeafPatternMetadata for LeafPatternProbe<T> {
#[inline(always)]
fn leaf_pattern_list_id(&self) -> Option<TypeId> {
- T::ALL_EXACT_LEAF.then(TypeId::of::<T>)
+ (T::ALL_EXACT_LEAF && T::matches_key()).then(TypeId::of::<T::Key>)
}
#[inline(always)]
diff --git a/rust/tvm-ffi/src/type_traits.rs b/rust/tvm-ffi/src/type_traits.rs
index 0e3abeaf..7f6bef87 100644
--- a/rust/tvm-ffi/src/type_traits.rs
+++ b/rust/tvm-ffi/src/type_traits.rs
@@ -37,6 +37,14 @@ pub unsafe trait AnyCompatible: Sized {
#[doc(hidden)]
const MATCH_ANY_EXACT: bool = false;
+ /// Whether strict matching accepts exactly `match_any_exact_type_index()`.
+ /// Unlike `MATCH_ANY_EXACT`, this covers every tag, including scalars:
+ /// `check_any_strict` and `match_constraints` together must succeed if and
+ /// only if the index matches.
+ /// Container-element checks must have the same acceptance set.
+ #[doc(hidden)]
+ const MATCH_ANY_STRICT_EXACT: bool = false;
+
/// Return the sole runtime object type index accepted by this conversion.
#[doc(hidden)]
fn match_any_exact_type_index() -> i32 {
@@ -74,6 +82,16 @@ pub unsafe trait AnyCompatible: Sized {
// check if the value is compatible with the type
unsafe fn check_any_strict(data: &TVMFFIAny) -> bool;
+ /// Additional value-preservation checks used only by `match_any!`, after
+ /// `check_any_strict` succeeds. Ordinary conversion APIs are unchanged.
+ ///
+ /// # Safety
+ /// `data` must have passed `Self::check_any_strict`.
+ #[doc(hidden)]
+ unsafe fn match_constraints(_data: &TVMFFIAny) -> bool {
+ true
+ }
+
/// Copy value from TVMFFIAny after checking
/// caller must ensure that the value is compatible with the type
unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) -> Self;
@@ -209,6 +227,7 @@ mod container_element_ops {
unsafe fn container_copy_to_any_view(src: &Self, data: &mut TVMFFIAny);
unsafe fn container_move_to_any(src: Self, data: &mut TVMFFIAny);
unsafe fn container_check_any_strict(data: &TVMFFIAny) -> bool;
+ unsafe fn container_match_constraints(data: &TVMFFIAny) -> bool;
unsafe fn container_copy_from_any_view_after_check(data: &TVMFFIAny)
-> Self;
unsafe fn container_move_from_any_after_check(data: &mut TVMFFIAny) ->
Self;
unsafe fn container_try_cast_from_any_view(data: &TVMFFIAny) ->
Result<Self, ()>;
@@ -232,6 +251,10 @@ mod container_element_ops {
unsafe fn container_check_any_strict(data: &TVMFFIAny) -> bool {
<T as AnyCompatible>::check_any_strict(data)
}
+ #[inline]
+ unsafe fn container_match_constraints(data: &TVMFFIAny) -> bool {
+ <T as AnyCompatible>::match_constraints(data)
+ }
#[inline]
unsafe fn container_copy_from_any_view_after_check(data: &TVMFFIAny)
-> Self {
@@ -281,6 +304,10 @@ mod container_element_ops {
unsafe fn container_check_any_strict(_data: &TVMFFIAny) -> bool {
true
}
+ #[inline]
+ unsafe fn container_match_constraints(_data: &TVMFFIAny) -> bool {
+ true
+ }
#[inline]
unsafe fn container_copy_from_any_view_after_check(data: &TVMFFIAny)
-> Self {
@@ -373,6 +400,10 @@ macro_rules! impl_any_compatible_for_int {
data.type_index == TypeIndex::kTVMFFIInt as i32
}
+ unsafe fn match_constraints(data: &TVMFFIAny) -> bool {
+ <$int_type>::try_from(data.data_union.v_int64).is_ok()
+ }
+
unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) ->
Self {
data.data_union.v_int64 as $int_type
}
@@ -450,6 +481,10 @@ unsafe impl<T: AnyCompatible> AnyCompatible for Option<T> {
return T::check_any_strict(data) || data.type_index ==
TypeIndex::kTVMFFINone as i32;
}
+ unsafe fn match_constraints(data: &TVMFFIAny) -> bool {
+ data.type_index == TypeIndex::kTVMFFINone as i32 ||
T::match_constraints(data)
+ }
+
unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) -> Self {
if data.type_index == TypeIndex::kTVMFFINone as i32 {
None
@@ -534,6 +569,11 @@ macro_rules! impl_any_compatible_for_float {
data.type_index == TypeIndex::kTVMFFIFloat as i32
}
+ unsafe fn match_constraints(data: &TVMFFIAny) -> bool {
+ let value = data.data_union.v_float64;
+ ((value as $float_type) as f64).to_bits() ==
value.to_bits()
+ }
+
unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) ->
Self {
data.data_union.v_float64 as $float_type
}
diff --git a/rust/tvm-ffi/tests/test_match_any.rs
b/rust/tvm-ffi/tests/test_match_any.rs
index bcc8d025..d3906a37 100644
--- a/rust/tvm-ffi/tests/test_match_any.rs
+++ b/rust/tvm-ffi/tests/test_match_any.rs
@@ -22,15 +22,7 @@ use std::any::TypeId;
use tvm_ffi::match_any_internal::{ArmId, LeafLookupTable, LeafPatternMetadata,
LeafPatternProbe};
use tvm_ffi::{match_any, Any, AnyView, Array, Function, Map, Module, Shape,
Tensor, TypeIndex};
-struct CustomModuleMatcher;
-
-impl<'a> TryFrom<AnyView<'a>> for CustomModuleMatcher {
- type Error = &'static str;
-
- fn try_from(value: AnyView<'a>) -> Result<Self, Self::Error> {
- value.try_as::<Module>().map(|_| Self).ok_or("not a Module")
- }
-}
+type CustomModuleMatcher = Module;
#[test]
fn matches_concrete_object_containers_in_source_order() {
@@ -77,7 +69,7 @@ fn matches_concrete_object_containers_in_source_order() {
let source = Array::new(vec![1_i64, 2, 3]);
let count = || ObjectArc::strong_count(<Array<i64> as
ObjectRefCore>::data(&source));
let size = match_any! {
- source {
+ &source {
Array::<i64>(ref handle) if {
assert_eq!(count(), 2);
assert_eq!(handle.len(), 3);
@@ -100,7 +92,7 @@ fn matches_concrete_object_containers_in_source_order() {
assert_eq!(size, 3);
assert_eq!(count(), 1);
let size = match_any! {
- source {
+ &source {
Array::<i64>(ref handle) => {
let _: &Array<i64> = handle;
assert_eq!(count(), 2);
@@ -114,7 +106,7 @@ fn matches_concrete_object_containers_in_source_order() {
let mut saved = None;
assert_eq!(
match_any! {
- source {
+ &source {
Array::<i64>(ref handle) if { saved = Some(handle); true } =>
saved.unwrap().len(),
_ => 0,
}
@@ -124,17 +116,175 @@ fn matches_concrete_object_containers_in_source_order() {
let node = 7;
assert_eq!(
match_any! {
- source {
+ &source {
ArrayObj(ref node) if node.size == 0 => 0,
_ => node,
}
},
7
);
+
+ // Matching distinguishes runtime categories and rejects numeric
truncation.
+ let mut evaluations = 0;
+ let mut guards = 0;
+ assert_eq!(
+ match_any!({ evaluations += 1; Any::from(true) } {
+ i64(_) if { guards += 1; true } => 0,
+ f64(_) => 0,
+ bool(n) if { guards += 1; !n } => 0,
+ bool(n) => i32::from(n),
+ _ => 0,
+ }),
+ 1
+ );
+ assert_eq!((evaluations, guards), (1, 1));
+ assert_eq!(
+ match_any!(Any::from(1.25_f64) { f32(n) => n, _ => 0.0 }),
+ 1.25
+ );
+ for (stored, unsigned, signed) in [
+ (-129_i64, None, None),
+ (-128, None, Some(-128)),
+ (127, Some(127), Some(127)),
+ (128, Some(128), None),
+ (255, Some(255), None),
+ (256, None, None),
+ ] {
+ assert_eq!(
+ match_any!(Any::from(stored) { u8(n) => Some(n), _ => None }),
+ unsigned
+ );
+ assert_eq!(
+ match_any!(Any::from(stored) { i8(n) => Some(n), _ => None }),
+ signed
+ );
+ }
+ assert_eq!(
+ match_any!(Any::from(i64::MAX) { u64(n) => Some(n), _ => None }),
+ Some(i64::MAX as u64)
+ );
+ assert_eq!(
+ match_any!(Any::from(u64::MAX) { u64(_) => unreachable!(), i64(n) =>
n, _ => 0 }),
+ -1
+ );
+ for (stored, expected) in [
+ (-0.0, Some(0x8000_0000)),
+ (f64::INFINITY, Some(f32::INFINITY.to_bits())),
+ (f32::from_bits(0xffc0_0001) as f64, Some(0xffc0_0001)),
+ (f64::from_bits(0x7ff8_0000_0000_0001), None),
+ (16_777_217.0, None),
+ (1e40, None),
+ ] {
+ assert_eq!(
+ match_any!(Any::from(stored) {
+ f32(n) => Some(n.to_bits()),
+ f64(n) => { assert_eq!(n.to_bits(), stored.to_bits()); None },
+ _ => unreachable!(),
+ }),
+ expected
+ );
+ }
+ assert_eq!(
+ match_any!(Any::from(257_i64) { Option<u8>(n) => n, _ => None }),
+ None
+ );
+ assert_eq!(
+ match_any!(Any::from(()) { Option<u8>(n) => Some(n), _ => None }),
+ Some(None)
+ );
+
+ assert!(match_any!(Any::from(true) { bool(ref n) => *n, _ => false }));
+ assert!(match_any!(Any::from(()) { ()(()) => true, _ => false }));
+ let pointer = core::ptr::null_mut::<core::ffi::c_void>();
+ assert!(
+ match_any!(Any::from(pointer) { *mut core::ffi::c_void(p) =>
p.is_null(), _ => false })
+ );
+ trait Payload {
+ type Item;
+ }
+ struct Integer;
+ impl Payload for Integer {
+ type Item = i64;
+ }
+ assert_eq!(
+ match_any!(Any::from(7_i64) { <Integer as Payload>::Item(n) => n, _ =>
0 }),
+ 7
+ );
+ fn identity<'a>(view: AnyView<'a>) -> AnyView<'a> {
+ match_any!(view { AnyView::<'_>(rest) => rest, _ => unreachable!() })
+ }
+ assert_eq!(
+ identity(AnyView::from(&source)).debug_strong_count(),
+ Some(1)
+ );
+
+ use tvm_ffi::{InplaceMode, MutationView};
+ let mut capability_evaluations = 0;
+ let capability = match_any!({
+ capability_evaluations += 1;
+ MutationView::borrowed(AnyView::from(&source))
+ } {
+ tvm_ffi::MutationView<Array<f64>>(_) => unreachable!(),
+ tvm_ffi::MutationView<Array<i64>>(matched) if {
+ assert_eq!(matched.inplace_mode(), InplaceMode::Disallow);
+ assert_eq!(count(), 1);
+ false
+ } => unreachable!(),
+ Array<i64>(owned) if {
+ assert_eq!(owned.len(), 3);
+ assert_eq!(count(), 2);
+ false
+ } => unreachable!(),
+ ArrayObj(ref node) if { assert_eq!(count(), 1); node.size == 0 } =>
unreachable!(),
+ AnyView::<'_>(view) if { assert_eq!(view.debug_strong_count(),
Some(1)); false } => unreachable!(),
+ tvm_ffi::MutationView<Any>(rest) => rest,
+ _ => unreachable!(),
+ });
+ assert_eq!(capability_evaluations, 1);
+ assert_eq!(count(), 1);
+ assert_eq!(capability.inplace_mode(), InplaceMode::Disallow);
+ assert_eq!(
+ match_any!(&capability { ArrayObj(ref node) => node.size, _ => 0 }),
+ 3
+ );
+
+ let typed = match_any!(capability {
+ tvm_ffi::MutationView<Array<i64>>(array) => array,
+ _ => unreachable!(),
+ });
+ assert_eq!(typed.as_any_view().debug_strong_count(), Some(1));
+ // Already narrowed inputs are normalized before attempts and guard
recovery.
+ assert_eq!(
+ match_any!(typed {
+ tvm_ffi::MutationView<Array<f64>>(_) => 0,
+ ArrayObj(ref node) => node.size,
+ _ => 0,
+ }),
+ 3
+ );
+ assert!(match_any!(MutationView::borrowed(AnyView::from(&())) {
+ tvm_ffi::MutationView<()>(_unit) => true, _ => false,
+ }));
+
+ fn native_control_flow(value: AnyView<'_>) -> Result<i64, ()> {
+ for _ in 0..1 {
+ match_any!(value {
+ i64(n) if { if n < 0 { return Err(()); } true } => return
Ok(n),
+ bool(_) => continue,
+ _ => break,
+ });
+ }
+ let n = match_any!(value { f64(_) => Err(())?, _ => 0 });
+ Ok(n)
+ }
+ assert_eq!(native_control_flow(AnyView::from(&7_i64)), Ok(7));
+ assert_eq!(native_control_flow(AnyView::from(&-1_i64)), Err(()));
+ assert_eq!(native_control_flow(AnyView::from(&true)), Ok(0));
+ assert_eq!(native_control_flow(AnyView::from(&1.0_f64)), Err(()));
}
#[test]
-fn custom_try_into_matcher_keeps_ordered_compatibility() {
+fn type_alias_matcher_keeps_strict_compatibility() {
fn matches(value: AnyView<'_>) -> bool {
match_any! {
value {
@@ -177,6 +327,52 @@ fn
parameterized_containers_use_complete_conversion_semantics() {
};
assert_eq!(selected, "float array");
+ use tvm_ffi::{List, MutationView};
+ let values = Array::new(vec![257_i64]);
+ assert_eq!(
+ match_any!(&values { Array<u8>(_) => 0, Array<i64>(_) => 1, _ => 2 }),
+ 1
+ );
+ assert!(!match_any!(Array::new(vec![true]) { Array<i64>(_) => true, _ =>
false }));
+ assert!(!match_any!(&values { List<i64>(_) => true, _ => false }));
+ let list = [257_i64].into_iter().collect::<List<i64>>();
+ assert!(!match_any!(&list { Array<i64>(_) => true, List<u8>(_) => true, _
=> false }));
+ let nested = Array::new(vec![Array::new(vec![Some(257_i64)])]);
+ assert_eq!(
+ match_any!(&nested {
+ Array<Array<Option<u8>>>(_) => 0,
+ Array<Array<Option<i64>>>(_) => 1,
+ _ => 2,
+ }),
+ 1
+ );
+ let map = [(1_i64, values)]
+ .into_iter()
+ .collect::<Map<i64, Array<i64>>>();
+ assert_eq!(
+ match_any!(&map { Map<u8, Array<u8>>(_) => 0, Map<u8, Array<i64>>(_)
=> 1, _ => 2 }),
+ 1
+ );
+ // Transparent result carriers retain the same recursive numeric checks.
+ use tvm_ffi::{Unchanged, UnchangedOr};
+ assert!(!match_any!(Any::from(257_i64) { UnchangedOr<u8>(_) => true, _ =>
false }));
+ assert!(match_any!(Any::from(Unchanged) { UnchangedOr<u8>(_) => true, _ =>
false }));
+ assert!(!match_any!(MutationView::borrowed(AnyView::from(&nested)) {
+ MutationView<UnchangedOr<Array<Array<Option<u8>>>>>(_) => true,
+ _ => false,
+ }));
+ type Cap<'a, T> = MutationView<'a, T>;
+ let cap = MutationView::borrowed(AnyView::from(&nested));
+ let recovered = match_any!(cap {
+ Cap<Array<Array<Option<u8>>>>(_) => unreachable!(),
+ Cap<Array<Array<Option<i64>>>>(value) if {
+ assert_eq!(value.inplace_mode(), tvm_ffi::InplaceMode::Disallow);
+ false
+ } => unreachable!(),
+ Cap<Any>(value) => value,
+ _ => unreachable!(),
+ });
+ assert_eq!(recovered.as_any_view().debug_strong_count(), Some(1));
}
#[test]
@@ -228,4 +424,87 @@ fn metadata_only_accepts_exact_leaf_patterns() {
type Custom = (NoAnyCompatibleMetadata, ());
let custom = LeafPatternProbe::<Custom>::new();
assert!((&custom).leaf_pattern_list_id().is_none());
+
+ // Large generic and lifetime-bearing matches must compile without a static
+ // bound; their metadata does not justify a final-object table.
+ fn generic<T: tvm_ffi::AnyCompatible>(view: AnyView<'_>) -> Option<T> {
+ match_any!(view {
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ T(value) => Some(value),
+ _ => None,
+ })
+ }
+ fn generic_cap<T: tvm_ffi::AnyCompatible>(value:
tvm_ffi::MutationView<'_>) -> bool {
+ match_any!(value {
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ tvm_ffi::MutationView<T>(_) => true,
+ _ => false,
+ })
+ }
+ fn identity<'a>(view: AnyView<'a>) -> AnyView<'a> {
+ match_any!(view {
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ AnyView::<'_>(rest) => rest,
+ _ => unreachable!(),
+ })
+ }
+ assert_eq!(generic::<i64>(identity(AnyView::from(&7_i64))), Some(7));
+ assert_eq!(generic::<bool>(AnyView::from(&7_i64)), None);
+ assert!(generic_cap::<i64>(tvm_ffi::MutationView::borrowed(
+ AnyView::from(&7_i64)
+ )));
}
diff --git a/rust/tvm-ffi/tests/test_match_any_ast_lookup.rs
b/rust/tvm-ffi/tests/test_match_any_ast_lookup.rs
index 340d7c51..b422324a 100644
--- a/rust/tvm-ffi/tests/test_match_any_ast_lookup.rs
+++ b/rust/tvm-ffi/tests/test_match_any_ast_lookup.rs
@@ -117,7 +117,7 @@ fn dispatches_representative_ast_leaf_nodes() {
// The ref adapter must expose final-node metadata, including in mixed
lists.
use tvm_ffi::match_any_internal::{
- LeafPatternMetadata, LeafPatternProbe, PatternConversionProbe,
RefPatternConversion,
+ LeafPatternMetadata, LeafPatternProbe, PatternConversionProbe,
RefPatternMetadata,
};
let node_probe = PatternConversionProbe::<AddExprObj>::new();
let mixed = LeafPatternProbe::<(SubExpr,
())>::new().prepend((&&&node_probe).leaf_pattern());
@@ -129,32 +129,39 @@ fn dispatches_representative_ast_leaf_nodes() {
[AddExprObj::type_index(), SubExprObj::type_index()]
);
- fn classify(value: Any) -> &'static str {
- match_any! {
- value {
- AddExpr(_) => "add",
- SubExpr(_) => "sub",
- MulExpr(_) => "mul",
- DivExpr(_) => "div",
- ModExpr(_) => "mod",
- NegExpr(_) => "neg",
- CallExpr(_) => "call",
- LetExpr(_) => "let",
- IfExpr(_) => "if",
- TupleExpr(_) => "tuple",
- TupleGetItemExpr(_) => "tuple_get_item",
- CastExpr(_) => "cast",
- LoadExpr(_) => "load",
- StoreExpr(_) => "store",
- ForExpr(_) => "for",
- WhileExpr(_) => "while",
- SeqExpr(_) => "seq",
- ReturnExpr(_) => "return",
- ConstantExpr(_) => "constant",
- VarExpr(_) => "var",
- _ => "unsupported",
+ type Owned<T> = T;
+ use tvm_ffi::MutationView;
+ macro_rules! classify_all {
+ ($value:expr; $wrapper:ident) => {
+ match_any! {
+ $value {
+ $wrapper<AddExpr>(_) => "add",
+ $wrapper<SubExpr>(_) => "sub",
+ $wrapper<MulExpr>(_) => "mul",
+ $wrapper<DivExpr>(_) => "div",
+ $wrapper<ModExpr>(_) => "mod",
+ $wrapper<NegExpr>(_) => "neg",
+ $wrapper<CallExpr>(_) => "call",
+ $wrapper<LetExpr>(_) => "let",
+ $wrapper<IfExpr>(_) => "if",
+ $wrapper<TupleExpr>(_) => "tuple",
+ $wrapper<TupleGetItemExpr>(_) => "tuple_get_item",
+ $wrapper<CastExpr>(_) => "cast",
+ $wrapper<LoadExpr>(_) => "load",
+ $wrapper<StoreExpr>(_) => "store",
+ $wrapper<ForExpr>(_) => "for",
+ $wrapper<WhileExpr>(_) => "while",
+ $wrapper<SeqExpr>(_) => "seq",
+ $wrapper<ReturnExpr>(_) => "return",
+ $wrapper<ConstantExpr>(_) => "constant",
+ $wrapper<VarExpr>(_) => "var",
+ _ => "unsupported",
+ }
}
- }
+ };
+ }
+ fn classify(value: Any) -> &'static str {
+ classify_all!(value; Owned)
}
fn checked<T>(_: &T, value: &Any, count: usize, name: &'static str) ->
&'static str {
@@ -164,7 +171,7 @@ fn dispatches_representative_ast_leaf_nodes() {
fn classify_refs<T: ObjectCore>(value: &Any) -> &'static str {
match_any! {
- *value {
+ value {
T(ref node) => checked(node, value, 1, "add"),
SubExpr(ref handle) => checked(handle, value, 2, "sub"),
MulExprObj(ref node) => checked(node, value, 1, "mul"),
@@ -218,6 +225,12 @@ fn dispatches_representative_ast_leaf_nodes() {
assert_eq!(classify_refs::<AddExprObj>(&value), expected);
assert_eq!(value.debug_strong_count(), Some(1));
}
+ assert_eq!(classify_all!(&value; Owned), expected);
+ assert_eq!(
+
classify_all!(MutationView::borrowed(tvm_ffi::AnyView::from(&value));
MutationView),
+ expected
+ );
+ assert_eq!(value.debug_strong_count(), Some(1));
assert_eq!(classify(value), expected);
}
diff --git a/rust/tvm-ffi/tests/test_structural_mutate.rs
b/rust/tvm-ffi/tests/test_structural_mutate.rs
index 724e3f17..82c4a8fb 100644
--- a/rust/tvm-ffi/tests/test_structural_mutate.rs
+++ b/rust/tvm-ffi/tests/test_structural_mutate.rs
@@ -570,6 +570,18 @@ fn
consuming_callbacks_forward_permissions_without_temporary_owners() {
value: MutationView<'_, Array<Any>>,
ctx: &mut Mutator,
) -> Result<UnchangedOr<Any>> {
+ let mode = value.inplace_mode();
+ let count = value.as_any_view().debug_strong_count();
+ let value = tvm_ffi::match_any!(value {
+ MutationView<FfiString>(_) => unreachable!(),
+ MutationView<Array<Any>>(array) if {
+ assert_eq!(array.inplace_mode(), mode);
+ assert_eq!(array.as_any_view().debug_strong_count(),
count);
+ false
+ } => unreachable!(),
+ MutationView<Array<Any>>(array) => array,
+ _ => unreachable!(),
+ });
assert_eq!(ctx.inplace_mode(), value.inplace_mode());
self.observe(&value);
ctx.default_mutate_with_mode_result(self, value, self.requested)