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 135c2e8b [Rust][Feat] Add match_any! for object-backed values (#682)
135c2e8b is described below

commit 135c2e8bbf51a353db1c26a75404e03d03e802cb
Author: Shushi Hong <[email protected]>
AuthorDate: Thu Jul 23 00:37:24 2026 -0400

    [Rust][Feat] Add match_any! for object-backed values (#682)
    
    This PR adds Rust match_any!, a Phase 1 macro that evaluates an
    Any-compatible scrutinee once, converts it to AnyView, and tries
    object-backed target conversions in source order using the existing
    standard TryInto/TryFrom implementations. The first successful
    conversion whose optional guard passes selects its arm; a failed
    conversion or guard continues to the next arm, while a required final _
    handles non-object or unmatched values. This implementation
    intentionally does not add type-index dispatch tables, caches, custom
    pattern protocols, or heuristic dispatch.
    
    ```rust
    match_any! {
        value {
            Tensor(tensor)
                if tensor.shape().len() == 2 => ("matrix", 
tensor.shape().len()),
            Tensor(tensor) => ("tensor", tensor.shape().len()),
            Shape(shape) => ("shape", shape.len()),
            Array::<i64>(array) => ("array", array.len()),
            _ => ("unsupported", 0),
        }
    }
    ```
---
 rust/tvm-ffi-macros/src/lib.rs       |  13 +++
 rust/tvm-ffi-macros/src/match_any.rs | 161 +++++++++++++++++++++++++++++++++++
 rust/tvm-ffi-macros/src/utils.rs     |   5 +-
 rust/tvm-ffi/src/any.rs              |   7 ++
 rust/tvm-ffi/src/lib.rs              |   1 +
 rust/tvm-ffi/tests/test_match_any.rs |  61 +++++++++++++
 6 files changed, 245 insertions(+), 3 deletions(-)

diff --git a/rust/tvm-ffi-macros/src/lib.rs b/rust/tvm-ffi-macros/src/lib.rs
index 64fe3f18..aeffad1f 100644
--- a/rust/tvm-ffi-macros/src/lib.rs
+++ b/rust/tvm-ffi-macros/src/lib.rs
@@ -20,9 +20,22 @@
 use proc_macro::TokenStream;
 use proc_macro_error::proc_macro_error;
 
+mod match_any;
 mod object_macros;
 mod utils;
 
+/// Match object-backed values carried by an Any-compatible scrutinee.
+///
+/// The scrutinee may be an owned object handle, `Any`, or `AnyView`. Convert 
an
+/// already-borrowed object handle to `AnyView` before invoking the macro.
+///
+/// Non-object values skip the typed patterns and use the `_` fallback.
+#[proc_macro_error]
+#[proc_macro]
+pub fn match_any(input: TokenStream) -> TokenStream {
+    match_any::expand(input)
+}
+
 #[proc_macro_error]
 #[proc_macro_derive(Object, attributes(type_key, type_index))]
 pub fn derive_object(input: TokenStream) -> TokenStream {
diff --git a/rust/tvm-ffi-macros/src/match_any.rs 
b/rust/tvm-ffi-macros/src/match_any.rs
new file mode 100644
index 00000000..14839210
--- /dev/null
+++ b/rust/tvm-ffi-macros/src/match_any.rs
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::quote;
+use syn::parse::{Parse, ParseStream};
+use syn::{braced, parenthesized, Expr, Pat, Path, Result, Token};
+
+use crate::utils::get_tvm_ffi_crate;
+
+struct MatchAnyInput {
+    scrutinee: Expr,
+    arms: Vec<TypedArm>,
+    fallback: Expr,
+}
+
+struct TypedArm {
+    matcher: Path,
+    binding: Pat,
+    guard: Option<Expr>,
+    body: Expr,
+}
+
+impl Parse for MatchAnyInput {
+    fn parse(input: ParseStream<'_>) -> Result<Self> {
+        let scrutinee = input.call(Expr::parse_without_eager_brace)?;
+        let content;
+        braced!(content in input);
+
+        let mut arms = Vec::new();
+        let mut fallback = None;
+        while !content.is_empty() {
+            if fallback.is_some() {
+                return Err(content.error("the `_` fallback must be the final 
arm"));
+            }
+
+            if content.peek(Token![_]) {
+                content.parse::<Token![_]>()?;
+                if content.peek(Token![if]) {
+                    return Err(content.error("the `_` fallback cannot have a 
guard"));
+                }
+                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"));
+                }
+                let guard = if content.peek(Token![if]) {
+                    content.parse::<Token![if]>()?;
+                    Some(content.parse::<Expr>()?)
+                } else {
+                    None
+                };
+                content.parse::<Token![=>]>()?;
+                let body = content.parse::<Expr>()?;
+                arms.push(TypedArm {
+                    matcher,
+                    binding,
+                    guard,
+                    body,
+                });
+            }
+
+            if content.peek(Token![,]) {
+                content.parse::<Token![,]>()?;
+            } else if !content.is_empty() {
+                return Err(content.error("expected `,` between match_any! 
arms"));
+            }
+        }
+
+        let fallback = fallback
+            .ok_or_else(|| content.error("match_any! requires a final `_` 
fallback arm"))?;
+        Ok(Self {
+            scrutinee,
+            arms,
+            fallback,
+        })
+    }
+}
+
+pub fn expand(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let input = syn::parse_macro_input!(input as MatchAnyInput);
+    expand_match_any(input).into()
+}
+
+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 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 scrutinee = input.scrutinee;
+    let fallback = input.fallback;
+    let dispatch_fallback = fallback.clone();
+    let arms = input.arms;
+    let dispatch = arms
+        .into_iter()
+        .rev()
+        .fold(quote!({ #dispatch_fallback }), |next, arm| {
+            let matcher = arm.matcher;
+            let binding = arm.binding;
+            let body = arm.body;
+            let matched = if let Some(guard) = arm.guard {
+                quote!(::core::result::Result::Ok(#binding) if #guard)
+            } else {
+                quote!(::core::result::Result::Ok(#binding))
+            };
+
+            quote! {
+                match ::core::convert::TryInto::<#matcher>::try_into(#view) {
+                    #matched => { #body },
+                    #rejected => {
+                        ::core::mem::drop(#rejected);
+                        #next
+                    },
+                }
+            }
+        });
+
+    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 {},
+            };
+            if #view.type_index()
+                >= #tvm_ffi::TypeIndex::kTVMFFIStaticObjectBegin as i32
+            {
+                #dispatch
+            } else {
+                #fallback
+            }
+        }
+    }
+}
diff --git a/rust/tvm-ffi-macros/src/utils.rs b/rust/tvm-ffi-macros/src/utils.rs
index da86534f..b1ebd5f1 100644
--- a/rust/tvm-ffi-macros/src/utils.rs
+++ b/rust/tvm-ffi-macros/src/utils.rs
@@ -20,10 +20,9 @@ use proc_macro2::TokenStream;
 use quote::quote;
 use std::env;
 
-/// Get the tvm-rt crate name
-/// \return The tvm-rt crate name
+/// Return the path used to reference the `tvm-ffi` crate in generated code.
 pub(crate) fn get_tvm_ffi_crate() -> TokenStream {
-    if env::var("CARGO_PKG_NAME").unwrap() == "tvm-ffi" {
+    if env::var("CARGO_CRATE_NAME").unwrap() == "tvm_ffi" {
         quote!(crate)
     } else {
         quote!(tvm_ffi)
diff --git a/rust/tvm-ffi/src/any.rs b/rust/tvm-ffi/src/any.rs
index ecf8b9ea..0c4c8476 100644
--- a/rust/tvm-ffi/src/any.rs
+++ b/rust/tvm-ffi/src/any.rs
@@ -103,6 +103,13 @@ impl<'a, T: AnyCompatible> From<&'a T> for AnyView<'a> {
     }
 }
 
+impl<'a> From<&AnyView<'a>> for AnyView<'a> {
+    #[inline]
+    fn from(value: &AnyView<'a>) -> Self {
+        *value
+    }
+}
+
 impl Default for AnyView<'_> {
     fn default() -> Self {
         Self::new()
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index 72b8d160..d5e2b2e8 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -49,6 +49,7 @@ pub use crate::object::{Object, ObjectArc, ObjectCore, 
ObjectCoreWithExtraItems,
 pub use crate::optional::Optional;
 pub use crate::string::{Bytes, String};
 pub use crate::type_traits::AnyCompatible;
+pub use tvm_ffi_macros::match_any;
 
 pub use tvm_ffi_sys::TVMFFITypeIndex as TypeIndex;
 pub use tvm_ffi_sys::{
diff --git a/rust/tvm-ffi/tests/test_match_any.rs 
b/rust/tvm-ffi/tests/test_match_any.rs
new file mode 100644
index 00000000..de40b482
--- /dev/null
+++ b/rust/tvm-ffi/tests/test_match_any.rs
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use tvm_ffi::{match_any, Any, AnyView, Array, Map, Shape, Tensor};
+
+#[test]
+fn matches_concrete_object_containers_in_source_order() {
+    fn classify(expr: Any) -> (&'static str, usize) {
+        match_any! {
+            expr {
+                Tensor(tensor)
+                    if tensor.shape().len() == 2 => ("matrix", 
tensor.shape().len()),
+                Tensor(tensor) => ("tensor", tensor.shape().len()),
+                Shape(shape) => ("shape", shape.len()),
+                Array::<i64>(array) => ("array", array.len()),
+                _ => ("unsupported", 0),
+            }
+        }
+    }
+
+    let matrix = Tensor::from_slice(&[0_f32; 6], &[2, 3]).unwrap();
+    let volume = Tensor::from_slice(&[0_f32; 24], &[2, 3, 4]).unwrap();
+    let shape = Shape::from([2_i64, 3, 4, 5]);
+    let array = [1_i64, 2, 3].into_iter().collect::<Array<i64>>();
+
+    assert_eq!(classify(Any::from(matrix)), ("matrix", 2));
+    assert_eq!(classify(Any::from(volume)), ("tensor", 3));
+    assert_eq!(classify(Any::from(shape)), ("shape", 4));
+    assert_eq!(classify(Any::from(array)), ("array", 3));
+    assert_eq!(
+        classify(Any::from(Map::<i64, i64>::default())),
+        ("unsupported", 0)
+    );
+    assert_eq!(classify(Any::from(1_i64)), ("unsupported", 0));
+
+    let tensor = Tensor::from_slice(&[0_f32; 6], &[2, 3]).unwrap();
+    let view = AnyView::from(&tensor);
+    let matched_view = match_any! {
+        view {
+            Tensor(tensor) => ("tensor", tensor.shape().len()),
+            _ => ("unsupported", 0),
+        }
+    };
+    assert_eq!(matched_view, ("tensor", 2));
+}

Reply via email to