Skip to content

Commit b1dee66

Browse files
Googlercopybara-github
Googler
authored andcommitted
Fix the parsing of matches_pattern! to properly support enums like Option. Previously, the pattern Some(_) would be parsed as None. Now the Some variant with wildcard will be parsed correctly.
Toward #626 PiperOrigin-RevId: 748656817
1 parent 514ac48 commit b1dee66

File tree

3 files changed

+69
-7
lines changed

3 files changed

+69
-7
lines changed

googletest/src/matchers/matches_pattern.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,17 @@
268268
/// # should_fail().unwrap_err();
269269
/// ```
270270
///
271+
/// When matching against enums like `Option`, the `matches_pattern!` macro now
272+
/// correctly handles patterns like `Some(_)` and `Some(value)`.
273+
/// For example:
274+
///
275+
/// ```
276+
/// # use googletest::prelude::*;
277+
/// let result: Option<i8> = None;
278+
/// verify_that!(result, matches_pattern!(Some(_))).is_err(); // Fails
279+
/// verify_that!(result, matches_pattern!(Some(123))).is_err(); // Fails
280+
/// ```
281+
271282
/// This macro does not support plain (non-struct) tuples. But it should not be
272283
/// necessary as tuple of matchers are matchers of tuple. In other words, if
273284
/// `MatcherU: Matcher<U>` and `MatcherT: Matcher<T>`, then `(MatcherU,

googletest/tests/matches_pattern_test.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ fn matches_tuple_struct_with_two_fields_and_trailing_comma() -> Result<()> {
494494

495495
#[test]
496496
fn matches_tuple_struct_with_interleaved_underscore() -> Result<()> {
497-
#[derive(Debug)]
497+
#[derive(Debug, Copy, Clone)]
498498
struct NonCopy;
499499
#[derive(Debug)]
500500
struct AStruct(u32, NonCopy, u32);
@@ -1870,3 +1870,47 @@ fn matches_struct_auto_eq() -> Result<()> {
18701870
matches_pattern!(&AStruct { int: 123, string: ref "123", option: Some(123) })
18711871
)
18721872
}
1873+
1874+
#[test]
1875+
fn matches_enum_none_with_wildcard() -> Result<()> {
1876+
let result: Option<i8> = None;
1877+
let result = verify_that!(result, matches_pattern!(Some(_)));
1878+
1879+
const EXPECTED: &str = indoc!(
1880+
"
1881+
Expected: is Some which has field `0`, which is anything
1882+
Actual: None,
1883+
which has the wrong enum variant `None`
1884+
"
1885+
);
1886+
1887+
verify_that!(result, err(displays_as(contains_substring(EXPECTED))))
1888+
}
1889+
1890+
#[test]
1891+
fn matches_enum_none_with_value() -> Result<()> {
1892+
let result: Option<i8> = None;
1893+
let result = verify_that!(result, matches_pattern!(Some(123)));
1894+
1895+
const EXPECTED: &str = indoc!(
1896+
"
1897+
Expected: is Some which has field `0`, which is equal to 123
1898+
Actual: None,
1899+
which has the wrong enum variant `None`
1900+
"
1901+
);
1902+
1903+
verify_that!(result, err(displays_as(contains_substring(EXPECTED))))
1904+
}
1905+
1906+
#[test]
1907+
fn matches_enum_none_with_none() -> Result<()> {
1908+
let result: Option<i8> = None;
1909+
verify_that!(result, matches_pattern!(None))
1910+
}
1911+
1912+
#[test]
1913+
fn matches_value_with_wildcard() -> Result<()> {
1914+
let result: Option<i8> = Some(123);
1915+
verify_that!(result, matches_pattern!(Some(_)))
1916+
}

googletest_macro/src/matches_pattern.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
1616
use quote::{quote, ToTokens};
1717
use syn::{
1818
parse::{Parse, ParseStream, Parser as _},
19-
parse_macro_input,
19+
parse_macro_input, parse_quote,
2020
token::DotDot,
2121
Expr, ExprCall, Pat, Token,
2222
};
@@ -131,7 +131,9 @@ fn into_match_pattern_expr(stream: TokenStream) -> syn::Result<TokenStream> {
131131
Pat::parse_multi.parse2(stream.clone())?;
132132
Ok(quote! {
133133
googletest::matchers::__internal_unstable_do_not_depend_on_these::pattern_only(
134-
|v| matches!(v, #stream),
134+
|v| {
135+
matches!(v, #stream)
136+
},
135137
concat!("is ", stringify!(#stream)),
136138
concat!("is not ", stringify!(#stream)))
137139
})
@@ -140,8 +142,7 @@ fn into_match_pattern_expr(stream: TokenStream) -> syn::Result<TokenStream> {
140142
////////////////////////////////////////////////////////////////////////////////
141143
// Parse tuple struct patterns
142144

143-
/// Each part in a tuple matcher pattern that's between the commas. When `None`,
144-
/// it represents `_` which matches anything.
145+
/// Each part in a tuple matcher pattern that's between the commas.
145146
struct MaybeTupleFieldPattern(Option<TupleFieldPattern>);
146147

147148
struct TupleFieldPattern {
@@ -152,7 +153,7 @@ struct TupleFieldPattern {
152153
impl Parse for MaybeTupleFieldPattern {
153154
fn parse(input: ParseStream) -> syn::Result<Self> {
154155
let pattern = match input.parse::<Option<Token![_]>>()? {
155-
Some(_) => None,
156+
Some(_) => Some(TupleFieldPattern { ref_token: None, matcher: parse_quote!(_) }),
156157
None => Some(TupleFieldPattern { ref_token: input.parse()?, matcher: input.parse()? }),
157158
};
158159
Ok(MaybeTupleFieldPattern(pattern))
@@ -173,7 +174,13 @@ fn parse_tuple_pattern_args(
173174
.filter_map(|(index, maybe_pattern)| maybe_pattern.0.map(|pattern| (index, pattern)))
174175
.map(|(index, TupleFieldPattern { ref_token, matcher })| {
175176
let index = syn::Index::from(index);
176-
quote! { googletest::matchers::field!(#struct_name.#index, #ref_token #matcher) }
177+
// `_` matches anything, so we can use anything as the matcher.
178+
let matcher_to_use = if matches!(syn::parse2::<Expr>(quote! {#matcher}), Ok(parsed_matcher) if parsed_matcher == parse_quote!(_)) {
179+
quote! { googletest::matchers::anything() }
180+
} else {
181+
quote! { #matcher }
182+
};
183+
quote! { googletest::matchers::field!(#struct_name.#index, #ref_token #matcher_to_use) }
177184
});
178185

179186
let matcher = quote! {

0 commit comments

Comments
 (0)