diff --git a/googletest/src/lib.rs b/googletest/src/lib.rs index 3f59546d..382fac2a 100644 --- a/googletest/src/lib.rs +++ b/googletest/src/lib.rs @@ -30,7 +30,7 @@ pub mod matcher; pub mod matcher_support; pub mod matchers; -pub use googletest_macro::{__abbreviated_stringify, __googletest_macro_verify_pred}; +pub use googletest_macro::__abbreviated_stringify; /// Re-exports of the symbols in this crate which are most likely to be used. /// diff --git a/googletest/src/matchers/matches_pattern.rs b/googletest/src/matchers/matches_pattern.rs index 7dc9ce33..5de7d7c0 100644 --- a/googletest/src/matchers/matches_pattern.rs +++ b/googletest/src/matchers/matches_pattern.rs @@ -293,571 +293,14 @@ macro_rules! __matches_pattern { #[doc(hidden)] #[macro_export] macro_rules! matches_pattern_internal { - ( - @name [$($struct_name:tt)*], - { $field_name:ident : ref $matcher:expr $(,)? } - ) => { - $crate::matchers::__internal_unstable_do_not_depend_on_these::is( - stringify!($($struct_name)*), - all!(field!($($struct_name)*.$field_name, ref $matcher)) - ) - }; - - ( - @name [$($struct_name:tt)*], - { $field_name:ident : $matcher:expr $(,)? } - ) => { - $crate::matchers::__internal_unstable_do_not_depend_on_these::is( - stringify!($($struct_name)*), - all!(field!($($struct_name)*.$field_name, $matcher)) - ) - }; - - ( - @name [$($struct_name:tt)*], - { $property_name:ident($($argument:expr),* $(,)?) : ref $matcher:expr $(,)? } - ) => { - $crate::matchers::__internal_unstable_do_not_depend_on_these::is( - stringify!($($struct_name)*), - all!(property!($($struct_name)*.$property_name($($argument),*), ref $matcher)) - ) - }; - - ( - @name [$($struct_name:tt)*], - { $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? } - ) => { - $crate::matchers::__internal_unstable_do_not_depend_on_these::is( - stringify!($($struct_name)*), - all!(property!($($struct_name)*.$property_name($($argument),*), $matcher)) - ) - }; - - ( - @name [$($struct_name:tt)*], - { $field_name:ident : ref $matcher:expr, $($rest:tt)* } - ) => { - $crate::matches_pattern_internal!( - @fields (field!($($struct_name)*.$field_name, ref $matcher)), - [$($struct_name)*], - { $($rest)* } - ) - }; - - ( - @name [$($struct_name:tt)*], - { $field_name:ident : $matcher:expr, $($rest:tt)* } - ) => { - $crate::matches_pattern_internal!( - @fields (field!($($struct_name)*.$field_name, $matcher)), - [$($struct_name)*], - { $($rest)* } - ) - }; - - ( - @name [$($struct_name:tt)*], - { $property_name:ident($($argument:expr),* $(,)?) : ref $matcher:expr, $($rest:tt)* } - ) => { - $crate::matches_pattern_internal!( - @fields (property!($($struct_name)*.$property_name($($argument),*), ref $matcher)), - [$($struct_name)*], - { $($rest)* } - ) - }; - - ( - @name [$($struct_name:tt)*], - { $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* } - ) => { - $crate::matches_pattern_internal!( - @fields (property!($($struct_name)*.$property_name($($argument),*), $matcher)), - [$($struct_name)*], - { $($rest)* } - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - { $field_name:ident : ref $matcher:expr $(,)? } - ) => { - $crate::matchers::__internal_unstable_do_not_depend_on_these::is( - stringify!($($struct_name)*), - all!( - $($processed)*, - field!($($struct_name)*.$field_name, ref $matcher) - )) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - { $field_name:ident : $matcher:expr $(,)? } - ) => { - $crate::matchers::__internal_unstable_do_not_depend_on_these::is( - stringify!($($struct_name)*), - all!( - $($processed)*, - field!($($struct_name)*.$field_name, $matcher) - )) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - { $property_name:ident($($argument:expr),* $(,)?) : ref $matcher:expr $(,)? } - ) => { - $crate::matchers::__internal_unstable_do_not_depend_on_these::is( - stringify!($($struct_name)*), - all!( - $($processed)*, - property!($($struct_name)*.$property_name($($argument),*), ref $matcher) - )) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - { $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? } - ) => { - $crate::matchers::__internal_unstable_do_not_depend_on_these::is( - stringify!($($struct_name)*), - all!( - $($processed)*, - property!($($struct_name)*.$property_name($($argument),*), $matcher) - )) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - { $field_name:ident : ref $matcher:expr, $($rest:tt)* } - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.$field_name, ref $matcher) - ), - [$($struct_name)*], - { $($rest)* } - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - { $field_name:ident : $matcher:expr, $($rest:tt)* } - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.$field_name, $matcher) - ), - [$($struct_name)*], - { $($rest)* } - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - { $property_name:ident($($argument:expr),* $(,)?) : ref $matcher:expr, $($rest:tt)* } - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - property!(ref $($struct_name)*.$property_name($($argument),*), $matcher) - ), - [$($struct_name)*], - { $($rest)* } - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - { $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* } - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - property!($($struct_name)*.$property_name($($argument),*), $matcher) - ), - [$($struct_name)*], - { $($rest)* } - ) - }; - - ( - @name [$($struct_name:tt)*], - ) => { - $crate::matchers::__internal_unstable_do_not_depend_on_these::pattern_only( - |v| matches!(v, $($struct_name)*), - concat!("is ", stringify!($($struct_name)*)), - concat!("is not ", stringify!($($struct_name)*))) - }; - - ( - @name [$($struct_name:tt)*], - (ref $matcher:expr $(,)?) - ) => { - $crate::matchers::__internal_unstable_do_not_depend_on_these::is( - stringify!($($struct_name)*), - all!(field!($($struct_name)*.0, ref $matcher)) - ) - }; - - ( - @name [$($struct_name:tt)*], - ($matcher:expr $(,)?) - ) => { - $crate::matchers::__internal_unstable_do_not_depend_on_these::is( - stringify!($($struct_name)*), - all!(field!($($struct_name)*.0, $matcher)) - ) - }; - - ( - @name [$($struct_name:tt)*], - (ref $matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - field!($($struct_name)*.0, ref $matcher) - ), - [$($struct_name)*], - 1, - ($($rest)*) - ) - }; - - ( - @name [$($struct_name:tt)*], - ($matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - field!($($struct_name)*.0, $matcher) - ), - [$($struct_name)*], - 1, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - $field:tt, - (ref $matcher:expr $(,)?) - ) => { - $crate::matchers::__internal_unstable_do_not_depend_on_these::is( - stringify!($($struct_name)*), - all!( - $($processed)*, - field!($($struct_name)*.$field, ref $matcher) - )) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - $field:tt, - ($matcher:expr $(,)?) - ) => { - $crate::matchers::__internal_unstable_do_not_depend_on_these::is( - stringify!($($struct_name)*), - all!( - $($processed)*, - field!($($struct_name)*.$field, $matcher) - )) - }; - - // We need to repeat this once for every supported field position, unfortunately. There appears - // to be no way in declarative macros to compute $field + 1 and have the result evaluated to a - // token which can be used as a tuple index. - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 1, - (ref $matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.1, ref $matcher) - ), - [$($struct_name)*], - 2, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 1, - ($matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.1, $matcher) - ), - [$($struct_name)*], - 2, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 2, - (ref $matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.2, ref $matcher) - ), - [$($struct_name)*], - 3, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 2, - ($matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.2, $matcher) - ), - [$($struct_name)*], - 3, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 3, - (ref $matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.3, ref $matcher) - ), - [$($struct_name)*], - 4, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 3, - ($matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.3, $matcher) - ), - [$($struct_name)*], - 4, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 4, - (ref $matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.4, ref $matcher) - ), - [$($struct_name)*], - 5, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 4, - ($matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.4, $matcher) - ), - [$($struct_name)*], - 5, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 5, - (ref $matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.5, ref $matcher) - ), - [$($struct_name)*], - 6, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 5, - ($matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.5, $matcher) - ), - [$($struct_name)*], - 6, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 6, - (ref $matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.6, ref $matcher) - ), - [$($struct_name)*], - 7, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 6, - ($matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.6, $matcher) - ), - [$($struct_name)*], - 7, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 7, - (ref $matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.7, ref $matcher) - ), - [$($struct_name)*], - 8, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 7, - ($matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.7, $matcher) - ), - [$($struct_name)*], - 8, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 8, - (ref $matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.8, ref $matcher) - ), - [$($struct_name)*], - 9, - ($($rest)*) - ) - }; - - ( - @fields ($($processed:tt)*), - [$($struct_name:tt)*], - 8, - ($matcher:expr, $($rest:tt)*) - ) => { - $crate::matches_pattern_internal!( - @fields ( - $($processed)*, - field!($($struct_name)*.8, $matcher) - ), - [$($struct_name)*], - 9, - ($($rest)*) - ) - }; - - (@name [$($struct_name:tt)*], $first:tt $($rest:tt)*) => { - $crate::matches_pattern_internal!(@name [$($struct_name)* $first], $($rest)*) + ($($tt:tt)*) => { + { + use $crate::{self as googletest}; + #[allow(unused)] + use $crate::matchers::{all, field, property}; + $crate::matchers::__internal_unstable_do_not_depend_on_these::__googletest_macro_matches_pattern!($($tt)*) + } }; - - ($first:tt $($rest:tt)*) => {{ - #[allow(unused)] - use $crate::matchers::{all, field, property}; - $crate::matches_pattern_internal!(@name [$first], $($rest)*) - }}; } /// An alias for [`matches_pattern`][crate::matchers::matches_pattern!]. @@ -872,6 +315,8 @@ pub mod internal { use crate::matcher::{Matcher, MatcherBase}; use std::fmt::Debug; + pub use ::googletest_macro::__googletest_macro_matches_pattern; + // Specialized implementation of the `predicate` matcher to support ref binding // mode for `matches_pattern`. pub fn pattern_only<T>( diff --git a/googletest/src/matchers/mod.rs b/googletest/src/matchers/mod.rs index 16ca900f..18ea1b28 100644 --- a/googletest/src/matchers/mod.rs +++ b/googletest/src/matchers/mod.rs @@ -113,7 +113,7 @@ pub mod __internal_unstable_do_not_depend_on_these { pub use super::elements_are_matcher::internal::ElementsAre; pub use super::field_matcher::internal::field_matcher; pub use super::is_matcher::is; - pub use super::matches_pattern::internal::pattern_only; + pub use super::matches_pattern::internal::{__googletest_macro_matches_pattern, pattern_only}; pub use super::pointwise_matcher::internal::PointwiseMatcher; pub use super::property_matcher::internal::{property_matcher, property_ref_matcher}; pub use super::result_of_matcher::internal::{result_of, result_of_ref}; diff --git a/googletest/src/matchers/property_matcher.rs b/googletest/src/matchers/property_matcher.rs index 98c51a67..5744f8ef 100644 --- a/googletest/src/matchers/property_matcher.rs +++ b/googletest/src/matchers/property_matcher.rs @@ -177,25 +177,25 @@ macro_rules! __property { #[macro_export] macro_rules! property_internal { - (&$($t:ident)::+.$method:tt($($argument:tt),* $(,)?), ref $m:expr) => {{ + (&$($t:ident)::+.$method:tt($($argument:expr),* $(,)?), ref $m:expr) => {{ $crate::matchers::__internal_unstable_do_not_depend_on_these::property_ref_matcher( |o: &$($t)::+| $($t)::+::$method(o, $($argument),*), &stringify!($method($($argument),*)), $crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!($m)) }}; - ($($t:ident)::+.$method:tt($($argument:tt),* $(,)?), ref $m:expr) => {{ + ($($t:ident)::+.$method:tt($($argument:expr),* $(,)?), ref $m:expr) => {{ $crate::matchers::__internal_unstable_do_not_depend_on_these::property_ref_matcher( |o: $($t)::+| $($t)::+::$method(o, $($argument),*), &stringify!($method($($argument),*)), $crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!($m)) }}; - (& $($t:ident)::+.$method:tt($($argument:tt),* $(,)?), $m:expr) => {{ + (& $($t:ident)::+.$method:tt($($argument:expr),* $(,)?), $m:expr) => {{ $crate::matchers::__internal_unstable_do_not_depend_on_these::property_matcher( |o: &&$($t)::+| o.$method($($argument),*), &stringify!($method($($argument),*)), $crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!($m)) }}; - ($($t:ident)::+.$method:tt($($argument:tt),* $(,)?), $m:expr) => {{ + ($($t:ident)::+.$method:tt($($argument:expr),* $(,)?), $m:expr) => {{ $crate::matchers::__internal_unstable_do_not_depend_on_these::property_matcher( |o: &$($t)::+| o.$method($($argument),*), &stringify!($method($($argument),*)), diff --git a/googletest/tests/matches_pattern_test.rs b/googletest/tests/matches_pattern_test.rs index d1b97e79..186a488e 100644 --- a/googletest/tests/matches_pattern_test.rs +++ b/googletest/tests/matches_pattern_test.rs @@ -146,7 +146,6 @@ fn has_correct_assertion_failure_message_for_single_field() -> Result<()> { let actual = AStruct { a_field: 123 }; let result = verify_that!(actual, matches_pattern!(&AStruct { a_field: eq(234) })); - #[rustversion::before(1.76)] const EXPECTED: &str = indoc!( " Value of: actual @@ -156,16 +155,6 @@ fn has_correct_assertion_failure_message_for_single_field() -> Result<()> { " ); - #[rustversion::since(1.76)] - const EXPECTED: &str = indoc!( - " - Value of: actual - Expected: is &AStruct which has field `a_field`, which is equal to 234 - Actual: AStruct { a_field: 123 }, - which has field `a_field`, which isn't equal to 234 - " - ); - verify_that!(result, err(displays_as(contains_substring(EXPECTED)))) } @@ -182,7 +171,6 @@ fn has_correct_assertion_failure_message_for_two_fields() -> Result<()> { matches_pattern!(&AStruct { a_field: eq(234), another_field: eq(123) }) ); - #[rustversion::before(1.76)] const EXPECTED: &str = indoc!( " Value of: actual @@ -194,18 +182,6 @@ fn has_correct_assertion_failure_message_for_two_fields() -> Result<()> { * which has field `another_field`, which isn't equal to 123" ); - #[rustversion::since(1.76)] - const EXPECTED: &str = indoc!( - " - Value of: actual - Expected: is &AStruct which has all the following properties: - * has field `a_field`, which is equal to 234 - * has field `another_field`, which is equal to 123 - Actual: AStruct { a_field: 123, another_field: 234 }, - * which has field `a_field`, which isn't equal to 234 - * which has field `another_field`, which isn't equal to 123" - ); - verify_that!(result, err(displays_as(contains_substring(EXPECTED)))) } @@ -227,27 +203,14 @@ fn has_correct_assertion_failure_message_for_field_and_property() -> Result<()> matches_pattern!(&AStruct { get_field(): eq(234), another_field: eq(123) }) ); - #[rustversion::before(1.76)] const EXPECTED: &str = indoc!( " Value of: actual Expected: is & AStruct which has all the following properties: - * has property `get_field ()`, which is equal to 234 - * has field `another_field`, which is equal to 123 - Actual: AStruct { a_field: 123, another_field: 234 }, - * whose property `get_field ()` is `123`, which isn't equal to 234 - * which has field `another_field`, which isn't equal to 123" - ); - - #[rustversion::since(1.76)] - const EXPECTED: &str = indoc!( - " - Value of: actual - Expected: is &AStruct which has all the following properties: - * has property `get_field ()`, which is equal to 234 + * has property `get_field()`, which is equal to 234 * has field `another_field`, which is equal to 123 Actual: AStruct { a_field: 123, another_field: 234 }, - * whose property `get_field ()` is `123`, which isn't equal to 234 + * whose property `get_field()` is `123`, which isn't equal to 234 * which has field `another_field`, which isn't equal to 123" ); @@ -484,7 +447,8 @@ fn matches_enum_without_field() -> Result<()> { } let actual = AnEnum::A; - verify_that!(actual, matches_pattern!(&AnEnum::A)) + verify_that!(actual, matches_pattern!(&AnEnum::A))?; + verify_that!(actual, matches_pattern!(&AnEnum::A,)) } #[test] @@ -495,7 +459,8 @@ fn matches_enum_without_field_ref_binding_mode() -> Result<()> { } let actual = AnEnum::A; - verify_that!(actual, matches_pattern!(AnEnum::A)) + verify_that!(actual, matches_pattern!(AnEnum::A))?; + verify_that!(actual, matches_pattern!(AnEnum::A,)) } #[test] @@ -506,7 +471,43 @@ fn matches_enum_without_field_copy() -> Result<()> { } let actual = AnEnum::A; - verify_that!(actual, matches_pattern!(AnEnum::A)) + verify_that!(actual, matches_pattern!(AnEnum::A))?; + verify_that!(actual, matches_pattern!(AnEnum::A,)) +} + +#[test] +fn matches_match_pattern_literal() -> Result<()> { + let actual = false; + #[allow(clippy::redundant_pattern_matching)] + verify_that!(actual, matches_pattern!(false))?; + #[allow(clippy::redundant_pattern_matching)] + verify_that!(actual, matches_pattern!(false,))?; + let actual = 1; + verify_that!(actual, matches_pattern!(1))?; + verify_that!(actual, matches_pattern!(1,))?; + let actual = "test"; + verify_that!(actual, matches_pattern!(&"test"))?; + verify_that!(actual, matches_pattern!(&"test",)) +} + +#[test] +fn matches_match_pattern_struct() -> Result<()> { + #[allow(dead_code)] + #[derive(Debug)] + struct AStruct { + a: u32, + } + let actual = AStruct { a: 123 }; + verify_that!(actual, matches_pattern!(AStruct { .. })) +} + +#[test] +fn matches_match_pattern_tuple() -> Result<()> { + #[allow(dead_code)] + #[derive(Debug)] + struct AStruct(u32); + let actual = AStruct(123); + verify_that!(actual, matches_pattern!(AStruct(_))) } #[test] @@ -521,12 +522,7 @@ fn generates_correct_failure_output_when_enum_variant_without_field_is_not_match let result = verify_that!(actual, matches_pattern!(&AnEnum::A)); - #[rustversion::before(1.76)] const EXPECTED: &str = "is not & AnEnum :: A"; - - #[rustversion::since(1.76)] - const EXPECTED: &str = "is not &AnEnum::A"; - verify_that!(result, err(displays_as(contains_substring(EXPECTED)))) } #[test] @@ -539,12 +535,7 @@ fn generates_correct_failure_output_when_enum_variant_without_field_is_matched() let result = verify_that!(actual, not(matches_pattern!(&AnEnum::A))); - #[rustversion::before(1.76)] const EXPECTED: &str = "is & AnEnum :: A"; - - #[rustversion::since(1.76)] - const EXPECTED: &str = "is &AnEnum::A"; - verify_that!(result, err(displays_as(contains_substring(EXPECTED)))) } #[test] @@ -579,12 +570,7 @@ fn includes_enum_variant_in_description_with_field() -> Result<()> { let result = verify_that!(actual, matches_pattern!(&AnEnum::A(eq(234)))); - #[rustversion::before(1.76)] const EXPECTED: &str = "Expected: is & AnEnum :: A which has field `0`"; - - #[rustversion::since(1.76)] - const EXPECTED: &str = "Expected: is &AnEnum::A which has field `0`"; - verify_that!(result, err(displays_as(contains_substring(EXPECTED)))) } #[test] @@ -597,12 +583,7 @@ fn includes_enum_variant_in_negative_description_with_field() -> Result<()> { let result = verify_that!(actual, not(matches_pattern!(&AnEnum::A(eq(123))))); - #[rustversion::before(1.76)] const EXPECTED: &str = "Expected: is not & AnEnum :: A which has field `0`, which is equal to"; - - #[rustversion::since(1.76)] - const EXPECTED: &str = "Expected: is not &AnEnum::A which has field `0`, which is equal to"; - verify_that!(result, err(displays_as(contains_substring(EXPECTED)))) } #[test] @@ -615,12 +596,7 @@ fn includes_enum_variant_in_description_with_two_fields() -> Result<()> { let result = verify_that!(actual, matches_pattern!(&AnEnum::A(eq(234), eq(234)))); - #[rustversion::before(1.76)] const EXPECTED: &str = "Expected: is & AnEnum :: A which has all the following properties"; - - #[rustversion::since(1.76)] - const EXPECTED: &str = "Expected: is &AnEnum::A which has all the following properties"; - verify_that!(result, err(displays_as(contains_substring(EXPECTED)))) } #[test] @@ -633,12 +609,7 @@ fn includes_enum_variant_in_description_with_three_fields() -> Result<()> { let result = verify_that!(actual, matches_pattern!(&AnEnum::A(eq(234), eq(234), eq(345)))); - #[rustversion::before(1.76)] const EXPECTED: &str = "Expected: is & AnEnum :: A which has all the following properties"; - - #[rustversion::since(1.76)] - const EXPECTED: &str = "Expected: is &AnEnum::A which has all the following properties"; - verify_that!(result, err(displays_as(contains_substring(EXPECTED)))) } #[test] @@ -651,12 +622,7 @@ fn includes_enum_variant_in_description_with_named_field() -> Result<()> { let result = verify_that!(actual, matches_pattern!(&AnEnum::A { field: eq(234) })); - #[rustversion::before(1.76)] const EXPECTED: &str = "Expected: is & AnEnum :: A which has field `field`"; - - #[rustversion::since(1.76)] - const EXPECTED: &str = "Expected: is &AnEnum::A which has field `field`"; - verify_that!(result, err(displays_as(contains_substring(EXPECTED)))) } #[test] @@ -672,12 +638,7 @@ fn includes_enum_variant_in_description_with_two_named_fields() -> Result<()> { matches_pattern!(&AnEnum::A { field: eq(234), another_field: eq(234) }) ); - #[rustversion::before(1.76)] const EXPECTED: &str = "Expected: is & AnEnum :: A which has all the following properties"; - - #[rustversion::since(1.76)] - const EXPECTED: &str = "Expected: is &AnEnum::A which has all the following properties"; - verify_that!(&result, err(displays_as(contains_substring(EXPECTED)))) } #[test] @@ -695,12 +656,7 @@ fn includes_struct_name_in_description_with_property() -> Result<()> { let result = verify_that!(actual, matches_pattern!(&AStruct { get_field(): eq(234) })); - #[rustversion::before(1.76)] - const EXPECTED: &str = "Expected: is & AStruct which has property `get_field ()`"; - - #[rustversion::since(1.76)] - const EXPECTED: &str = "Expected: is &AStruct which has property `get_field ()`"; - + const EXPECTED: &str = "Expected: is & AStruct which has property `get_field()`"; verify_that!(result, err(displays_as(contains_substring(EXPECTED)))) } #[test] @@ -718,12 +674,7 @@ fn includes_struct_name_in_description_with_ref_property() -> Result<()> { let result = verify_that!(actual, matches_pattern!(&AStruct { get_field(): eq(&234) })); - #[rustversion::before(1.76)] - const EXPECTED: &str = "Expected: is & AStruct which has property `get_field ()`"; - - #[rustversion::since(1.76)] - const EXPECTED: &str = "Expected: is &AStruct which has property `get_field ()`"; - + const EXPECTED: &str = "Expected: is & AStruct which has property `get_field()`"; verify_that!(result, err(displays_as(contains_substring(EXPECTED)))) } @@ -743,12 +694,7 @@ fn includes_struct_name_in_description_with_property_after_field() -> Result<()> let result = verify_that!(actual, matches_pattern!(&AStruct { field: eq(123), get_field(): eq(234) })); - #[rustversion::before(1.76)] const EXPECTED: &str = "Expected: is & AStruct which has all the following properties"; - - #[rustversion::since(1.76)] - const EXPECTED: &str = "Expected: is &AStruct which has all the following properties"; - verify_that!(result, err(displays_as(contains_substring(EXPECTED)))) } @@ -768,12 +714,7 @@ fn includes_struct_name_in_description_with_ref_property_after_field() -> Result let result = verify_that!(actual, matches_pattern!(&AStruct { field: eq(123), get_field(): eq(&234) })); - #[rustversion::before(1.76)] const EXPECTED: &str = "Expected: is & AStruct which has all the following properties"; - - #[rustversion::since(1.76)] - const EXPECTED: &str = "Expected: is &AStruct which has all the following properties"; - verify_that!(result, err(displays_as(contains_substring(EXPECTED)))) } #[test] diff --git a/googletest_macro/src/lib.rs b/googletest_macro/src/lib.rs index 79e60ee2..1b81eb27 100644 --- a/googletest_macro/src/lib.rs +++ b/googletest_macro/src/lib.rs @@ -332,9 +332,22 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { .into() } +mod matches_pattern; + +/// This is an implementation detail of `googletest::matches_pattern!`. +/// +/// It's not intended to be used directly. +#[doc(hidden)] +#[proc_macro] +pub fn __googletest_macro_matches_pattern( + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + matches_pattern::matches_pattern_impl(input) +} + mod verify_pred; -/// This is an implementation detail of `verify_pred!`. +/// This is an implementation detail of `googletest::verify_pred!`. /// /// It's not intended to be used directly. #[doc(hidden)] diff --git a/googletest_macro/src/matches_pattern.rs b/googletest_macro/src/matches_pattern.rs new file mode 100644 index 00000000..4c6a41a2 --- /dev/null +++ b/googletest_macro/src/matches_pattern.rs @@ -0,0 +1,255 @@ +// Copyright 2024 Google LLC +// +// Licensed 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::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream, Parser as _}, + parse_macro_input, + punctuated::Punctuated, + Expr, ExprCall, ExprInfer, Pat, Token, +}; + +/// This is an implementation detail of `googletest::matches_pattern!`. It +/// assumes that a few symbols from `googletest::matchers` have been imported +/// and that `$crate` has been aliased to `googletest` (which might otherwise +/// have been imported as a different alias), both of which are done +/// by `googletest::matches_pattern!` before calling this proc macro. +pub(crate) fn matches_pattern_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let result = parse_macro_input!(input as ParsedMatchPattern).into_matcher_expr(); + quote! { #result }.into() +} + +/// Represents one of the root-level pseudo-patterns supported by +/// `match_pattern!`. +/// +/// Examples with `struct_name` set and `group` being `None`: +/// * `true` +/// * `Enum::Variant` +/// * `&Enum::Variant` +/// +/// Examples with `group` being non-`None`. +/// * `Struct { a: eq(1), b: ends_with("foo") }` +/// * `&Struct(ref eq(&1), ends_with("foo"))` +#[derive(Debug)] +struct ParsedMatchPattern { + struct_name: TokenStream, + group: Option<Group>, +} + +impl Parse for ParsedMatchPattern { + fn parse(input: ParseStream) -> syn::Result<Self> { + let mut struct_name: Vec<TokenTree> = vec![]; + let mut group: Option<Group> = None; + + // Use a TT-muncher (as opposed to parsing `Expr` or `Pat` or such) since: + // - The `struct_name` can be a struct/enum type or literal value (`true`, `1`, + // `&"test"`). + // - The braced part supports syntax that is not valid rust like `&Foo { a: ref + // eq(1) }`. + input.step(|cursor| { + let mut rest = *cursor; + + while let Some((tt, next)) = rest.token_tree() { + // If we reached a group (`{...}` or `(...)`), that should be the end of the + // pattern. Other groups (`[...]`) are not supported in struct names, but that's + // checked later. + if let TokenTree::Group(g) = tt { + group = Some(g); + return Ok(((), next)); + } + + // Everything before the group is the struct/enum name, possibly prefixed with + // `&`. + struct_name.push(tt); + rest = next; + } + + // If no group found, remove any trailing comma that might have been + // incorporated into the struct/enum name. + if matches!(struct_name.last(), Some(TokenTree::Punct(p)) if p.as_char() == ',') { + struct_name.pop(); + } + + Ok(((), rest)) + })?; + + input.parse::<Option<Token![,]>>()?; + let struct_name = struct_name.into_iter().collect(); + Ok(ParsedMatchPattern { struct_name, group }) + } +} + +impl ParsedMatchPattern { + fn into_matcher_expr(self) -> TokenStream { + let Self { struct_name, group } = self; + // `matcher_pattern` supports both its custom (not necessarily valid rust) + // syntax and also native match pattern (like `Struct { .. }` and + // `Struct(_)`). So we need to speculatively attempt the first and then + // fall back to the latter if the first fails. If both fail, we return + // the error from the first attempt. + let mut first_err = None; + + // `matcher_pattern` special syntax. + if let Some(ref g) = group { + let res = match g.delimiter() { + Delimiter::Parenthesis => parse_tuple_pattern_args(struct_name.clone(), g.stream()), + Delimiter::Brace => parse_braced_pattern_args(struct_name.clone(), g.stream()), + Delimiter::Bracket => compile_err(g.span(), "[...] syntax is not meaningful"), + Delimiter::None => compile_err(g.span(), "undelimited group not supported"), + }; + match res { + Ok(res) => return res, + Err(e) => first_err = Some(e), + } + } + + // Standard `match` pattern (prioritize `first_err` if both fail). + into_match_pattern_expr(quote! { #struct_name #group }) + .map_err(|e| first_err.unwrap_or(e)) + .unwrap_or_else(syn::Error::into_compile_error) + } +} + +/// Returns a pattern match expression as long as `stream` is a valid pattern. +/// Otherwise, returns failure. +fn into_match_pattern_expr(stream: TokenStream) -> syn::Result<TokenStream> { + // Only produce if stream successfully parses as a pattern. Otherwise, return + // failure so that we can instead return the error due to failing to parse + // the `matcher_pattern` custom syntax. + Pat::parse_multi.parse2(stream.clone())?; + Ok(quote! { + googletest::matchers::__internal_unstable_do_not_depend_on_these::pattern_only( + |v| matches!(v, #stream), + concat!("is ", stringify!(#stream)), + concat!("is not ", stringify!(#stream))) + }) +} + +//////////////////////////////////////////////////////////////////////////////// +// Parse tuple struct patterns + +/// Each part in a tuple matcher pattern that's between the commas for use with +/// `Punctuated`'s parser. +struct TupleFieldPattern { + ref_token: Option<Token![ref]>, + matcher: Expr, +} + +impl Parse for TupleFieldPattern { + fn parse(input: ParseStream) -> syn::Result<Self> { + let ref_token = input.parse()?; + let matcher = input.parse()?; + match matcher { + // `_` is an expr in const generics contexts, but is not supported in regular + // `Expr` use like in the `matches_pattern` custom syntax. So fail in that case. + // That should allow `into_match_expr` above to fall back to `into_match_pattern_expr` + // and still attempt to parse `matcher` as a pattern. + Expr::Infer(ExprInfer { underscore_token, .. }) => compile_err( + underscore_token.spans[0], + "unexpected `_` for `matches_pattern!` tuple field matcher", + ), + _ => Ok(TupleFieldPattern { ref_token, matcher }), + } + } +} + +/// Parses a tuple struct's fields into a match expression. +fn parse_tuple_pattern_args( + struct_name: TokenStream, + group_content: TokenStream, +) -> syn::Result<TokenStream> { + let parser = Punctuated::<TupleFieldPattern, Token![,]>::parse_terminated; + let fields = parser.parse2(group_content)?.into_iter().enumerate().map( + |(index, TupleFieldPattern { ref_token, matcher })| { + let index = syn::Index::from(index); + quote! { googletest::matchers::field!(#struct_name.#index, #ref_token #matcher) } + }, + ); + Ok(quote! { + googletest::matchers::__internal_unstable_do_not_depend_on_these::is( + stringify!(#struct_name), + all!( #(#fields),* ) + ) + }) +} + +//////////////////////////////////////////////////////////////////////////////// +// Parse braced structs patterns + +enum FieldOrMethod { + Field(Ident), + Method(ExprCall), +} + +impl Parse for FieldOrMethod { + /// Parses the field name or method call along with the `:` that follows it. + fn parse(input: ParseStream) -> syn::Result<Self> { + let value = if input.peek2(Token![:]) { + input.parse().map(FieldOrMethod::Field) + } else { + input.parse().map(FieldOrMethod::Method) + }?; + input.parse::<Token![:]>()?; + Ok(value) + } +} + +/// Either field or method call matcher. E.g.: +/// * `field: starts_with("something")` +/// * `property(arg1, arg2): starts_with("something") +struct FieldOrMethodPattern { + ref_token: Option<Token![ref]>, + field_or_method: FieldOrMethod, + matcher: Expr, +} + +impl Parse for FieldOrMethodPattern { + fn parse(input: ParseStream) -> syn::Result<Self> { + Ok(FieldOrMethodPattern { + field_or_method: input.parse()?, + ref_token: input.parse()?, + matcher: input.parse()?, + }) + } +} + +/// Parses a struct's fields or method calls into a match expression. +fn parse_braced_pattern_args( + struct_name: TokenStream, + group_content: TokenStream, +) -> syn::Result<TokenStream> { + let parser = Punctuated::<FieldOrMethodPattern, Token![,]>::parse_terminated; + let fields = parser.parse2(group_content)?.into_iter().map( + |FieldOrMethodPattern { ref_token, field_or_method, matcher }| match field_or_method { + FieldOrMethod::Field(ident) => { + quote! { field!(#struct_name . #ident, #ref_token #matcher) } + } + FieldOrMethod::Method(call) => { + quote! { property!(#struct_name . #call, #ref_token #matcher) } + } + }, + ); + + Ok(quote! { + googletest::matchers::__internal_unstable_do_not_depend_on_these::is( + stringify!(#struct_name), + all!( #(#fields),* ) + ) + }) +} + +fn compile_err<T>(span: Span, message: &str) -> syn::Result<T> { + Err(syn::Error::new(span, message)) +}