Skip to content

Commit dcb345e

Browse files
Googlercopybara-github
Googler
authored andcommitted
Fix the parsing logic of matches_pattern! to support _ at the top level in top-level structs.
In the current parsing logic, the `_` field in pattern `Some(_)` is parsed as `None`, and as a result no FieldMatcher is generated, and the internal `is()` matcher only evaluates the innerMatcher(the field_matcher). We add `pattern_only()` matcher for the case when field_matcher is empty and skip the `is()` wrapping. Toward #626 PiperOrigin-RevId: 748656817
1 parent 90f3144 commit dcb345e

File tree

3 files changed

+169
-30
lines changed

3 files changed

+169
-30
lines changed

googletest/src/matchers/matches_pattern.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,8 @@
246246
/// # .unwrap();
247247
/// ```
248248
///
249-
/// One can also match enum values:
249+
/// The macro also allows matching on specific enum values and supports wildcard
250+
/// patterns like `MyEnum::Case(_)`.
250251
///
251252
/// ```
252253
/// # use googletest::prelude::*;
@@ -260,11 +261,16 @@
260261
/// verify_that!(MyEnum::A(123), matches_pattern!(&MyEnum::A(eq(123))))?; // Passes
261262
/// # Ok(())
262263
/// # }
264+
/// # fn should_pass_with_wildcard() -> Result<()> {
265+
/// verify_that!(MyEnum::A(123), matches_pattern!(MyEnum::A(_)))?; // Passes
266+
/// # Ok(())
267+
/// # }
263268
/// # fn should_fail() -> Result<()> {
264269
/// verify_that!(MyEnum::B, matches_pattern!(&MyEnum::A(eq(123))))?; // Fails - wrong enum variant
265270
/// # Ok(())
266271
/// # }
267272
/// # should_pass().unwrap();
273+
/// # should_pass_with_wildcard().unwrap();
268274
/// # should_fail().unwrap_err();
269275
/// ```
270276
///

googletest/tests/matches_pattern_test.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,112 @@ fn generates_correct_failure_output_when_enum_variant_without_field_is_matched()
639639
const EXPECTED: &str = "is & AnEnum :: A";
640640
verify_that!(result, err(displays_as(contains_substring(EXPECTED))))
641641
}
642+
643+
#[test]
644+
fn has_failure_when_wrong_enum_variant_is_matched_non_exhaustively() -> Result<()> {
645+
#[allow(dead_code)]
646+
#[derive(Debug)]
647+
enum AnEnum {
648+
Variant1(i8),
649+
Variant2,
650+
}
651+
let actual: AnEnum = AnEnum::Variant2;
652+
653+
let result = verify_that!(actual, matches_pattern!(&AnEnum::Variant1(..)));
654+
655+
const EXPECTED: &str = indoc!(
656+
"
657+
Expected: is & AnEnum :: Variant1(..)
658+
Actual: Variant2,
659+
which is not & AnEnum :: Variant1(..)
660+
"
661+
);
662+
verify_that!(result, err(displays_as(contains_substring(EXPECTED))))
663+
}
664+
665+
#[test]
666+
fn has_failure_when_wrong_enum_variant_is_matched_with_underscore() -> Result<()> {
667+
#[allow(dead_code)]
668+
#[derive(Debug)]
669+
enum AnEnum {
670+
Variant1(i8),
671+
Variant2,
672+
}
673+
let actual: AnEnum = AnEnum::Variant2;
674+
675+
let result = verify_that!(actual, matches_pattern!(&AnEnum::Variant1(_)));
676+
677+
const EXPECTED: &str = indoc!(
678+
"
679+
Expected: is & AnEnum :: Variant1(_)
680+
Actual: Variant2,
681+
which is not & AnEnum :: Variant1(_)
682+
"
683+
);
684+
verify_that!(result, err(displays_as(contains_substring(EXPECTED))))
685+
}
686+
687+
#[test]
688+
fn has_failure_when_wrong_enum_variant_is_matched_with_value() -> Result<()> {
689+
#[allow(dead_code)]
690+
#[derive(Debug)]
691+
enum AnEnum {
692+
Variant1(i8),
693+
Variant2,
694+
}
695+
let actual: AnEnum = AnEnum::Variant2;
696+
697+
let result = verify_that!(actual, matches_pattern!(&AnEnum::Variant1(123)));
698+
699+
const EXPECTED: &str = indoc!(
700+
"
701+
Expected: is & AnEnum :: Variant1 which has field `0`, which is equal to 123
702+
Actual: Variant2,
703+
which has the wrong enum variant `Variant2`
704+
"
705+
);
706+
verify_that!(result, err(displays_as(contains_substring(EXPECTED))))
707+
}
708+
709+
#[test]
710+
fn matches_enum_struct_field_with_mutliple_variants() -> Result<()> {
711+
#[allow(dead_code)]
712+
#[derive(Debug)]
713+
enum AnEnum {
714+
Variant1(i8),
715+
Variant2,
716+
}
717+
let actual: AnEnum = AnEnum::Variant2;
718+
719+
verify_that!(actual, matches_pattern!(&AnEnum::Variant2))
720+
}
721+
722+
#[test]
723+
fn matches_enum_struct_field_with_multiple_variants_non_exhaustive() -> Result<()> {
724+
#[allow(dead_code)]
725+
#[derive(Debug)]
726+
enum AnEnum {
727+
Variant1(i8),
728+
Variant2,
729+
}
730+
let actual: AnEnum = AnEnum::Variant1(123);
731+
732+
verify_that!(actual, matches_pattern!(&AnEnum::Variant1(..)))
733+
}
734+
735+
#[test]
736+
fn matches_enum_struct_field_with_multiple_variants_with_wildcard() -> Result<()> {
737+
#[allow(dead_code)]
738+
#[derive(Debug)]
739+
enum AnEnum {
740+
Variant1(i8),
741+
Variant2,
742+
}
743+
let actual: AnEnum = AnEnum::Variant1(123);
744+
745+
verify_that!(actual, matches_pattern!(&AnEnum::Variant1(_)))
746+
}
747+
642748
#[test]
643749
fn matches_enum_with_field() -> Result<()> {
644750
#[derive(Debug)]

googletest_macro/src/matches_pattern.rs

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -167,43 +167,70 @@ fn parse_tuple_pattern_args(
167167
let (patterns, dot_dot) =
168168
parse_list_terminated_pattern::<MaybeTupleFieldPattern>.parse2(group_content)?;
169169
let field_count = patterns.len();
170-
let field_patterns = patterns
170+
let field_patterns: Vec<_> = patterns
171171
.into_iter()
172172
.enumerate()
173173
.filter_map(|(index, maybe_pattern)| maybe_pattern.0.map(|pattern| (index, pattern)))
174174
.map(|(index, TupleFieldPattern { ref_token, matcher })| {
175175
let index = syn::Index::from(index);
176176
quote! { googletest::matchers::field!(#struct_name.#index, #ref_token #matcher) }
177-
});
177+
})
178+
.collect();
178179

179-
let matcher = quote! {
180-
googletest::matchers::__internal_unstable_do_not_depend_on_these::is(
181-
stringify!(#struct_name),
182-
all!( #(#field_patterns),* )
183-
)
184-
};
180+
if field_patterns.is_empty() {
181+
// It is possible that the logic above didn't generate any field matchers
182+
// (e.g., for patterns like `Some(_)`).
183+
// In this case we verify that the enum has the correct case, but don't
184+
// verify the payload.
185+
#[allow(clippy::manual_repeat_n)]
186+
// `repeat_n` is not available on the Rust MSRV that we support in OSS
187+
let ignored_fields = std::iter::repeat(quote! { _ })
188+
.take(field_count)
189+
.chain(dot_dot.map(ToTokens::into_token_stream));
190+
let full_pattern = quote! { #struct_name ( #(#ignored_fields),* ) };
185191

186-
// Do a match to ensure:
187-
// - Fields are exhaustively listed unless the pattern ended with `..`.
188-
// - `UNDEFINED_SYMBOL(..)` fails to compile.
189-
let empty_fields = std::iter::repeat(quote! { _ })
190-
.take(field_count)
191-
.chain(dot_dot.map(ToTokens::into_token_stream));
192-
Ok(quote! {
193-
googletest::matchers::__internal_unstable_do_not_depend_on_these::compile_assert_and_match(
194-
|actual| {
195-
// Exhaustively check that all field names are specified.
196-
match actual {
197-
#struct_name ( #(#empty_fields),* ) => (),
198-
// The pattern below is unreachable if the type is a struct (as opposed to
199-
// an enum). Since the macro can't know which it is, we always include it
200-
// and just tell the compiler not to complain.
201-
#[allow(unreachable_patterns)]
202-
_ => {},
203-
}
204-
},
205-
#matcher)
206-
})
192+
Ok(quote! {
193+
googletest::matchers::__internal_unstable_do_not_depend_on_these::pattern_only(
194+
|actual| { matches!(actual, #full_pattern) },
195+
concat!("is ", stringify!(#full_pattern)),
196+
concat!("is not ", stringify!(#full_pattern))
197+
)
198+
})
199+
} else {
200+
// We have created at least one field matcher. Each field matcher will verify
201+
// not only its part of the payload, but also that the enum has the
202+
// correct case.
203+
let matcher = quote! {
204+
googletest::matchers::__internal_unstable_do_not_depend_on_these::is(
205+
stringify!(#struct_name),
206+
all!( #(#field_patterns),* )
207+
)
208+
};
209+
210+
// Do a match to ensure:
211+
// - Fields are exhaustively listed unless the pattern ended with `..`.
212+
// - `UNDEFINED_SYMBOL(..)` fails to compile.
213+
#[allow(clippy::manual_repeat_n)]
214+
// `repeat_n` is not available on the Rust MSRV that we support in OSS
215+
let empty_fields = std::iter::repeat(quote! { _ })
216+
.take(field_count)
217+
.chain(dot_dot.map(ToTokens::into_token_stream));
218+
Ok(quote! {
219+
googletest::matchers::__internal_unstable_do_not_depend_on_these::compile_assert_and_match(
220+
|actual| {
221+
// Exhaustively check that all field names are specified.
222+
match actual {
223+
#struct_name ( #(#empty_fields),* ) => (),
224+
// The pattern below is unreachable if the type is a struct (as opposed to
225+
// an enum). Since the macro can't know which it is, we always include it
226+
// and just tell the compiler not to complain.
227+
#[allow(unreachable_patterns)]
228+
_ => {},
229+
}
230+
},
231+
#matcher)
232+
})
233+
}
207234
}
208235

209236
////////////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)