Skip to content

Commit a17b655

Browse files
marcianxcopybara-github
authored andcommitted
Similar to native match pattern for tuple structs, make matches_pattern enforce exhaustive field checks for tuple structs by default, to be suppressed by explicit .. at the end of the pattern.
This change is not backward-compatible since it enforces field exhaustiveness by default in the absence of a trailing `..` in the pattern. Fixes #447 PiperOrigin-RevId: 705914628
1 parent 99bcfaa commit a17b655

File tree

3 files changed

+71
-9
lines changed

3 files changed

+71
-9
lines changed

googletest/src/matchers/matches_pattern.rs

+18
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,15 @@ mod compile_fail_tests {
453453
/// ```
454454
fn _dot_dot_supported_only_at_end_of_struct_pattern() {}
455455

456+
/// ```compile_fail
457+
/// use ::googletest::prelude::*;
458+
/// #[derive(Debug)]
459+
/// struct Foo(u32, u32);
460+
/// let actual = Foo(1, 2);
461+
/// verify_that!(actual, matches_pattern!(Foo(eq(&1), .., )));
462+
/// ```
463+
fn _dot_dot_supported_only_at_end_of_tuple_struct_pattern() {}
464+
456465
/// ```compile_fail
457466
/// use ::googletest::prelude::*;
458467
/// #[derive(Debug)]
@@ -472,4 +481,13 @@ mod compile_fail_tests {
472481
/// verify_that!(actual, matches_pattern!(Foo::Bar { a: eq(&1) }));
473482
/// ```
474483
fn _unexhaustive_enum_struct_field_check_requires_dot_dot() {}
484+
485+
/// ```compile_fail
486+
/// use ::googletest::prelude::*;
487+
/// #[derive(Debug)]
488+
/// struct Foo(u32, u32, u32);
489+
/// let actual = Foo(1, 2, 3);
490+
/// verify_that!(actual, matches_pattern!(Foo(eq(&1), eq(&2) )));
491+
/// ```
492+
fn _unexhaustive_tuple_struct_field_check_requires_dot_dot() {}
475493
}

googletest/tests/matches_pattern_test.rs

+22
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,28 @@ fn matches_tuple_struct_with_interleaved_underscore() -> Result<()> {
495495
verify_that!(actual, matches_pattern!(AStruct(eq(&1), _, eq(&3))))
496496
}
497497

498+
#[test]
499+
fn matches_tuple_struct_non_exhaustive() -> Result<()> {
500+
#[allow(dead_code)]
501+
#[derive(Debug)]
502+
struct AStruct(i32, u32);
503+
let actual = AStruct(1, 3);
504+
505+
verify_that!(actual, matches_pattern!(&AStruct(_, ..)))?;
506+
verify_that!(actual, matches_pattern!(AStruct(_, ..)))
507+
}
508+
509+
#[test]
510+
fn matches_generic_tuple_struct_exhaustively() -> Result<()> {
511+
#[allow(dead_code)]
512+
#[derive(Debug)]
513+
struct AStruct<T>(T, u32);
514+
let actual = AStruct(1, 3);
515+
516+
verify_that!(actual, matches_pattern!(&AStruct(_, _)))?;
517+
verify_that!(actual, matches_pattern!(AStruct(_, _)))
518+
}
519+
498520
#[test]
499521
fn matches_enum_without_field() -> Result<()> {
500522
#[derive(Debug)]

googletest_macro/src/matches_pattern.rs

+31-9
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
1616
use quote::quote;
1717
use syn::{
1818
parse::{Parse, ParseStream, Parser as _},
19-
parse_macro_input,
20-
punctuated::Punctuated,
21-
Expr, ExprCall, Pat, Token,
19+
parse_macro_input, Expr, ExprCall, Pat, Token,
2220
};
2321

2422
/// This is an implementation detail of `googletest::matches_pattern!`. It
@@ -164,22 +162,46 @@ fn parse_tuple_pattern_args(
164162
struct_name: TokenStream,
165163
group_content: TokenStream,
166164
) -> syn::Result<TokenStream> {
167-
let parser = Punctuated::<MaybeTupleFieldPattern, Token![,]>::parse_terminated;
168-
let fields = parser
169-
.parse2(group_content)?
165+
let (patterns, non_exhaustive) =
166+
parse_list_terminated_pattern::<MaybeTupleFieldPattern>.parse2(group_content)?;
167+
let field_count = patterns.len();
168+
let field_patterns = patterns
170169
.into_iter()
171170
.enumerate()
172171
.filter_map(|(index, maybe_pattern)| maybe_pattern.0.map(|pattern| (index, pattern)))
173172
.map(|(index, TupleFieldPattern { ref_token, matcher })| {
174173
let index = syn::Index::from(index);
175174
quote! { googletest::matchers::field!(#struct_name.#index, #ref_token #matcher) }
176175
});
177-
Ok(quote! {
176+
177+
let matcher = quote! {
178178
googletest::matchers::__internal_unstable_do_not_depend_on_these::is(
179179
stringify!(#struct_name),
180-
all!( #(#fields),* )
180+
all!( #(#field_patterns),* )
181181
)
182-
})
182+
};
183+
184+
// Do an exhaustiveness check only if the pattern doesn't end with `..`.
185+
if non_exhaustive {
186+
Ok(matcher)
187+
} else {
188+
let empty_fields = std::iter::repeat(quote! { _ }).take(field_count);
189+
Ok(quote! {
190+
googletest::matchers::__internal_unstable_do_not_depend_on_these::compile_assert_and_match(
191+
|actual| {
192+
// Exhaustively check that all field names are specified.
193+
match actual {
194+
#struct_name ( #(#empty_fields),* ) => (),
195+
// The pattern below is unreachable if the type is a struct (as opposed to
196+
// an enum). Since the macro can't know which it is, we always include it
197+
// and just tell the compiler not to complain.
198+
#[allow(unreachable_patterns)]
199+
_ => {},
200+
}
201+
},
202+
#matcher)
203+
})
204+
}
183205
}
184206

185207
////////////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)