Skip to content

Commit 914bbfb

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: 704273542
1 parent 3d65dea commit 914bbfb

File tree

4 files changed

+181
-38
lines changed

4 files changed

+181
-38
lines changed

googletest/src/matchers/matches_pattern.rs

+78
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,66 @@ pub mod internal {
369369
}
370370
}
371371
}
372+
373+
/// A matcher that ensures that the passed-in function compiles with the
374+
/// benefit of inference from the value being tested and the attached
375+
/// matcher invokes as well.
376+
///
377+
/// It forwards all description responsibilities to the passed-in matcher.
378+
pub fn compile_assert_and_match<T, M>(
379+
must_compile_function: fn(&T),
380+
matcher: M,
381+
) -> CompileAssertAndMatch<T, M> {
382+
CompileAssertAndMatch { must_compile_function, matcher }
383+
}
384+
385+
#[derive(MatcherBase)]
386+
#[doc(hidden)]
387+
pub struct CompileAssertAndMatch<T, M> {
388+
#[allow(dead_code)]
389+
must_compile_function: fn(&T),
390+
matcher: M,
391+
}
392+
393+
impl<'a, T: Debug, M> Matcher<&'a T> for CompileAssertAndMatch<T, M>
394+
where
395+
M: Matcher<&'a T>,
396+
{
397+
fn matches(&self, actual: &'a T) -> crate::matcher::MatcherResult {
398+
self.matcher.matches(actual)
399+
}
400+
401+
fn describe(
402+
&self,
403+
matcher_result: crate::matcher::MatcherResult,
404+
) -> crate::description::Description {
405+
self.matcher.describe(matcher_result)
406+
}
407+
408+
fn explain_match(&self, actual: &'a T) -> crate::description::Description {
409+
self.matcher.explain_match(actual)
410+
}
411+
}
412+
413+
impl<T: Debug + Copy, M> Matcher<T> for CompileAssertAndMatch<T, M>
414+
where
415+
M: Matcher<T>,
416+
{
417+
fn matches(&self, actual: T) -> crate::matcher::MatcherResult {
418+
self.matcher.matches(actual)
419+
}
420+
421+
fn describe(
422+
&self,
423+
matcher_result: crate::matcher::MatcherResult,
424+
) -> crate::description::Description {
425+
self.matcher.describe(matcher_result)
426+
}
427+
428+
fn explain_match(&self, actual: T) -> crate::description::Description {
429+
self.matcher.explain_match(actual)
430+
}
431+
}
372432
}
373433

374434
mod compile_fail_tests {
@@ -393,6 +453,15 @@ mod compile_fail_tests {
393453
/// ```
394454
fn _dot_dot_supported_only_at_end_of_struct_pattern() {}
395455

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+
396465
/// ```compile_fail
397466
/// use ::googletest::prelude::*;
398467
/// #[derive(Debug)]
@@ -412,4 +481,13 @@ mod compile_fail_tests {
412481
/// verify_that!(actual, matches_pattern!(Foo::Bar { a: eq(&1) }));
413482
/// ```
414483
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() {}
415493
}

googletest/src/matchers/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,9 @@ pub mod __internal_unstable_do_not_depend_on_these {
113113
pub use super::elements_are_matcher::internal::ElementsAre;
114114
pub use super::field_matcher::internal::field_matcher;
115115
pub use super::is_matcher::is;
116-
pub use super::matches_pattern::internal::{__googletest_macro_matches_pattern, pattern_only};
116+
pub use super::matches_pattern::internal::{
117+
__googletest_macro_matches_pattern, compile_assert_and_match, pattern_only,
118+
};
117119
pub use super::pointwise_matcher::internal::PointwiseMatcher;
118120
pub use super::property_matcher::internal::{property_matcher, property_ref_matcher};
119121
pub use super::result_of_matcher::internal::{result_of, result_of_ref};

googletest/tests/matches_pattern_test.rs

+45
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,22 @@ fn matches_struct_containing_non_copy_field_binding_mode() -> Result<()> {
150150
verify_that!(actual, matches_pattern!(AStruct { a_string: eq("123") }))
151151
}
152152

153+
#[test]
154+
fn matches_generic_struct_exhaustively() -> Result<()> {
155+
#[allow(dead_code)]
156+
#[derive(Copy, Clone, Debug)]
157+
struct AStruct<T> {
158+
a: T,
159+
b: u32,
160+
}
161+
let actual = AStruct { a: 1, b: 3 };
162+
163+
// Need to use `&actual` to match the pattern since otherwise the generic
164+
// argument can't be correctly deduced.
165+
verify_that!(&actual, matches_pattern!(&AStruct { a: _, b: _ }))?;
166+
verify_that!(actual, matches_pattern!(AStruct { a: _, b: _ }))
167+
}
168+
153169
#[test]
154170
fn matches_struct_with_interleaved_underscore() -> Result<()> {
155171
#[derive(Debug)]
@@ -481,6 +497,35 @@ fn matches_tuple_struct_with_interleaved_underscore() -> Result<()> {
481497
verify_that!(actual, matches_pattern!(AStruct(eq(&1), _, eq(&3))))
482498
}
483499

500+
#[test]
501+
fn matches_tuple_struct_non_exhaustive() -> Result<()> {
502+
#[allow(dead_code)]
503+
#[derive(Copy, Clone, Debug)]
504+
struct AStruct<T>(T, u32);
505+
let actual = AStruct(1, 3);
506+
507+
// Need to use `&actual` to match the pattern since otherwise the generic
508+
// argument can't be correctly deduced.
509+
verify_that!(&actual, matches_pattern!(&AStruct(_, ..)))?;
510+
verify_that!(actual, matches_pattern!(AStruct(_, ..)))
511+
}
512+
513+
#[test]
514+
fn matches_generic_tuple_struct_exhaustively() -> Result<()> {
515+
#[allow(dead_code)]
516+
#[derive(Copy, Clone, Debug)]
517+
struct AStruct<T> {
518+
a: T,
519+
b: u32,
520+
}
521+
let actual = AStruct { a: 1, b: 3 };
522+
523+
// Need to use `&actual` to match the pattern since otherwise the generic
524+
// argument can't be correctly deduced.
525+
verify_that!(&actual, matches_pattern!(&AStruct { a: _, b: _ }))?;
526+
verify_that!(actual, matches_pattern!(AStruct { a: _, b: _ }))
527+
}
528+
484529
#[test]
485530
fn matches_enum_without_field() -> Result<()> {
486531
#[derive(Debug)]

googletest_macro/src/matches_pattern.rs

+55-37
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 `..` and has.
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
////////////////////////////////////////////////////////////////////////////////
@@ -257,6 +279,13 @@ fn parse_braced_pattern_args(
257279
})
258280
.collect();
259281

282+
let matcher = quote! {
283+
googletest::matchers::__internal_unstable_do_not_depend_on_these::is(
284+
stringify!(#struct_name),
285+
all!(#(#field_patterns),* )
286+
)
287+
};
288+
260289
// Do an exhaustiveness check only if the pattern doesn't end with `..` and has
261290
// any fields in the pattern. This latter part is required because
262291
// `matches_pattern!` also uses the brace notation for tuple structs when
@@ -268,36 +297,25 @@ fn parse_braced_pattern_args(
268297
// matches_pattern!(foo, Struct { bar(): eq(1) })
269298
// ```
270299
// and we can't emit an exhaustiveness check based on the `matches_pattern!`.
271-
let maybe_assert_exhaustive = if non_exhaustive || field_names.is_empty() {
272-
None
300+
if non_exhaustive || field_names.is_empty() {
301+
Ok(matcher)
273302
} else {
274-
// Note that `struct_name` might be an enum variant (`Enum::Foo`), which is not
275-
// a valid type. So we need to infer the type, produce an instance, and match on
276-
// it. Fortunately, `[_; 0]` can be trivially initialized to `[]` and can
277-
// produce an instance by indexing into it without failing compilation.
278-
Some(quote! {
279-
fn __matches_pattern_ensure_exhastive_match(i: usize) {
280-
let val: [_; 0] = [];
281-
let _ = match val[i] {
282-
#struct_name { #(#field_names: _),* } => (),
283-
// The pattern below is unreachable if the type is a struct (as opposed to an
284-
// enum). Since the macro can't know which it is, we always include it and just
285-
// tell the compiler not to complain.
286-
#[allow(unreachable_patterns)]
287-
_ => (),
288-
};
289-
}
303+
Ok(quote! {
304+
googletest::matchers::__internal_unstable_do_not_depend_on_these::compile_assert_and_match(
305+
|actual| {
306+
// Exhaustively check that all field names are specified.
307+
match actual {
308+
#struct_name { #(#field_names: _),* } => {},
309+
// The pattern below is unreachable if the type is a struct (as opposed to
310+
// an enum). Since the macro can't know which it is, we always include it
311+
// and just tell the compiler not to complain.
312+
#[allow(unreachable_patterns)]
313+
_ => {},
314+
}
315+
},
316+
#matcher)
290317
})
291-
};
292-
293-
Ok(quote! {
294-
googletest::matchers::__internal_unstable_do_not_depend_on_these::is(
295-
stringify!(#struct_name), {
296-
#maybe_assert_exhaustive
297-
all!( #(#field_patterns),* )
298-
}
299-
)
300-
})
318+
}
301319
}
302320

303321
////////////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)