From 4e6e619f77a20d4c2249794e090892d265738590 Mon Sep 17 00:00:00 2001 From: Ashish Myles Date: Fri, 20 Jun 2025 12:00:31 -0700 Subject: [PATCH] Deduplicate `property_matcher!` and `field_matcher!` variants using the nifty trick from https://users.rust-lang.org/t/130872. PiperOrigin-RevId: 773790208 --- googletest/src/matchers/field_matcher.rs | 60 ++++++++------ googletest/src/matchers/property_matcher.rs | 89 ++++++++------------- 2 files changed, 69 insertions(+), 80 deletions(-) diff --git a/googletest/src/matchers/field_matcher.rs b/googletest/src/matchers/field_matcher.rs index 8dbab0f4..98eba6dc 100644 --- a/googletest/src/matchers/field_matcher.rs +++ b/googletest/src/matchers/field_matcher.rs @@ -196,38 +196,47 @@ macro_rules! __field { #[doc(hidden)] #[macro_export] macro_rules! field_internal { - (&$($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? .$field:tt, ref $m:expr) => {{ + // `ref` variant. + ( + // The 3 cases of `$(& $($_amp:literal)?)?` -> `$(& $($_amp)*)*` + // 1. outer group captures nothing => expansion produces nothing + // 2. outer group captures just `&` => expansion produces `&` + // 3. outer group captures `& ` => disallowed by `@assert_empty` subrule invocation + // + // `$_amp:literal` works only because the following `$t:ident` or `::` can't be captured by + // it. + $(& $($_amp:literal)?)? // Optional `&`'s presence is implicitly captured by `_amp`. + $(:: $($_cs:literal)?)? // Optional `::`'s presence is implicitly captured by `_cs`. + $($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? .$field:tt, + ref $m:expr) => {{ + $crate::field_internal!(@assert_empty $($($_amp)*)* $($($_cs)*)*); $crate::field_internal!(@internal - [&_] [&$($t)::* $(::<$($t_ty_args),*>)*] + struct_type: [&_] + field_prefix: [$(& $($_amp)*)* $(:: $($_cs)*)* $($t)::* $(::<$($t_ty_args),*>)*] [$field] [ref] [$m]) }}; - (&$($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? .$field:tt, $m:expr) => {{ - $crate::field_internal!(@internal - [&&_] [&$($t)::* $(::<$($t_ty_args),*>)*] - [$field] [] [$m]) - }}; - ($($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? .$field:tt, $m:expr) => {{ - $crate::field_internal!(@internal - [&_] [$($t)::* $(::<$($t_ty_args),*>)*] - [$field] [] [$m]) - }}; - (& :: $($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? .$field:tt, ref $m:expr) => {{ - $crate::field_internal!(@internal - [&_] [&::$($t)::* $(::<$($t_ty_args),*>)*] - [$field] [ref] [$m]) - }}; - (& :: $($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? .$field:tt, $m:expr) => {{ - $crate::field_internal!(@internal - [&&_] [&::$($t)::* $(::<$($t_ty_args),*>)*] - [$field] [] [$m]) - }}; - (:: $($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? .$field:tt, $m:expr) => {{ + + // Non-`ref` variant. + ( + // See comment on previous variant above. + $(& $($_amp:literal)?)? // Optional `&`'s presence is implicitly captured by `_amp`. + $(:: $($_cs:literal)?)? // Optional `::`'s presence is implicitly captured by `_cs`. + $($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? .$field:tt, $m:expr) => {{ + $crate::field_internal!(@assert_empty $($($_amp)*)* $($($_cs)*)*); $crate::field_internal!(@internal - [&_] [::$($t)::* $(::<$($t_ty_args),*>)*] + struct_type: [$(& $($_amp)*)* &_] + field_prefix: [$(& $($_amp)*)* $(:: $($_cs)*)* $($t)::* $(::<$($t_ty_args),*>)*] [$field] [] [$m]) }}; - (@internal [$struct_ty:ty] [$($field_prefix:tt)*] [$field:tt] [$($ref:tt)?] [$m:expr]) => {{ + (@assert_empty) => {}; + (@assert_empty $($l:literal)+) => { + compile_error!("property! argument must start with an optional `&` followed by a path") + }; + + (@internal struct_type: [$struct_ty:ty] + field_prefix: [$($field_prefix:tt)*] + [$field:tt] [$($ref:tt)?] [$m:expr]) => {{ $crate::matchers::__internal_unstable_do_not_depend_on_these::field_matcher( |o: $struct_ty| { match o { @@ -241,7 +250,6 @@ macro_rules! field_internal { }, &stringify!($field), $crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!($m)) - }} } diff --git a/googletest/src/matchers/property_matcher.rs b/googletest/src/matchers/property_matcher.rs index 86307c2e..c77b726a 100644 --- a/googletest/src/matchers/property_matcher.rs +++ b/googletest/src/matchers/property_matcher.rs @@ -176,68 +176,49 @@ macro_rules! __property { #[doc(hidden)] #[macro_export] macro_rules! property_internal { - (& $($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? + // `ref` variant. + ( + // The 3 cases of `$(& $($_amp:literal)?)?` -> `$(& $($_amp)*)*` + // 1. outer group captures nothing => expansion produces nothing + // 2. outer group captures just `&` => expansion produces `&` + // 3. outer group captures `& ` => disallowed by `@assert_empty` subrule invocation + // + // `$_amp:literal` works only because the following `$t:ident` or `::` can't be captured by + // it. + $(& $($_amp:literal)?)? // Optional `&`'s presence is implicitly captured by `_amp`. + $(:: $($_cs:literal)?)? // Optional `::`'s presence is implicitly captured by `_cs`. + $($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? .$method:tt $(::<$($m_ty_args:ty),* $(,)?>)? ($($argument:expr),* $(,)?), - ref $m:expr) => {{ - $crate::property_internal!(@self_arg - struct_type: [&$($t)::+ $( <$($t_ty_args),*>)*] - method_prefix: [ $($t)::+ $(::<$($t_ty_args),*>)*] - [$method $(::<$($m_ty_args),*>)*] [$($argument),*] [$m]) - }}; - ($($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? - .$method:tt $(::<$($m_ty_args:ty),* $(,)?>)? ($($argument:expr),* $(,)?), - ref $m:expr) => {{ - $crate::property_internal!(@self_arg - struct_type: [$($t)::+ $( <$($t_ty_args),*>)*] - method_prefix: [$($t)::+ $(::<$($t_ty_args),*>)*] - [$method $(::<$($m_ty_args),*>)*] [$($argument),*] [$m]) - }}; - (& ::$($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? - .$method:tt $(::<$($m_ty_args:ty),* $(,)?>)? ($($argument:expr),* $(,)?), - ref $m:expr) => {{ - $crate::property_internal!(@self_arg - struct_type: [&::$($t)::+ $( <$($t_ty_args),*>)*] - method_prefix: [ ::$($t)::+ $(::<$($t_ty_args),*>)*] - [$method $(::<$($m_ty_args),*>)*] [$($argument),*] [$m]) - }}; - (::$($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? - .$method:tt $(::<$($m_ty_args:ty),* $(,)?>)? ($($argument:expr),* $(,)?), - ref $m:expr) => {{ - $crate::property_internal!(@self_arg - struct_type: [::$($t)::+ $( <$($t_ty_args),*>)*] - method_prefix: [::$($t)::+ $(::<$($t_ty_args),*>)*] + ref $m:expr + ) => {{ + $crate::property_internal!(@assert_empty $($($_amp)*)* $($($_cs)*)*); + $crate::property_internal!( + @self_arg + struct_type: [$(& $($_amp)*)* $(:: $($_cs)*)* $($t)::+ $( <$($t_ty_args),*>)*] + method_prefix: [ $(:: $($_cs)*)* $($t)::+ $(::<$($t_ty_args),*>)*] [$method $(::<$($m_ty_args),*>)*] [$($argument),*] [$m]) }}; - (& $($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? + // Non-`ref` variant. + ( + // See comment on previous variant above. + $(& $($_amp:literal)?)? // Optional `&`'s presence is implicitly captured by `_amp`. + $(:: $($_cs:literal)?)? // Optional `::`'s presence is implicitly captured by `_cs`. + $($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? .$method:tt $(::<$($m_ty_args:ty),* $(,)?>)? ($($argument:expr),* $(,)?), $m:expr) => {{ - $crate::property_internal!(@self_dot - struct_type: [&&$($t)::+ $(<$($t_ty_args),*>)*] - [$method $(::<$($m_ty_args),*>)*] [$($argument),*] [$m]) - }}; - ($($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? - .$method:tt $(::<$($m_ty_args:ty),* $(,)?>)? ($($argument:expr),* $(,)?), - $m:expr) => {{ - $crate::property_internal!(@self_dot - struct_type: [&$($t)::+ $(<$($t_ty_args),*>)*] - [$method $(::<$($m_ty_args),*>)*] [$($argument),*] [$m]) - }}; - (& ::$($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? - .$method:tt $(::<$($m_ty_args:ty),* $(,)?>)? ($($argument:expr),* $(,)?), - $m:expr) => {{ - $crate::property_internal!(@self_dot - struct_type: [&&::$($t)::+ $(<$($t_ty_args),*>)*] - [$method $(::<$($m_ty_args),*>)*] [$($argument),*] [$m]) - }}; - (::$($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? - .$method:tt $(::<$($m_ty_args:ty),* $(,)?>)? ($($argument:expr),* $(,)?), - $m:expr) => {{ - $crate::property_internal!(@self_dot - struct_type: [&::$($t)::+ $(<$($t_ty_args),*>)*] + $crate::property_internal!(@assert_empty $($($_amp)*)* $($($_cs)*)*); + $crate::property_internal!( + @self_dot + struct_type: [$(& $($_amp)*)* $(:: $($_cs)*)* $($t)::+ $(<$($t_ty_args),*>)*] [$method $(::<$($m_ty_args),*>)*] [$($argument),*] [$m]) }}; + (@assert_empty) => {}; + (@assert_empty $($l:literal)+) => { + compile_error!("property! argument must start with an optional `&` followed by a path") + }; + (@self_arg struct_type: [$struct_ty:ty] method_prefix: [$($method_prefix:tt)+] [$($method:tt)*] [$($argument:expr),*] [$m:expr]) => {{ @@ -250,7 +231,7 @@ macro_rules! property_internal { (@self_dot struct_type: [$struct_ty:ty] [$($method:tt)*] [$($argument:expr),*] [$m:expr]) => {{ $crate::matchers::__internal_unstable_do_not_depend_on_these::property_matcher( - |o: $struct_ty| o.$($method)* ($($argument),*), + |o: &$struct_ty| o.$($method)* ($($argument),*), &stringify!($($method)* ($($argument),*)), $crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!($m)) }};