From b72449de6f7e1503a08e434ce73235e1c9686883 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Mon, 5 May 2025 18:25:25 -0700 Subject: [PATCH 01/11] Added the `select_ty` macro --- crates/bevy_reflect/src/lib.rs | 1 + crates/bevy_reflect/src/macros/mod.rs | 3 + crates/bevy_reflect/src/macros/select_ty.rs | 362 ++++++++++++++++++++ 3 files changed, 366 insertions(+) create mode 100644 crates/bevy_reflect/src/macros/mod.rs create mode 100644 crates/bevy_reflect/src/macros/select_ty.rs diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 58e9b8714f..4e62e291a7 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -609,6 +609,7 @@ mod impls { pub mod attributes; mod enums; mod generics; +pub mod macros; pub mod serde; pub mod std_traits; #[cfg(feature = "debug_stack")] diff --git a/crates/bevy_reflect/src/macros/mod.rs b/crates/bevy_reflect/src/macros/mod.rs new file mode 100644 index 0000000000..74a003d362 --- /dev/null +++ b/crates/bevy_reflect/src/macros/mod.rs @@ -0,0 +1,3 @@ +pub use select_ty::*; + +mod select_ty; diff --git a/crates/bevy_reflect/src/macros/select_ty.rs b/crates/bevy_reflect/src/macros/select_ty.rs new file mode 100644 index 0000000000..0765632721 --- /dev/null +++ b/crates/bevy_reflect/src/macros/select_ty.rs @@ -0,0 +1,362 @@ +/// A helper macro for downcasting a [`PartialReflect`] value to a concrete type. +/// +/// # Syntax +/// +/// The first argument to the macro is the identifier of the variable holding the reflected value. +/// This is also the default binding for all downcasted values. +/// +/// All other arguments to the macro are match-like cases statements that follow the following pattern: +/// +/// ```text +/// => , +/// ``` +/// +/// Where `` denotes what kind of downcasting to perform: +/// - `&` - Downcasts with `try_downcast_ref` +/// - `&mut` - Downcasts with `try_downcast_mut` +/// - None - Downcasts with `try_take` +/// +/// And `` is an optional binding (i.e. ` @`) for the downcasted value. +/// +/// If the `` doesn't evaluate to `()`, an `else` case is required: +/// +/// ```text +/// else => , +/// ``` +/// +/// The `` on an `else` case can optionally be used to access a slice that contains the +/// [`Type`] of each `` in the macro. +/// This can be used as a convenience for debug messages or logging. +/// +/// # Examples +/// +/// Basic usage: +/// +/// ``` +/// # use bevy_reflect::macros::select_ty; +/// # use bevy_reflect::PartialReflect; +/// # +/// fn try_to_f32(value: &dyn PartialReflect) -> Option { +/// select_ty! {value, +/// &f32 => Some(*value), +/// &i32 => Some(*value as f32), +/// else => None +/// } +/// } +/// # +/// # assert_eq!(try_to_f32(&123_i32), Some(123_f32)); +/// # assert_eq!(try_to_f32(&123.0_f32), Some(123.0_f32)); +/// # assert_eq!(try_to_f32(&123_u32), None); +/// ``` +/// +/// With bindings: +/// +/// ``` +/// # use bevy_reflect::macros::select_ty; +/// # use bevy_reflect::PartialReflect; +/// # +/// fn try_push_value(container: &mut dyn PartialReflect, value: i32) { +/// select_ty! {container, +/// // By default, cases use the given identifier as the binding identifier +/// &mut Vec => { +/// container.push(value); +/// }, +/// // But you can also provide your own binding identifier +/// list @ &mut Vec => { +/// list.push(value as u32); +/// }, +/// // The `else` case also supports bindings. +/// // Here, `types` contains all the types from the cases above +/// types @ else => panic!("expected types: {:?}", types) +/// } +/// } +/// # +/// # let mut list: Vec = vec![1, 2]; +/// # try_push_value(&mut list, 3); +/// # assert_eq!(list, vec![1, 2, 3]); +/// # +/// # let mut list: Vec = vec![1, 2]; +/// # try_push_value(&mut list, 3); +/// # assert_eq!(list, vec![1, 2, 3]); +/// ``` +/// +/// [`PartialReflect`]: crate::PartialReflect +/// [`Type`]: crate::Type +#[macro_export] +macro_rules! select_ty { + + {$value:ident} => {}; + + {$value:ident, $($tt:tt)*} => {{ + #![allow( + clippy::allow_attributes_without_reason, + reason = "the warnings generated by these macros should only be visible to `bevy_reflect`" + )] + #![allow(unused_parens)] + + // We use an import over fully-qualified syntax so users don't have to + // cast to `dyn PartialReflect` or dereference manually + #[allow(unused_imports)] + use $crate::PartialReflect; + + select_ty!(@selector[] $value, $value, $($tt)*) + }}; + + // === Internal === // + // Each internal selector contains: + // 1. The collection of case types encountered (used to build the type slice) + // 2. The identifier of the user-given value being processed + // 3. The detected binding (or the same identifier as the value if none) + // 4. The pattern to match + + // --- Empty Case --- // + // This allows usages to contain no cases (e.g., all commented out or macro-generated) + {@selector[$($tys:ty,)*] $value:ident, $binding:ident, } => {{}}; + + // --- Else Case --- // + {@selector[$($tys:ty,)*] $value:ident, $binding:ident, else => $action:expr $(,)? } => { + $action + }; + {@selector[$($tys:ty,)*] $value:ident, $_binding:ident, $binding:ident @ else => $action:expr $(,)? } => {{ + let $binding: &[$crate::Type] = &[$($crate::Type::of::<$tys>(),)*]; + $action + }}; + + // --- Binding Matcher --- // + // This rule is used to detect an optional binding (i.e. ` @`) for each case. + // Note that its placement is _below_ the `else` rules. + // This is to prevent this binding rule from superseding the custom one for the `else` case. + {@selector[$($tys:ty,)*] $value:ident, $_old_binding:ident, $binding:ident @ $($tt:tt)+} => { + select_ty!(@selector[$($tys,)*] $value, $binding, $($tt)+) + }; + + // --- Main Cases --- // + // Note that each main case comes with two rules: a non-terminal and a terminal rule. + // The non-terminal rule is the one that can be used as an expression since it should be exhaustive. + // The terminal rule is the one that can be used for non-exhaustive statements. + + // ~~~ Mutable Borrow ~~~ // + {@selector[$($tys:ty,)*] $value:ident, $binding:ident, &mut $ty:ty => $action:expr , $($tt:tt)+} => { + match $value.as_partial_reflect_mut().try_downcast_mut::<$ty>() { + Some($binding) => $action, + None => select_ty!(@selector[$($tys,)* &mut $ty,] $value, $value, $($tt)+) + } + }; + {@selector[$($tys:ty,)*] $value:ident, $binding:ident, &mut $ty:ty => $action:expr $(,)?} => { + if let Some($binding) = $value.as_partial_reflect_mut().try_downcast_mut::<$ty>() { + $action + } + }; + + // ~~~ Immutable Borrow ~~~ // + {@selector[$($tys:ty,)*] $value:ident, $binding:ident, &$ty:ty => $action:expr , $($tt:tt)+} => { + match $value.as_partial_reflect().try_downcast_ref::<$ty>() { + Some($binding) => $action, + None => select_ty!(@selector[$($tys,)* &mut $ty,] $value, $value, $($tt)+) + } + }; + {@selector[$($tys:ty,)*] $value:ident, $binding:ident, &$ty:ty => $action:expr $(,)?} => { + if let Some($binding) = $value.as_partial_reflect().try_downcast_ref::<$ty>() { + $action + } + }; + + // ~~~ Owned ~~~ // + {@selector[$($tys:ty,)*] $value:ident, $binding:ident, $ty:ty => $action:expr , $($tt:tt)+} => { + match $value.into_partial_reflect().try_take::<$ty>() { + #[allow(unused_mut)] + Ok(mut $binding) => $action, + #[allow(unused_variables, unused_mut)] + Err(mut $value) => select_ty!(@selector[$($tys,)* $ty,] $value, $value, $($tt)+), + } + }; + {@selector[$($tys:ty,)*] $value:ident, $binding:ident, $ty:ty => $action:expr $(,)?} => { + if let Ok($binding) = $value.into_partial_reflect().try_take::<$ty>() { + $action + } + }; +} + +pub use select_ty; + +#[cfg(test)] +mod tests { + #![allow( + clippy::allow_attributes, + reason = "the warnings generated by these macros are only visible to bevy_reflect" + )] + use super::*; + use crate::{PartialReflect, Type}; + use alloc::boxed::Box; + use alloc::string::{String, ToString}; + use alloc::vec::Vec; + use alloc::{format, vec}; + + #[test] + fn should_allow_empty() { + fn empty(_value: Box) { + select_ty! {_value} + select_ty! {_value,} + } + + empty(Box::new(42)); + } + + #[test] + fn should_downcast_ref() { + fn to_string(value: &dyn PartialReflect) -> String { + select_ty! {value, + &String => value.clone(), + &i32 => value.to_string(), + &f32 => format!("{:.2}", value), + else => "unknown".to_string() + } + } + + assert_eq!(to_string(&String::from("hello")), "hello"); + assert_eq!(to_string(&42_i32), "42"); + assert_eq!(to_string(&1.2345_f32), "1.23"); + assert_eq!(to_string(&true), "unknown"); + } + + #[test] + fn should_downcast_mut() { + fn push_value(container: &mut dyn PartialReflect, value: i32) -> bool { + select_ty! {container, + &mut Vec => container.push(value), + &mut Vec => container.push(value as u32), + else => return false + } + + true + } + + let mut list: Vec = vec![1, 2]; + assert!(push_value(&mut list, 3)); + assert_eq!(list, vec![1, 2, 3]); + + let mut list: Vec = vec![1, 2]; + assert!(push_value(&mut list, 3)); + assert_eq!(list, vec![1, 2, 3]); + + let mut list: Vec = vec![String::from("hello")]; + assert!(!push_value(&mut list, 3)); + } + + #[test] + fn should_downcast_owned() { + fn into_string(value: Box) -> Option { + select_ty! {value, + String => Some(value), + i32 => Some(value.to_string()), + else => None + } + } + + let value = Box::new("hello".to_string()); + let result = into_string(value); + assert_eq!(result, Some("hello".to_string())); + + let value = Box::new(42); + let result = into_string(value); + assert_eq!(result, Some("42".to_string())); + + let value = Box::new(true); + let result = into_string(value); + assert_eq!(result, None); + } + + #[test] + fn should_allow_mixed_borrows() { + fn process(value: Box) { + select_ty! {value, + Option => { + let value = value.unwrap(); + assert_eq!(value, 1.0); + return; + }, + &Option => { + let value = value.as_ref().unwrap(); + assert_eq!(*value, 42); + return; + }, + &mut Option => { + let value = value.as_mut().unwrap(); + value.push_str(" world"); + assert_eq!(*value, "hello world"); + return; + }, + } + + panic!("test should not reach here"); + } + + process(Box::new(Some(String::from("hello")))); + process(Box::new(Some(42_i32))); + process(Box::new(Some(1.0_f32))); + } + + #[test] + fn should_allow_custom_bindings() { + fn process(mut value: Box) { + select_ty! {value, + foo @ &mut i32 => { + *foo *= 2; + assert_eq!(*foo, 246); + return; + }, + bar @ &u32 => { + assert_eq!(*bar, 42); + return; + }, + baz @ bool => { + assert!(baz); + return; + }, + } + + panic!("test should not reach here"); + } + + process(Box::new(123_i32)); + process(Box::new(42_u32)); + process(Box::new(true)); + } + + #[test] + fn should_allow_else_with_binding() { + let _value = Box::new(123); + + select_ty! {_value, + f32 => { + assert_eq!(_value, 123.0); + }, + f64 => { + assert_eq!(_value, 123.0); + }, + types @ else => { + assert_eq!(types.len(), 2); + assert_eq!(types[0], Type::of::()); + assert_eq!(types[1], Type::of::()); + }, + } + } + + #[test] + fn should_handle_slice_types() { + let _value = Box::new("hello world"); + + select_ty! {_value, + (&str) => { + return; + }, + (&[i32]) => { + panic!("unexpected type"); + }, + (&mut [u32]) => { + panic!("unexpected type"); + }, + else => panic!("unexpected type"), + } + } +} From eaaa77645570eb1988197ab324c5e2ef8b920306 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Mon, 5 May 2025 22:12:23 -0700 Subject: [PATCH 02/11] Clean up `#[allow]` attributes --- crates/bevy_reflect/src/macros/select_ty.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/crates/bevy_reflect/src/macros/select_ty.rs b/crates/bevy_reflect/src/macros/select_ty.rs index 0765632721..1b96a21ed5 100644 --- a/crates/bevy_reflect/src/macros/select_ty.rs +++ b/crates/bevy_reflect/src/macros/select_ty.rs @@ -88,15 +88,8 @@ macro_rules! select_ty { {$value:ident} => {}; {$value:ident, $($tt:tt)*} => {{ - #![allow( - clippy::allow_attributes_without_reason, - reason = "the warnings generated by these macros should only be visible to `bevy_reflect`" - )] - #![allow(unused_parens)] - // We use an import over fully-qualified syntax so users don't have to // cast to `dyn PartialReflect` or dereference manually - #[allow(unused_imports)] use $crate::PartialReflect; select_ty!(@selector[] $value, $value, $($tt)*) @@ -164,9 +157,7 @@ macro_rules! select_ty { // ~~~ Owned ~~~ // {@selector[$($tys:ty,)*] $value:ident, $binding:ident, $ty:ty => $action:expr , $($tt:tt)+} => { match $value.into_partial_reflect().try_take::<$ty>() { - #[allow(unused_mut)] Ok(mut $binding) => $action, - #[allow(unused_variables, unused_mut)] Err(mut $value) => select_ty!(@selector[$($tys,)* $ty,] $value, $value, $($tt)+), } }; @@ -183,8 +174,13 @@ pub use select_ty; mod tests { #![allow( clippy::allow_attributes, - reason = "the warnings generated by these macros are only visible to bevy_reflect" + unused_imports, + unused_parens, + unused_mut, + unused_variables, + reason = "the warnings generated by these macros should only be visible to `bevy_reflect`" )] + use super::*; use crate::{PartialReflect, Type}; use alloc::boxed::Box; @@ -347,9 +343,7 @@ mod tests { let _value = Box::new("hello world"); select_ty! {_value, - (&str) => { - return; - }, + (&str) => {}, (&[i32]) => { panic!("unexpected type"); }, From 437130aee9c1e1ded769cd59ce1c5f50808d565f Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Mon, 5 May 2025 22:50:08 -0700 Subject: [PATCH 03/11] Support `_` bindings --- crates/bevy_reflect/src/macros/select_ty.rs | 58 ++++++++++++++++----- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/crates/bevy_reflect/src/macros/select_ty.rs b/crates/bevy_reflect/src/macros/select_ty.rs index 1b96a21ed5..5d813f38df 100644 --- a/crates/bevy_reflect/src/macros/select_ty.rs +++ b/crates/bevy_reflect/src/macros/select_ty.rs @@ -17,6 +17,7 @@ /// - None - Downcasts with `try_take` /// /// And `` is an optional binding (i.e. ` @`) for the downcasted value. +/// It can also be used to bind the value to `_` to ignore the value if not needed. /// /// If the `` doesn't evaluate to `()`, an `else` case is required: /// @@ -104,13 +105,13 @@ macro_rules! select_ty { // --- Empty Case --- // // This allows usages to contain no cases (e.g., all commented out or macro-generated) - {@selector[$($tys:ty,)*] $value:ident, $binding:ident, } => {{}}; + {@selector[$($tys:ty,)*] $value:ident, $binding:tt, } => {{}}; // --- Else Case --- // - {@selector[$($tys:ty,)*] $value:ident, $binding:ident, else => $action:expr $(,)? } => { + {@selector[$($tys:ty,)*] $value:ident, $binding:tt, else => $action:expr $(,)? } => { $action }; - {@selector[$($tys:ty,)*] $value:ident, $_binding:ident, $binding:ident @ else => $action:expr $(,)? } => {{ + {@selector[$($tys:ty,)*] $value:ident, $_binding:tt, $binding:ident @ else => $action:expr $(,)? } => {{ let $binding: &[$crate::Type] = &[$($crate::Type::of::<$tys>(),)*]; $action }}; @@ -119,49 +120,68 @@ macro_rules! select_ty { // This rule is used to detect an optional binding (i.e. ` @`) for each case. // Note that its placement is _below_ the `else` rules. // This is to prevent this binding rule from superseding the custom one for the `else` case. - {@selector[$($tys:ty,)*] $value:ident, $_old_binding:ident, $binding:ident @ $($tt:tt)+} => { + {@selector[$($tys:ty,)*] $value:ident, $_old_binding:tt, $binding:tt @ $($tt:tt)+} => { select_ty!(@selector[$($tys,)*] $value, $binding, $($tt)+) }; + // --- Binding Helpers --- // + {@bind_mut _} => { + _ + }; + {@bind_mut $binding:ident} => { + mut $binding + }; + // --- Main Cases --- // // Note that each main case comes with two rules: a non-terminal and a terminal rule. // The non-terminal rule is the one that can be used as an expression since it should be exhaustive. // The terminal rule is the one that can be used for non-exhaustive statements. // ~~~ Mutable Borrow ~~~ // - {@selector[$($tys:ty,)*] $value:ident, $binding:ident, &mut $ty:ty => $action:expr , $($tt:tt)+} => { + {@selector[$($tys:ty,)*] $value:ident, $binding:tt, &mut $ty:ty => $action:expr , $($tt:tt)+} => { match $value.as_partial_reflect_mut().try_downcast_mut::<$ty>() { Some($binding) => $action, None => select_ty!(@selector[$($tys,)* &mut $ty,] $value, $value, $($tt)+) } }; - {@selector[$($tys:ty,)*] $value:ident, $binding:ident, &mut $ty:ty => $action:expr $(,)?} => { + {@selector[$($tys:ty,)*] $value:ident, $binding:tt, &mut $ty:ty => $action:expr $(,)?} => { if let Some($binding) = $value.as_partial_reflect_mut().try_downcast_mut::<$ty>() { $action } }; // ~~~ Immutable Borrow ~~~ // - {@selector[$($tys:ty,)*] $value:ident, $binding:ident, &$ty:ty => $action:expr , $($tt:tt)+} => { + {@selector[$($tys:ty,)*] $value:ident, $binding:tt, &$ty:ty => $action:expr , $($tt:tt)+} => { match $value.as_partial_reflect().try_downcast_ref::<$ty>() { Some($binding) => $action, None => select_ty!(@selector[$($tys,)* &mut $ty,] $value, $value, $($tt)+) } }; - {@selector[$($tys:ty,)*] $value:ident, $binding:ident, &$ty:ty => $action:expr $(,)?} => { + {@selector[$($tys:ty,)*] $value:ident, $binding:tt, &$ty:ty => $action:expr $(,)?} => { if let Some($binding) = $value.as_partial_reflect().try_downcast_ref::<$ty>() { $action } }; // ~~~ Owned ~~~ // - {@selector[$($tys:ty,)*] $value:ident, $binding:ident, $ty:ty => $action:expr , $($tt:tt)+} => { + {@selector[$($tys:ty,)*] $value:ident, $binding:tt, $ty:ty => $action:expr , $($tt:tt)+} => { match $value.into_partial_reflect().try_take::<$ty>() { - Ok(mut $binding) => $action, - Err(mut $value) => select_ty!(@selector[$($tys,)* $ty,] $value, $value, $($tt)+), + Ok(select_ty!(@bind_mut $binding)) => $action, + Err($value) => { + // We have to rebind `$value` here so that we can unconditionally ignore it + // due to the fact that `unused_variables` seems to be the only lint that + // is visible outside the macro when used within other crates. + #[allow( + unused_variables, + reason = "unfortunately this variable cannot receive a custom binding to let it be ignored otherwise" + )] + let mut $value = $value; + + select_ty!(@selector[$($tys,)* $ty,] $value, $value, $($tt)+) + }, } }; - {@selector[$($tys:ty,)*] $value:ident, $binding:ident, $ty:ty => $action:expr $(,)?} => { + {@selector[$($tys:ty,)*] $value:ident, $binding:tt, $ty:ty => $action:expr $(,)?} => { if let Ok($binding) = $value.into_partial_reflect().try_take::<$ty>() { $action } @@ -177,7 +197,6 @@ mod tests { unused_imports, unused_parens, unused_mut, - unused_variables, reason = "the warnings generated by these macros should only be visible to `bevy_reflect`" )] @@ -262,6 +281,19 @@ mod tests { assert_eq!(result, None); } + #[test] + fn should_retrieve_owned() { + let original_value = Box::new(String::from("hello")); + let cloned_value = original_value.clone(); + + let value = select_ty! {cloned_value, + _ @ Option => panic!("unexpected type"), + else => cloned_value + }; + + assert_eq!(value.try_take::().unwrap(), *original_value); + } + #[test] fn should_allow_mixed_borrows() { fn process(value: Box) { From b68a07278e023ff0b6ce79f5bc7ec99ca9226856 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Mon, 5 May 2025 23:47:02 -0700 Subject: [PATCH 04/11] Use consistent binding behavior on `else` --- crates/bevy_reflect/src/macros/select_ty.rs | 37 ++++++++++++--------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/crates/bevy_reflect/src/macros/select_ty.rs b/crates/bevy_reflect/src/macros/select_ty.rs index 5d813f38df..c83a9ea8de 100644 --- a/crates/bevy_reflect/src/macros/select_ty.rs +++ b/crates/bevy_reflect/src/macros/select_ty.rs @@ -22,10 +22,10 @@ /// If the `` doesn't evaluate to `()`, an `else` case is required: /// /// ```text -/// else => , +/// else <[ ]?> => , /// ``` /// -/// The `` on an `else` case can optionally be used to access a slice that contains the +/// The `` on an `else` case can optionally be used to access a slice that contains the /// [`Type`] of each `` in the macro. /// This can be used as a convenience for debug messages or logging. /// @@ -66,9 +66,9 @@ /// list @ &mut Vec => { /// list.push(value as u32); /// }, -/// // The `else` case also supports bindings. -/// // Here, `types` contains all the types from the cases above -/// types @ else => panic!("expected types: {:?}", types) +/// // The `else` case also supports bindings as well as a special syntax +/// // for getting a slice over all the types in the macro +/// other @ else [types] => panic!("expected types: {:?} but received {:?}", types, other.reflect_type_path()) /// } /// } /// # @@ -108,12 +108,17 @@ macro_rules! select_ty { {@selector[$($tys:ty,)*] $value:ident, $binding:tt, } => {{}}; // --- Else Case --- // - {@selector[$($tys:ty,)*] $value:ident, $binding:tt, else => $action:expr $(,)? } => { - $action - }; - {@selector[$($tys:ty,)*] $value:ident, $_binding:tt, $binding:ident @ else => $action:expr $(,)? } => {{ - let $binding: &[$crate::Type] = &[$($crate::Type::of::<$tys>(),)*]; - $action + {@selector[$($tys:ty,)*] $value:ident, $_binding:tt, else => $action:expr $(,)? } => {{ + $action + }}; + {@selector[$($tys:ty,)*] $value:ident, $_binding:tt, $($binding:tt @)? else => $action:expr $(,)? } => {{ + $(let select_ty!(@bind_mut $binding) = $value;)? + $action + }}; + {@selector[$($tys:ty,)*] $value:ident, $_binding:tt, $($binding:tt @)? else [$types:ident] => $action:expr $(,)? } => {{ + $(let select_ty!(@bind_mut $binding) = $value;)? + let $types: &[$crate::Type] = &[$($crate::Type::of::<$tys>(),)*]; + $action }}; // --- Binding Matcher --- // @@ -353,16 +358,16 @@ mod tests { #[test] fn should_allow_else_with_binding() { - let _value = Box::new(123); + let value = Box::new(123); - select_ty! {_value, + select_ty! {value, f32 => { - assert_eq!(_value, 123.0); + assert_eq!(value, 123.0); }, f64 => { - assert_eq!(_value, 123.0); + assert_eq!(value, 123.0); }, - types @ else => { + _ @ else [types] => { assert_eq!(types.len(), 2); assert_eq!(types[0], Type::of::()); assert_eq!(types[1], Type::of::()); From 14ba2442fb202bb2c462e7f59de161ef74fb0d4b Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Tue, 6 May 2025 20:52:05 -0700 Subject: [PATCH 05/11] Rename to `match_type` --- .../macros/{select_ty.rs => match_type.rs} | 48 +++++++++---------- crates/bevy_reflect/src/macros/mod.rs | 4 +- 2 files changed, 26 insertions(+), 26 deletions(-) rename crates/bevy_reflect/src/macros/{select_ty.rs => match_type.rs} (91%) diff --git a/crates/bevy_reflect/src/macros/select_ty.rs b/crates/bevy_reflect/src/macros/match_type.rs similarity index 91% rename from crates/bevy_reflect/src/macros/select_ty.rs rename to crates/bevy_reflect/src/macros/match_type.rs index c83a9ea8de..37ed9e34be 100644 --- a/crates/bevy_reflect/src/macros/select_ty.rs +++ b/crates/bevy_reflect/src/macros/match_type.rs @@ -34,11 +34,11 @@ /// Basic usage: /// /// ``` -/// # use bevy_reflect::macros::select_ty; +/// # use bevy_reflect::macros::match_type; /// # use bevy_reflect::PartialReflect; /// # /// fn try_to_f32(value: &dyn PartialReflect) -> Option { -/// select_ty! {value, +/// match_type! {value, /// &f32 => Some(*value), /// &i32 => Some(*value as f32), /// else => None @@ -53,11 +53,11 @@ /// With bindings: /// /// ``` -/// # use bevy_reflect::macros::select_ty; +/// # use bevy_reflect::macros::match_type; /// # use bevy_reflect::PartialReflect; /// # /// fn try_push_value(container: &mut dyn PartialReflect, value: i32) { -/// select_ty! {container, +/// match_type! {container, /// // By default, cases use the given identifier as the binding identifier /// &mut Vec => { /// container.push(value); @@ -84,7 +84,7 @@ /// [`PartialReflect`]: crate::PartialReflect /// [`Type`]: crate::Type #[macro_export] -macro_rules! select_ty { +macro_rules! match_type { {$value:ident} => {}; @@ -93,7 +93,7 @@ macro_rules! select_ty { // cast to `dyn PartialReflect` or dereference manually use $crate::PartialReflect; - select_ty!(@selector[] $value, $value, $($tt)*) + match_type!(@selector[] $value, $value, $($tt)*) }}; // === Internal === // @@ -112,11 +112,11 @@ macro_rules! select_ty { $action }}; {@selector[$($tys:ty,)*] $value:ident, $_binding:tt, $($binding:tt @)? else => $action:expr $(,)? } => {{ - $(let select_ty!(@bind_mut $binding) = $value;)? + $(let match_type!(@bind_mut $binding) = $value;)? $action }}; {@selector[$($tys:ty,)*] $value:ident, $_binding:tt, $($binding:tt @)? else [$types:ident] => $action:expr $(,)? } => {{ - $(let select_ty!(@bind_mut $binding) = $value;)? + $(let match_type!(@bind_mut $binding) = $value;)? let $types: &[$crate::Type] = &[$($crate::Type::of::<$tys>(),)*]; $action }}; @@ -126,7 +126,7 @@ macro_rules! select_ty { // Note that its placement is _below_ the `else` rules. // This is to prevent this binding rule from superseding the custom one for the `else` case. {@selector[$($tys:ty,)*] $value:ident, $_old_binding:tt, $binding:tt @ $($tt:tt)+} => { - select_ty!(@selector[$($tys,)*] $value, $binding, $($tt)+) + match_type!(@selector[$($tys,)*] $value, $binding, $($tt)+) }; // --- Binding Helpers --- // @@ -146,7 +146,7 @@ macro_rules! select_ty { {@selector[$($tys:ty,)*] $value:ident, $binding:tt, &mut $ty:ty => $action:expr , $($tt:tt)+} => { match $value.as_partial_reflect_mut().try_downcast_mut::<$ty>() { Some($binding) => $action, - None => select_ty!(@selector[$($tys,)* &mut $ty,] $value, $value, $($tt)+) + None => match_type!(@selector[$($tys,)* &mut $ty,] $value, $value, $($tt)+) } }; {@selector[$($tys:ty,)*] $value:ident, $binding:tt, &mut $ty:ty => $action:expr $(,)?} => { @@ -159,7 +159,7 @@ macro_rules! select_ty { {@selector[$($tys:ty,)*] $value:ident, $binding:tt, &$ty:ty => $action:expr , $($tt:tt)+} => { match $value.as_partial_reflect().try_downcast_ref::<$ty>() { Some($binding) => $action, - None => select_ty!(@selector[$($tys,)* &mut $ty,] $value, $value, $($tt)+) + None => match_type!(@selector[$($tys,)* &mut $ty,] $value, $value, $($tt)+) } }; {@selector[$($tys:ty,)*] $value:ident, $binding:tt, &$ty:ty => $action:expr $(,)?} => { @@ -171,7 +171,7 @@ macro_rules! select_ty { // ~~~ Owned ~~~ // {@selector[$($tys:ty,)*] $value:ident, $binding:tt, $ty:ty => $action:expr , $($tt:tt)+} => { match $value.into_partial_reflect().try_take::<$ty>() { - Ok(select_ty!(@bind_mut $binding)) => $action, + Ok(match_type!(@bind_mut $binding)) => $action, Err($value) => { // We have to rebind `$value` here so that we can unconditionally ignore it // due to the fact that `unused_variables` seems to be the only lint that @@ -182,7 +182,7 @@ macro_rules! select_ty { )] let mut $value = $value; - select_ty!(@selector[$($tys,)* $ty,] $value, $value, $($tt)+) + match_type!(@selector[$($tys,)* $ty,] $value, $value, $($tt)+) }, } }; @@ -193,7 +193,7 @@ macro_rules! select_ty { }; } -pub use select_ty; +pub use match_type; #[cfg(test)] mod tests { @@ -215,8 +215,8 @@ mod tests { #[test] fn should_allow_empty() { fn empty(_value: Box) { - select_ty! {_value} - select_ty! {_value,} + match_type! {_value} + match_type! {_value,} } empty(Box::new(42)); @@ -225,7 +225,7 @@ mod tests { #[test] fn should_downcast_ref() { fn to_string(value: &dyn PartialReflect) -> String { - select_ty! {value, + match_type! {value, &String => value.clone(), &i32 => value.to_string(), &f32 => format!("{:.2}", value), @@ -242,7 +242,7 @@ mod tests { #[test] fn should_downcast_mut() { fn push_value(container: &mut dyn PartialReflect, value: i32) -> bool { - select_ty! {container, + match_type! {container, &mut Vec => container.push(value), &mut Vec => container.push(value as u32), else => return false @@ -266,7 +266,7 @@ mod tests { #[test] fn should_downcast_owned() { fn into_string(value: Box) -> Option { - select_ty! {value, + match_type! {value, String => Some(value), i32 => Some(value.to_string()), else => None @@ -291,7 +291,7 @@ mod tests { let original_value = Box::new(String::from("hello")); let cloned_value = original_value.clone(); - let value = select_ty! {cloned_value, + let value = match_type! {cloned_value, _ @ Option => panic!("unexpected type"), else => cloned_value }; @@ -302,7 +302,7 @@ mod tests { #[test] fn should_allow_mixed_borrows() { fn process(value: Box) { - select_ty! {value, + match_type! {value, Option => { let value = value.unwrap(); assert_eq!(value, 1.0); @@ -332,7 +332,7 @@ mod tests { #[test] fn should_allow_custom_bindings() { fn process(mut value: Box) { - select_ty! {value, + match_type! {value, foo @ &mut i32 => { *foo *= 2; assert_eq!(*foo, 246); @@ -360,7 +360,7 @@ mod tests { fn should_allow_else_with_binding() { let value = Box::new(123); - select_ty! {value, + match_type! {value, f32 => { assert_eq!(value, 123.0); }, @@ -379,7 +379,7 @@ mod tests { fn should_handle_slice_types() { let _value = Box::new("hello world"); - select_ty! {_value, + match_type! {_value, (&str) => {}, (&[i32]) => { panic!("unexpected type"); diff --git a/crates/bevy_reflect/src/macros/mod.rs b/crates/bevy_reflect/src/macros/mod.rs index 74a003d362..c693276277 100644 --- a/crates/bevy_reflect/src/macros/mod.rs +++ b/crates/bevy_reflect/src/macros/mod.rs @@ -1,3 +1,3 @@ -pub use select_ty::*; +pub use match_type::*; -mod select_ty; +mod match_type; From 4ae30d0a2eda5bc2d81e7ab9b7b8cc2c4f1506fd Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Thu, 8 May 2025 16:16:45 -0700 Subject: [PATCH 06/11] Update syntax to be more match-like --- crates/bevy_reflect/src/macros/match_type.rs | 342 ++++++++++--------- 1 file changed, 177 insertions(+), 165 deletions(-) diff --git a/crates/bevy_reflect/src/macros/match_type.rs b/crates/bevy_reflect/src/macros/match_type.rs index 37ed9e34be..85bb6c63e7 100644 --- a/crates/bevy_reflect/src/macros/match_type.rs +++ b/crates/bevy_reflect/src/macros/match_type.rs @@ -2,177 +2,156 @@ /// /// # Syntax /// -/// The first argument to the macro is the identifier of the variable holding the reflected value. -/// This is also the default binding for all downcasted values. -/// -/// All other arguments to the macro are match-like cases statements that follow the following pattern: +/// The syntax of the macro closely resembles a standard `match`, but with some subtle differences. /// /// ```text -/// => , +/// match_type! { , } +/// +/// := IDENT // the variable you’re matching +/// +/// := +/// ( , )* [ , ] // zero or more arms with optional trailing comma +/// +/// := +/// [ @ ] [ where ] => +/// +/// := IDENT | _ // rename or ignore the downcast value +/// +/// := // determines the downcast method +/// | TYPE // -> try_take::() +/// | & TYPE // -> try_downcast_ref::() +/// | &mut TYPE // -> try_downcast_mut::() +/// | _ // -> catch‑all (no downcast) +/// +/// := a boolean expression that acts as a guard for the arm +/// := expression or block that runs if the downcast succeeds for the given type /// ``` /// -/// Where `` denotes what kind of downcasting to perform: -/// - `&` - Downcasts with `try_downcast_ref` -/// - `&mut` - Downcasts with `try_downcast_mut` -/// - None - Downcasts with `try_take` +/// The `` must be a type that implements [`PartialReflect`]. +/// Owned values should be passed as a `Box`. /// -/// And `` is an optional binding (i.e. ` @`) for the downcasted value. -/// It can also be used to bind the value to `_` to ignore the value if not needed. +/// Types are matched in the order they are defined. +/// Any `_` cases must be the last case in the list. /// -/// If the `` doesn't evaluate to `()`, an `else` case is required: +/// If a custom binding is not provided, +/// the downcasted value will be bound to the same identifier as the input, thus shadowing it. +/// You can use `_` to ignore the downcasted value if you don’t need it, +/// which may be helpful to silence any "unused variable" lints. /// -/// ```text -/// else <[ ]?> => , -/// ``` -/// -/// The `` on an `else` case can optionally be used to access a slice that contains the -/// [`Type`] of each `` in the macro. -/// This can be used as a convenience for debug messages or logging. +/// The `where` clause is optional and can be used to add an extra boolean guard. +/// If the guard evaluates to `true`, the expression will be executed if the type matches. +/// Otherwise, matching will continue to the next arm even if the type matches. /// /// # Examples /// -/// Basic usage: /// /// ``` /// # use bevy_reflect::macros::match_type; /// # use bevy_reflect::PartialReflect; /// # -/// fn try_to_f32(value: &dyn PartialReflect) -> Option { -/// match_type! {value, -/// &f32 => Some(*value), -/// &i32 => Some(*value as f32), -/// else => None -/// } +/// fn stringify(mut value: Box) -> String { +/// match_type! { value, +/// // Downcast to an owned type +/// f32 => format!("{:.1}", value), +/// // Downcast to a mutable reference +/// &mut i32 => { +/// *value *= 2; +/// value.to_string() +/// }, +/// // Define custom bindings +/// chars @ &Vec => chars.iter().collect(), +/// // Define conditional guards +/// &String where value == "ping" => "pong".to_owned(), +/// &String => value.clone(), +/// // Fallback case +/// _ => "".to_string(), +/// } /// } -/// # -/// # assert_eq!(try_to_f32(&123_i32), Some(123_f32)); -/// # assert_eq!(try_to_f32(&123.0_f32), Some(123.0_f32)); -/// # assert_eq!(try_to_f32(&123_u32), None); -/// ``` /// -/// With bindings: -/// -/// ``` -/// # use bevy_reflect::macros::match_type; -/// # use bevy_reflect::PartialReflect; -/// # -/// fn try_push_value(container: &mut dyn PartialReflect, value: i32) { -/// match_type! {container, -/// // By default, cases use the given identifier as the binding identifier -/// &mut Vec => { -/// container.push(value); -/// }, -/// // But you can also provide your own binding identifier -/// list @ &mut Vec => { -/// list.push(value as u32); -/// }, -/// // The `else` case also supports bindings as well as a special syntax -/// // for getting a slice over all the types in the macro -/// other @ else [types] => panic!("expected types: {:?} but received {:?}", types, other.reflect_type_path()) -/// } -/// } -/// # -/// # let mut list: Vec = vec![1, 2]; -/// # try_push_value(&mut list, 3); -/// # assert_eq!(list, vec![1, 2, 3]); -/// # -/// # let mut list: Vec = vec![1, 2]; -/// # try_push_value(&mut list, 3); -/// # assert_eq!(list, vec![1, 2, 3]); +/// assert_eq!(stringify(Box::new(123.0_f32)), "123.0"); +/// assert_eq!(stringify(Box::new(123_i32)), "246"); +/// assert_eq!(stringify(Box::new(vec!['h', 'e', 'l', 'l', 'o'])), "hello"); +/// assert_eq!(stringify(Box::new("ping".to_string())), "pong"); +/// assert_eq!(stringify(Box::new("hello".to_string())), "hello"); +/// assert_eq!(stringify(Box::new(true)), ""); /// ``` /// /// [`PartialReflect`]: crate::PartialReflect -/// [`Type`]: crate::Type #[macro_export] macro_rules! match_type { - {$value:ident} => {}; + // === Entry Point === // - {$value:ident, $($tt:tt)*} => {{ + {$input:ident} => {{}}; + {$input:ident, $($tt:tt)*} => {{ // We use an import over fully-qualified syntax so users don't have to // cast to `dyn PartialReflect` or dereference manually use $crate::PartialReflect; - match_type!(@selector[] $value, $value, $($tt)*) + match_type!(@arm[$input] $($tt)*) }}; - // === Internal === // - // Each internal selector contains: - // 1. The collection of case types encountered (used to build the type slice) - // 2. The identifier of the user-given value being processed - // 3. The detected binding (or the same identifier as the value if none) - // 4. The pattern to match + // === Arm Parsing === // + // These rules take the following input (in `[]`): + // 1. The input identifier + // 2. An optional binding identifier (or `_`) + // + // Additionally, most cases are comprised of both a terminal and non-terminal rule. // --- Empty Case --- // - // This allows usages to contain no cases (e.g., all commented out or macro-generated) - {@selector[$($tys:ty,)*] $value:ident, $binding:tt, } => {{}}; + {@arm [$input:ident $(as $binding:tt)?]} => {{}}; - // --- Else Case --- // - {@selector[$($tys:ty,)*] $value:ident, $_binding:tt, else => $action:expr $(,)? } => {{ - $action - }}; - {@selector[$($tys:ty,)*] $value:ident, $_binding:tt, $($binding:tt @)? else => $action:expr $(,)? } => {{ - $(let match_type!(@bind_mut $binding) = $value;)? - $action - }}; - {@selector[$($tys:ty,)*] $value:ident, $_binding:tt, $($binding:tt @)? else [$types:ident] => $action:expr $(,)? } => {{ - $(let match_type!(@bind_mut $binding) = $value;)? - let $types: &[$crate::Type] = &[$($crate::Type::of::<$tys>(),)*]; - $action - }}; - - // --- Binding Matcher --- // - // This rule is used to detect an optional binding (i.e. ` @`) for each case. - // Note that its placement is _below_ the `else` rules. - // This is to prevent this binding rule from superseding the custom one for the `else` case. - {@selector[$($tys:ty,)*] $value:ident, $_old_binding:tt, $binding:tt @ $($tt:tt)+} => { - match_type!(@selector[$($tys,)*] $value, $binding, $($tt)+) + // --- Custom Bindings --- // + {@arm [$input:ident $(as $binding:tt)?] $new_binding:tt @ $($tt:tt)+} => { + match_type!(@arm [$input as $new_binding] $($tt)+) }; - // --- Binding Helpers --- // - {@bind_mut _} => { - _ + // --- Fallback Case --- // + {@arm [$input:ident $(as $binding:tt)?] _ $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { + match_type!(@else [$input $(as $binding)?, [$($condition)?], $action] $($tt)+) }; - {@bind_mut $binding:ident} => { - mut $binding + {@arm [$input:ident $(as $binding:tt)?] _ $(where $condition:expr)? => $action:expr $(,)?} => { + match_type!(@else [$input $(as $binding)?, [$($condition)?], $action]) }; - // --- Main Cases --- // - // Note that each main case comes with two rules: a non-terminal and a terminal rule. - // The non-terminal rule is the one that can be used as an expression since it should be exhaustive. - // The terminal rule is the one that can be used for non-exhaustive statements. - - // ~~~ Mutable Borrow ~~~ // - {@selector[$($tys:ty,)*] $value:ident, $binding:tt, &mut $ty:ty => $action:expr , $($tt:tt)+} => { - match $value.as_partial_reflect_mut().try_downcast_mut::<$ty>() { - Some($binding) => $action, - None => match_type!(@selector[$($tys,)* &mut $ty,] $value, $value, $($tt)+) - } + // --- Mutable Downcast Case --- // + {@arm [$input:ident $(as $binding:tt)?] &mut $ty:ty $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { + match_type!(@if [$input $(as $binding)?, mut, $ty, [$($condition)?], $action] $($tt)+) }; - {@selector[$($tys:ty,)*] $value:ident, $binding:tt, &mut $ty:ty => $action:expr $(,)?} => { - if let Some($binding) = $value.as_partial_reflect_mut().try_downcast_mut::<$ty>() { - $action - } + {@arm [$input:ident $(as $binding:tt)?] &mut $ty:ty $(where $condition:expr)? => $action:expr $(,)?} => { + match_type!(@if [$input $(as $binding)?, mut, $ty, [$($condition)?], $action]) }; - // ~~~ Immutable Borrow ~~~ // - {@selector[$($tys:ty,)*] $value:ident, $binding:tt, &$ty:ty => $action:expr , $($tt:tt)+} => { - match $value.as_partial_reflect().try_downcast_ref::<$ty>() { - Some($binding) => $action, - None => match_type!(@selector[$($tys,)* &mut $ty,] $value, $value, $($tt)+) - } + // --- Immutable Downcast Case --- // + {@arm [$input:ident $(as $binding:tt)?] & $ty:ty $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { + match_type!(@if [$input $(as $binding)?, ref, $ty, [$($condition)?], $action] $($tt)+) }; - {@selector[$($tys:ty,)*] $value:ident, $binding:tt, &$ty:ty => $action:expr $(,)?} => { - if let Some($binding) = $value.as_partial_reflect().try_downcast_ref::<$ty>() { - $action - } + {@arm [$input:ident $(as $binding:tt)?] & $ty:ty $(where $condition:expr)? => $action:expr $(,)?} => { + match_type!(@if [$input $(as $binding)?, ref, $ty, [$($condition)?], $action]) }; - // ~~~ Owned ~~~ // - {@selector[$($tys:ty,)*] $value:ident, $binding:tt, $ty:ty => $action:expr , $($tt:tt)+} => { - match $value.into_partial_reflect().try_take::<$ty>() { - Ok(match_type!(@bind_mut $binding)) => $action, - Err($value) => { + // --- Owned Downcast Case --- // + {@arm [$input:ident $(as $binding:tt)?] $ty:ty $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { + match_type!(@if [$input $(as $binding)?, box, $ty, [$($condition)?], $action] $($tt)+) + }; + {@arm [$input:ident $(as $binding:tt)?] $ty:ty $(where $condition:expr)? => $action:expr $(,)?} => { + match_type!(@if [$input $(as $binding)?, box, $ty, [$($condition)?], $action]) + }; + + // === Type Matching === // + // These rules take the following input (in `[]`): + // 1. The input identifier + // 2. An optional binding identifier (or `_`) + // 3. The kind of downcast (e.g., `mut`, `ref`, or `box`) + // 4. The type to downcast to + // 5. An optional condition (wrapped in `[]` for disambiguation) + // 6. The action to take if the downcast succeeds + + // This rule handles the owned downcast case + {@if [$input:ident $(as $binding:tt)?, box, $ty:ty, [$($condition:expr)?], $action:expr] $($rest:tt)*} => { + match match_type!(@downcast box, $ty, $input) { + Ok(match_type!(@bind [mut] $input $(as $binding)?)) $(if $condition)? => $action, + $input => { // We have to rebind `$value` here so that we can unconditionally ignore it // due to the fact that `unused_variables` seems to be the only lint that // is visible outside the macro when used within other crates. @@ -180,17 +159,75 @@ macro_rules! match_type { unused_variables, reason = "unfortunately this variable cannot receive a custom binding to let it be ignored otherwise" )] - let mut $value = $value; + let mut $input = match $input { + Ok($input) => $crate::__macro_exports::alloc_utils::Box::new($input) as $crate::__macro_exports::alloc_utils::Box, + Err($input) => $input + }; - match_type!(@selector[$($tys,)* $ty,] $value, $value, $($tt)+) - }, + match_type!(@arm [$input] $($rest)*) + } } }; - {@selector[$($tys:ty,)*] $value:ident, $binding:tt, $ty:ty => $action:expr $(,)?} => { - if let Ok($binding) = $value.into_partial_reflect().try_take::<$ty>() { - $action + // This rule handles the mutable and immutable downcast cases + {@if [$input:ident $(as $binding:tt)?, $kind:tt, $ty:ty, [$($condition:expr)?], $action:expr] $($rest:tt)*} => { + match match_type!(@downcast $kind, $ty, $input) { + Some(match_type!(@bind [] $input $(as $binding)?)) $(if $condition)? => $action, + _ => { + match_type!(@arm [$input] $($rest)*) + } } }; + + // This rule handles the fallback case where a condition has been provided + {@else [$input:ident $(as $binding:tt)?, [$condition:expr], $action:expr] $($rest:tt)*} => {{ + let match_type!(@bind [mut] _ $(as $binding)?) = $input; + + if $condition { + $action + } else { + match_type!(@arm [$input] $($rest)*) + } + }}; + // This rule handles the fallback case where no condition has been provided + {@else [$input:ident $(as $binding:tt)?, [], $action:expr] $($rest:tt)*} => {{ + let match_type!(@bind [mut] _ $(as $binding)?) = $input; + + $action + }}; + + // === Helpers === // + + // --- Downcasting --- // + // Helpers for downcasting `$input` to `$ty` + // based on the given keyword (`mut`, `ref`, or `box`). + + {@downcast mut, $ty:ty, $input:ident} => { + $input.as_partial_reflect_mut().try_downcast_mut::<$ty>() + }; + {@downcast ref, $ty:ty, $input:ident} => { + $input.as_partial_reflect().try_downcast_ref::<$ty>() + }; + {@downcast box, $ty:ty, $input:ident} => { + $input.into_partial_reflect().try_take::<$ty>() + }; + + // --- Binding --- // + // Helpers for creating a binding for the downcasted value. + // This ensures that we only add `mut` when necessary, + // and that `_` is handled appropriately. + + {@bind [$($mut:tt)?] _} => { + _ + }; + {@bind [$($mut:tt)?] $input:ident} => { + $($mut)? $input + }; + {@bind [$($mut:tt)?] $input:tt as _} => { + _ + }; + {@bind [$($mut:tt)?] $input:tt as $binding:ident} => { + $($mut)? $binding + }; } pub use match_type; @@ -215,8 +252,8 @@ mod tests { #[test] fn should_allow_empty() { fn empty(_value: Box) { - match_type! {_value} - match_type! {_value,} + let _: () = match_type! {_value}; + let _: () = match_type! {_value,}; } empty(Box::new(42)); @@ -229,7 +266,7 @@ mod tests { &String => value.clone(), &i32 => value.to_string(), &f32 => format!("{:.2}", value), - else => "unknown".to_string() + _ => "unknown".to_string() } } @@ -245,7 +282,7 @@ mod tests { match_type! {container, &mut Vec => container.push(value), &mut Vec => container.push(value as u32), - else => return false + _ => return false } true @@ -269,7 +306,7 @@ mod tests { match_type! {value, String => Some(value), i32 => Some(value.to_string()), - else => None + _ => None } } @@ -293,7 +330,7 @@ mod tests { let value = match_type! {cloned_value, _ @ Option => panic!("unexpected type"), - else => cloned_value + _ => cloned_value }; assert_eq!(value.try_take::().unwrap(), *original_value); @@ -356,38 +393,13 @@ mod tests { process(Box::new(true)); } - #[test] - fn should_allow_else_with_binding() { - let value = Box::new(123); - - match_type! {value, - f32 => { - assert_eq!(value, 123.0); - }, - f64 => { - assert_eq!(value, 123.0); - }, - _ @ else [types] => { - assert_eq!(types.len(), 2); - assert_eq!(types[0], Type::of::()); - assert_eq!(types[1], Type::of::()); - }, - } - } - #[test] fn should_handle_slice_types() { let _value = Box::new("hello world"); match_type! {_value, (&str) => {}, - (&[i32]) => { - panic!("unexpected type"); - }, - (&mut [u32]) => { - panic!("unexpected type"); - }, - else => panic!("unexpected type"), + _ => panic!("unexpected type"), } } } From 511a0c284c2f2d039633b3fc09638c2bb1f09f1b Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Thu, 8 May 2025 17:13:13 -0700 Subject: [PATCH 07/11] Added types array back in --- crates/bevy_reflect/src/macros/match_type.rs | 143 ++++++++++++++----- 1 file changed, 111 insertions(+), 32 deletions(-) diff --git a/crates/bevy_reflect/src/macros/match_type.rs b/crates/bevy_reflect/src/macros/match_type.rs index 85bb6c63e7..90b93e4cfe 100644 --- a/crates/bevy_reflect/src/macros/match_type.rs +++ b/crates/bevy_reflect/src/macros/match_type.rs @@ -15,6 +15,17 @@ /// := /// [ @ ] [ where ] => /// +/// := +/// [ @ ] // *optional* binding for the downcasted value +/// // type pattern to match +/// [ `[` `]` ] // *optional* type array +/// [ where ] // *optional* guard +/// => // expression or block to run on match +/// +/// := IDENT | _ +/// +/// := IDENT +/// /// := IDENT | _ // rename or ignore the downcast value /// /// := // determines the downcast method @@ -42,8 +53,13 @@ /// If the guard evaluates to `true`, the expression will be executed if the type matches. /// Otherwise, matching will continue to the next arm even if the type matches. /// -/// # Examples +/// If a `` is defined, this will be bound to a slice of [`Type`]s +/// that were checked up to and including the current arm. +/// This can be useful for debugging or logging purposes. +/// Note that this list may contain duplicates if the same type is checked in multiple arms, +/// such as when using `where` guards. /// +/// # Examples /// /// ``` /// # use bevy_reflect::macros::match_type; @@ -63,8 +79,11 @@ /// // Define conditional guards /// &String where value == "ping" => "pong".to_owned(), /// &String => value.clone(), -/// // Fallback case -/// _ => "".to_string(), +/// // Fallback case with an optional type array +/// _ [types] => { +/// println!("Couldn't match any types: {:?}", types); +/// "".to_string() +/// }, /// } /// } /// @@ -77,6 +96,7 @@ /// ``` /// /// [`PartialReflect`]: crate::PartialReflect +/// [`Type`]: crate::Type #[macro_export] macro_rules! match_type { @@ -88,7 +108,7 @@ macro_rules! match_type { // cast to `dyn PartialReflect` or dereference manually use $crate::PartialReflect; - match_type!(@arm[$input] $($tt)*) + match_type!(@arm[[], $input] $($tt)*) }}; // === Arm Parsing === // @@ -99,43 +119,43 @@ macro_rules! match_type { // Additionally, most cases are comprised of both a terminal and non-terminal rule. // --- Empty Case --- // - {@arm [$input:ident $(as $binding:tt)?]} => {{}}; + {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?]} => {{}}; // --- Custom Bindings --- // - {@arm [$input:ident $(as $binding:tt)?] $new_binding:tt @ $($tt:tt)+} => { - match_type!(@arm [$input as $new_binding] $($tt)+) + {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] $new_binding:tt @ $($tt:tt)+} => { + match_type!(@arm [[$($tys),*], $input as $new_binding] $($tt)+) }; // --- Fallback Case --- // - {@arm [$input:ident $(as $binding:tt)?] _ $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { - match_type!(@else [$input $(as $binding)?, [$($condition)?], $action] $($tt)+) + {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] _ $([$types:ident])? $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { + match_type!(@else [[$($tys),*], $input $(as $binding)?, [$($types)?], [$($condition)?], $action] $($tt)+) }; - {@arm [$input:ident $(as $binding:tt)?] _ $(where $condition:expr)? => $action:expr $(,)?} => { - match_type!(@else [$input $(as $binding)?, [$($condition)?], $action]) + {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] _ $([$types:ident])? $(where $condition:expr)? => $action:expr $(,)?} => { + match_type!(@else [[$($tys),*], $input $(as $binding)?, [$($types)?], [$($condition)?], $action]) }; // --- Mutable Downcast Case --- // - {@arm [$input:ident $(as $binding:tt)?] &mut $ty:ty $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { - match_type!(@if [$input $(as $binding)?, mut, $ty, [$($condition)?], $action] $($tt)+) + {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] &mut $ty:ty $([$types:ident])? $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { + match_type!(@if [[$($tys,)* &mut $ty], $input $(as $binding)?, mut, $ty, [$($types)?], [$($condition)?], $action] $($tt)+) }; - {@arm [$input:ident $(as $binding:tt)?] &mut $ty:ty $(where $condition:expr)? => $action:expr $(,)?} => { - match_type!(@if [$input $(as $binding)?, mut, $ty, [$($condition)?], $action]) + {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] &mut $ty:ty $([$types:ident])? $(where $condition:expr)? => $action:expr $(,)?} => { + match_type!(@if [[$($tys,)* &mut $ty], $input $(as $binding)?, mut, $ty, [$($types)?], [$($condition)?], $action]) }; // --- Immutable Downcast Case --- // - {@arm [$input:ident $(as $binding:tt)?] & $ty:ty $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { - match_type!(@if [$input $(as $binding)?, ref, $ty, [$($condition)?], $action] $($tt)+) + {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] & $ty:ty $([$types:ident])? $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { + match_type!(@if [[$($tys,)* &$ty], $input $(as $binding)?, ref, $ty, [$($types)?], [$($condition)?], $action] $($tt)+) }; - {@arm [$input:ident $(as $binding:tt)?] & $ty:ty $(where $condition:expr)? => $action:expr $(,)?} => { - match_type!(@if [$input $(as $binding)?, ref, $ty, [$($condition)?], $action]) + {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] & $ty:ty $([$types:ident])? $(where $condition:expr)? => $action:expr $(,)?} => { + match_type!(@if [[$($tys,)* &$ty], $input $(as $binding)?, ref, $ty, [$($types)?], [$($condition)?], $action]) }; // --- Owned Downcast Case --- // - {@arm [$input:ident $(as $binding:tt)?] $ty:ty $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { - match_type!(@if [$input $(as $binding)?, box, $ty, [$($condition)?], $action] $($tt)+) + {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] $ty:ty $([$types:ident])? $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { + match_type!(@if [[$($tys,)* $ty], $input $(as $binding)?, box, $ty, [$($types)?], [$($condition)?], $action] $($tt)+) }; - {@arm [$input:ident $(as $binding:tt)?] $ty:ty $(where $condition:expr)? => $action:expr $(,)?} => { - match_type!(@if [$input $(as $binding)?, box, $ty, [$($condition)?], $action]) + {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] $ty:ty $([$types:ident])? $(where $condition:expr)? => $action:expr $(,)?} => { + match_type!(@if [[$($tys,)* $ty], $input $(as $binding)?, box, $ty, [$($types)?], [$($condition)?], $action]) }; // === Type Matching === // @@ -148,9 +168,12 @@ macro_rules! match_type { // 6. The action to take if the downcast succeeds // This rule handles the owned downcast case - {@if [$input:ident $(as $binding:tt)?, box, $ty:ty, [$($condition:expr)?], $action:expr] $($rest:tt)*} => { + {@if [[$($tys:ty),*], $input:ident $(as $binding:tt)?, box, $ty:ty, [$($types:ident)?], [$($condition:expr)?], $action:expr] $($rest:tt)*} => { match match_type!(@downcast box, $ty, $input) { - Ok(match_type!(@bind [mut] $input $(as $binding)?)) $(if $condition)? => $action, + Ok(match_type!(@bind [mut] $input $(as $binding)?)) $(if $condition)? => { + match_type!(@collect [$($tys),*] $(as $types)?); + $action + }, $input => { // We have to rebind `$value` here so that we can unconditionally ignore it // due to the fact that `unused_variables` seems to be the only lint that @@ -164,32 +187,37 @@ macro_rules! match_type { Err($input) => $input }; - match_type!(@arm [$input] $($rest)*) + match_type!(@arm [[$($tys),*], $input] $($rest)*) } } }; // This rule handles the mutable and immutable downcast cases - {@if [$input:ident $(as $binding:tt)?, $kind:tt, $ty:ty, [$($condition:expr)?], $action:expr] $($rest:tt)*} => { + {@if [[$($tys:ty),*], $input:ident $(as $binding:tt)?, $kind:tt, $ty:ty, [$($types:ident)?], [$($condition:expr)?], $action:expr] $($rest:tt)*} => { match match_type!(@downcast $kind, $ty, $input) { - Some(match_type!(@bind [] $input $(as $binding)?)) $(if $condition)? => $action, + Some(match_type!(@bind [] $input $(as $binding)?)) $(if $condition)? => { + match_type!(@collect [$($tys),*] $(as $types)?); + $action + }, _ => { - match_type!(@arm [$input] $($rest)*) + match_type!(@arm [[$($tys),*], $input] $($rest)*) } } }; // This rule handles the fallback case where a condition has been provided - {@else [$input:ident $(as $binding:tt)?, [$condition:expr], $action:expr] $($rest:tt)*} => {{ + {@else [[$($tys:ty),*], $input:ident $(as $binding:tt)?, [$($types:ident)?], [$condition:expr], $action:expr] $($rest:tt)*} => {{ + match_type!(@collect [$($tys),*] $(as $types)?); let match_type!(@bind [mut] _ $(as $binding)?) = $input; if $condition { $action } else { - match_type!(@arm [$input] $($rest)*) + match_type!(@arm [[$($tys),*], $input] $($rest)*) } }}; // This rule handles the fallback case where no condition has been provided - {@else [$input:ident $(as $binding:tt)?, [], $action:expr] $($rest:tt)*} => {{ + {@else [[$($tys:ty),*], $input:ident $(as $binding:tt)?, [$($types:ident)?], [], $action:expr] $($rest:tt)*} => {{ + match_type!(@collect [$($tys),*] $(as $types)?); let match_type!(@bind [mut] _ $(as $binding)?) = $input; $action @@ -228,6 +256,14 @@ macro_rules! match_type { {@bind [$($mut:tt)?] $input:tt as $binding:ident} => { $($mut)? $binding }; + + // --- Collect Types --- // + // Helpers for collecting the types into an array of `Type`. + + {@collect [$($ty:ty),*]} => {}; + {@collect [$($tys:ty),*] as $types:ident} => { + let $types: &[$crate::Type] = &[$($crate::Type::of::<$tys>(),)*]; + }; } pub use match_type; @@ -402,4 +438,47 @@ mod tests { _ => panic!("unexpected type"), } } + + #[test] + fn should_capture_types() { + fn test(mut value: Box) { + match_type! {value, + _ @ &mut u8 [types] => { + assert_eq!(types.len(), 1); + assert_eq!(types[0], Type::of::<&mut u8>()); + }, + _ @ &u16 [types] => { + assert_eq!(types.len(), 2); + assert_eq!(types[0], Type::of::<&mut u8>()); + assert_eq!(types[1], Type::of::<&u16>()); + }, + u32 [types] where value > 10 => { + assert_eq!(types.len(), 3); + assert_eq!(types[0], Type::of::<&mut u8>()); + assert_eq!(types[1], Type::of::<&u16>()); + assert_eq!(types[2], Type::of::()); + }, + _ @ u32 [types] => { + assert_eq!(types.len(), 4); + assert_eq!(types[0], Type::of::<&mut u8>()); + assert_eq!(types[1], Type::of::<&u16>()); + assert_eq!(types[2], Type::of::()); + assert_eq!(types[3], Type::of::()); + }, + _ [types] => { + assert_eq!(types.len(), 4); + assert_eq!(types[0], Type::of::<&mut u8>()); + assert_eq!(types[1], Type::of::<&u16>()); + assert_eq!(types[2], Type::of::()); + assert_eq!(types[3], Type::of::()); + }, + } + } + + test(Box::new(0_u8)); + test(Box::new(0_u16)); + test(Box::new(123_u32)); + test(Box::new(0_u32)); + test(Box::new(0_u64)); + } } From 87f3afa8b73bfadd68a83eabf83d17fef693bece Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Thu, 8 May 2025 17:30:55 -0700 Subject: [PATCH 08/11] Allow unused_parens --- crates/bevy_reflect/src/macros/match_type.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_reflect/src/macros/match_type.rs b/crates/bevy_reflect/src/macros/match_type.rs index 90b93e4cfe..83d4b16a81 100644 --- a/crates/bevy_reflect/src/macros/match_type.rs +++ b/crates/bevy_reflect/src/macros/match_type.rs @@ -169,6 +169,7 @@ macro_rules! match_type { // This rule handles the owned downcast case {@if [[$($tys:ty),*], $input:ident $(as $binding:tt)?, box, $ty:ty, [$($types:ident)?], [$($condition:expr)?], $action:expr] $($rest:tt)*} => { + #[allow(unused_parens, reason = "may be used for disambiguation")] match match_type!(@downcast box, $ty, $input) { Ok(match_type!(@bind [mut] $input $(as $binding)?)) $(if $condition)? => { match_type!(@collect [$($tys),*] $(as $types)?); @@ -193,6 +194,7 @@ macro_rules! match_type { }; // This rule handles the mutable and immutable downcast cases {@if [[$($tys:ty),*], $input:ident $(as $binding:tt)?, $kind:tt, $ty:ty, [$($types:ident)?], [$($condition:expr)?], $action:expr] $($rest:tt)*} => { + #[allow(unused_parens, reason = "may be used for disambiguation")] match match_type!(@downcast $kind, $ty, $input) { Some(match_type!(@bind [] $input $(as $binding)?)) $(if $condition)? => { match_type!(@collect [$($tys),*] $(as $types)?); From c166c041de98ba43c258dda766397d2ab4fa285f Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Fri, 9 May 2025 22:27:48 -0700 Subject: [PATCH 09/11] Add tests for generics --- crates/bevy_reflect/src/macros/match_type.rs | 74 ++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/crates/bevy_reflect/src/macros/match_type.rs b/crates/bevy_reflect/src/macros/match_type.rs index 83d4b16a81..e426db162f 100644 --- a/crates/bevy_reflect/src/macros/match_type.rs +++ b/crates/bevy_reflect/src/macros/match_type.rs @@ -286,6 +286,8 @@ mod tests { use alloc::string::{String, ToString}; use alloc::vec::Vec; use alloc::{format, vec}; + use core::fmt::Debug; + use core::ops::MulAssign; #[test] fn should_allow_empty() { @@ -483,4 +485,76 @@ mod tests { test(Box::new(0_u32)); test(Box::new(0_u64)); } + + #[test] + fn should_downcast_from_generic() { + fn immutable(value: &T) { + match_type! {value, + &i32 => { + assert_eq!(*value, 1); + }, + _ => panic!("unexpected type"), + } + } + + fn mutable(value: &mut T) { + match_type! {value, + &mut i32 => { + *value = 2; + }, + _ => panic!("unexpected type"), + } + } + + fn owned(value: T) { + let value = Box::new(value); + match_type! {value, + i32 => { + assert_eq!(value, 2); + }, + _ => panic!("unexpected type"), + } + } + + let mut value = 1_i32; + immutable(&value); + mutable(&mut value); + owned(value); + } + + #[test] + fn should_downcast_to_generic() { + fn immutable>(value: &T) { + match_type! {value, + &U => { + assert_eq!(*value, 1); + }, + _ => panic!("unexpected type"), + } + } + + fn mutable>(value: &mut T) { + match_type! {value, + &mut U => { + *value *= 2; + }, + _ => panic!("unexpected type"), + } + } + + fn owned>(value: T) { + let value = Box::new(value); + match_type! {value, + U => { + assert_eq!(value, 2); + }, + _ => panic!("unexpected type"), + } + } + + let mut value = 1_i32; + immutable::<_, i32>(&value); + mutable::<_, i32>(&mut value); + owned::<_, i32>(value); + } } From 5c9c6a4793ac1cafdf1450f3e27b8b0782beeb6c Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Fri, 9 May 2025 22:30:05 -0700 Subject: [PATCH 10/11] Auto-box on owned downcast --- crates/bevy_reflect/src/macros/match_type.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/bevy_reflect/src/macros/match_type.rs b/crates/bevy_reflect/src/macros/match_type.rs index e426db162f..f319c79ebe 100644 --- a/crates/bevy_reflect/src/macros/match_type.rs +++ b/crates/bevy_reflect/src/macros/match_type.rs @@ -238,7 +238,8 @@ macro_rules! match_type { $input.as_partial_reflect().try_downcast_ref::<$ty>() }; {@downcast box, $ty:ty, $input:ident} => { - $input.into_partial_reflect().try_take::<$ty>() + // We eagerly box here so that we can support non-boxed values. + $crate::__macro_exports::alloc_utils::Box::new($input).into_partial_reflect().try_take::<$ty>() }; // --- Binding --- // @@ -365,7 +366,7 @@ mod tests { #[test] fn should_retrieve_owned() { - let original_value = Box::new(String::from("hello")); + let original_value = String::from("hello"); let cloned_value = original_value.clone(); let value = match_type! {cloned_value, @@ -435,7 +436,7 @@ mod tests { #[test] fn should_handle_slice_types() { - let _value = Box::new("hello world"); + let _value = "hello world"; match_type! {_value, (&str) => {}, @@ -507,7 +508,6 @@ mod tests { } fn owned(value: T) { - let value = Box::new(value); match_type! {value, i32 => { assert_eq!(value, 2); @@ -543,7 +543,6 @@ mod tests { } fn owned>(value: T) { - let value = Box::new(value); match_type! {value, U => { assert_eq!(value, 2); From 5c5b05b360a0da10c7ce923ae098022780d2bdec Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sat, 10 May 2025 00:09:56 -0700 Subject: [PATCH 11/11] Rename to `select_type` --- crates/bevy_reflect/src/macros/match_type.rs | 88 ++++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/crates/bevy_reflect/src/macros/match_type.rs b/crates/bevy_reflect/src/macros/match_type.rs index f319c79ebe..5ccc85807a 100644 --- a/crates/bevy_reflect/src/macros/match_type.rs +++ b/crates/bevy_reflect/src/macros/match_type.rs @@ -5,7 +5,7 @@ /// The syntax of the macro closely resembles a standard `match`, but with some subtle differences. /// /// ```text -/// match_type! { , } +/// select_type! { , } /// /// := IDENT // the variable you’re matching /// @@ -62,11 +62,11 @@ /// # Examples /// /// ``` -/// # use bevy_reflect::macros::match_type; +/// # use bevy_reflect::macros::select_type; /// # use bevy_reflect::PartialReflect; /// # /// fn stringify(mut value: Box) -> String { -/// match_type! { value, +/// select_type! { value, /// // Downcast to an owned type /// f32 => format!("{:.1}", value), /// // Downcast to a mutable reference @@ -98,7 +98,7 @@ /// [`PartialReflect`]: crate::PartialReflect /// [`Type`]: crate::Type #[macro_export] -macro_rules! match_type { +macro_rules! select_type { // === Entry Point === // @@ -108,7 +108,7 @@ macro_rules! match_type { // cast to `dyn PartialReflect` or dereference manually use $crate::PartialReflect; - match_type!(@arm[[], $input] $($tt)*) + select_type!(@arm[[], $input] $($tt)*) }}; // === Arm Parsing === // @@ -123,39 +123,39 @@ macro_rules! match_type { // --- Custom Bindings --- // {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] $new_binding:tt @ $($tt:tt)+} => { - match_type!(@arm [[$($tys),*], $input as $new_binding] $($tt)+) + select_type!(@arm [[$($tys),*], $input as $new_binding] $($tt)+) }; // --- Fallback Case --- // {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] _ $([$types:ident])? $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { - match_type!(@else [[$($tys),*], $input $(as $binding)?, [$($types)?], [$($condition)?], $action] $($tt)+) + select_type!(@else [[$($tys),*], $input $(as $binding)?, [$($types)?], [$($condition)?], $action] $($tt)+) }; {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] _ $([$types:ident])? $(where $condition:expr)? => $action:expr $(,)?} => { - match_type!(@else [[$($tys),*], $input $(as $binding)?, [$($types)?], [$($condition)?], $action]) + select_type!(@else [[$($tys),*], $input $(as $binding)?, [$($types)?], [$($condition)?], $action]) }; // --- Mutable Downcast Case --- // {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] &mut $ty:ty $([$types:ident])? $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { - match_type!(@if [[$($tys,)* &mut $ty], $input $(as $binding)?, mut, $ty, [$($types)?], [$($condition)?], $action] $($tt)+) + select_type!(@if [[$($tys,)* &mut $ty], $input $(as $binding)?, mut, $ty, [$($types)?], [$($condition)?], $action] $($tt)+) }; {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] &mut $ty:ty $([$types:ident])? $(where $condition:expr)? => $action:expr $(,)?} => { - match_type!(@if [[$($tys,)* &mut $ty], $input $(as $binding)?, mut, $ty, [$($types)?], [$($condition)?], $action]) + select_type!(@if [[$($tys,)* &mut $ty], $input $(as $binding)?, mut, $ty, [$($types)?], [$($condition)?], $action]) }; // --- Immutable Downcast Case --- // {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] & $ty:ty $([$types:ident])? $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { - match_type!(@if [[$($tys,)* &$ty], $input $(as $binding)?, ref, $ty, [$($types)?], [$($condition)?], $action] $($tt)+) + select_type!(@if [[$($tys,)* &$ty], $input $(as $binding)?, ref, $ty, [$($types)?], [$($condition)?], $action] $($tt)+) }; {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] & $ty:ty $([$types:ident])? $(where $condition:expr)? => $action:expr $(,)?} => { - match_type!(@if [[$($tys,)* &$ty], $input $(as $binding)?, ref, $ty, [$($types)?], [$($condition)?], $action]) + select_type!(@if [[$($tys,)* &$ty], $input $(as $binding)?, ref, $ty, [$($types)?], [$($condition)?], $action]) }; // --- Owned Downcast Case --- // {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] $ty:ty $([$types:ident])? $(where $condition:expr)? => $action:expr, $($tt:tt)+} => { - match_type!(@if [[$($tys,)* $ty], $input $(as $binding)?, box, $ty, [$($types)?], [$($condition)?], $action] $($tt)+) + select_type!(@if [[$($tys,)* $ty], $input $(as $binding)?, box, $ty, [$($types)?], [$($condition)?], $action] $($tt)+) }; {@arm [[$($tys:ty),*], $input:ident $(as $binding:tt)?] $ty:ty $([$types:ident])? $(where $condition:expr)? => $action:expr $(,)?} => { - match_type!(@if [[$($tys,)* $ty], $input $(as $binding)?, box, $ty, [$($types)?], [$($condition)?], $action]) + select_type!(@if [[$($tys,)* $ty], $input $(as $binding)?, box, $ty, [$($types)?], [$($condition)?], $action]) }; // === Type Matching === // @@ -170,9 +170,9 @@ macro_rules! match_type { // This rule handles the owned downcast case {@if [[$($tys:ty),*], $input:ident $(as $binding:tt)?, box, $ty:ty, [$($types:ident)?], [$($condition:expr)?], $action:expr] $($rest:tt)*} => { #[allow(unused_parens, reason = "may be used for disambiguation")] - match match_type!(@downcast box, $ty, $input) { - Ok(match_type!(@bind [mut] $input $(as $binding)?)) $(if $condition)? => { - match_type!(@collect [$($tys),*] $(as $types)?); + match select_type!(@downcast box, $ty, $input) { + Ok(select_type!(@bind [mut] $input $(as $binding)?)) $(if $condition)? => { + select_type!(@collect [$($tys),*] $(as $types)?); $action }, $input => { @@ -188,39 +188,39 @@ macro_rules! match_type { Err($input) => $input }; - match_type!(@arm [[$($tys),*], $input] $($rest)*) + select_type!(@arm [[$($tys),*], $input] $($rest)*) } } }; // This rule handles the mutable and immutable downcast cases {@if [[$($tys:ty),*], $input:ident $(as $binding:tt)?, $kind:tt, $ty:ty, [$($types:ident)?], [$($condition:expr)?], $action:expr] $($rest:tt)*} => { #[allow(unused_parens, reason = "may be used for disambiguation")] - match match_type!(@downcast $kind, $ty, $input) { - Some(match_type!(@bind [] $input $(as $binding)?)) $(if $condition)? => { - match_type!(@collect [$($tys),*] $(as $types)?); + match select_type!(@downcast $kind, $ty, $input) { + Some(select_type!(@bind [] $input $(as $binding)?)) $(if $condition)? => { + select_type!(@collect [$($tys),*] $(as $types)?); $action }, _ => { - match_type!(@arm [[$($tys),*], $input] $($rest)*) + select_type!(@arm [[$($tys),*], $input] $($rest)*) } } }; // This rule handles the fallback case where a condition has been provided {@else [[$($tys:ty),*], $input:ident $(as $binding:tt)?, [$($types:ident)?], [$condition:expr], $action:expr] $($rest:tt)*} => {{ - match_type!(@collect [$($tys),*] $(as $types)?); - let match_type!(@bind [mut] _ $(as $binding)?) = $input; + select_type!(@collect [$($tys),*] $(as $types)?); + let select_type!(@bind [mut] _ $(as $binding)?) = $input; if $condition { $action } else { - match_type!(@arm [[$($tys),*], $input] $($rest)*) + select_type!(@arm [[$($tys),*], $input] $($rest)*) } }}; // This rule handles the fallback case where no condition has been provided {@else [[$($tys:ty),*], $input:ident $(as $binding:tt)?, [$($types:ident)?], [], $action:expr] $($rest:tt)*} => {{ - match_type!(@collect [$($tys),*] $(as $types)?); - let match_type!(@bind [mut] _ $(as $binding)?) = $input; + select_type!(@collect [$($tys),*] $(as $types)?); + let select_type!(@bind [mut] _ $(as $binding)?) = $input; $action }}; @@ -269,7 +269,7 @@ macro_rules! match_type { }; } -pub use match_type; +pub use select_type; #[cfg(test)] mod tests { @@ -293,8 +293,8 @@ mod tests { #[test] fn should_allow_empty() { fn empty(_value: Box) { - let _: () = match_type! {_value}; - let _: () = match_type! {_value,}; + let _: () = select_type! {_value}; + let _: () = select_type! {_value,}; } empty(Box::new(42)); @@ -303,7 +303,7 @@ mod tests { #[test] fn should_downcast_ref() { fn to_string(value: &dyn PartialReflect) -> String { - match_type! {value, + select_type! {value, &String => value.clone(), &i32 => value.to_string(), &f32 => format!("{:.2}", value), @@ -320,7 +320,7 @@ mod tests { #[test] fn should_downcast_mut() { fn push_value(container: &mut dyn PartialReflect, value: i32) -> bool { - match_type! {container, + select_type! {container, &mut Vec => container.push(value), &mut Vec => container.push(value as u32), _ => return false @@ -344,7 +344,7 @@ mod tests { #[test] fn should_downcast_owned() { fn into_string(value: Box) -> Option { - match_type! {value, + select_type! {value, String => Some(value), i32 => Some(value.to_string()), _ => None @@ -369,7 +369,7 @@ mod tests { let original_value = String::from("hello"); let cloned_value = original_value.clone(); - let value = match_type! {cloned_value, + let value = select_type! {cloned_value, _ @ Option => panic!("unexpected type"), _ => cloned_value }; @@ -380,7 +380,7 @@ mod tests { #[test] fn should_allow_mixed_borrows() { fn process(value: Box) { - match_type! {value, + select_type! {value, Option => { let value = value.unwrap(); assert_eq!(value, 1.0); @@ -410,7 +410,7 @@ mod tests { #[test] fn should_allow_custom_bindings() { fn process(mut value: Box) { - match_type! {value, + select_type! {value, foo @ &mut i32 => { *foo *= 2; assert_eq!(*foo, 246); @@ -438,7 +438,7 @@ mod tests { fn should_handle_slice_types() { let _value = "hello world"; - match_type! {_value, + select_type! {_value, (&str) => {}, _ => panic!("unexpected type"), } @@ -447,7 +447,7 @@ mod tests { #[test] fn should_capture_types() { fn test(mut value: Box) { - match_type! {value, + select_type! {value, _ @ &mut u8 [types] => { assert_eq!(types.len(), 1); assert_eq!(types[0], Type::of::<&mut u8>()); @@ -490,7 +490,7 @@ mod tests { #[test] fn should_downcast_from_generic() { fn immutable(value: &T) { - match_type! {value, + select_type! {value, &i32 => { assert_eq!(*value, 1); }, @@ -499,7 +499,7 @@ mod tests { } fn mutable(value: &mut T) { - match_type! {value, + select_type! {value, &mut i32 => { *value = 2; }, @@ -508,7 +508,7 @@ mod tests { } fn owned(value: T) { - match_type! {value, + select_type! {value, i32 => { assert_eq!(value, 2); }, @@ -525,7 +525,7 @@ mod tests { #[test] fn should_downcast_to_generic() { fn immutable>(value: &T) { - match_type! {value, + select_type! {value, &U => { assert_eq!(*value, 1); }, @@ -534,7 +534,7 @@ mod tests { } fn mutable>(value: &mut T) { - match_type! {value, + select_type! {value, &mut U => { *value *= 2; }, @@ -543,7 +543,7 @@ mod tests { } fn owned>(value: T) { - match_type! {value, + select_type! {value, U => { assert_eq!(value, 2); },