Skip to content

Commit cada033

Browse files
marcianxcopybara-github
authored andcommitted
Make matches_pattern for braced structs support exhaustiveness with generic structs.
Previously, for field exhaustiveness, we emitted an isolated `Foo { a: _, b: _ }` divorced from the `actual` value, but if `Foo`'s `a` field is generic, we're not able to infer it at all. So instead, put the `match` within a `predicate` matcher and match against the `actual` value to be able to infer the field. Toward #447 PiperOrigin-RevId: 704716108
1 parent 3d65dea commit cada033

File tree

4 files changed

+101
-29
lines changed

4 files changed

+101
-29
lines changed

googletest/src/matchers/matches_pattern.rs

+60
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. The passed-in
375+
/// matcher is what is actually invoked for matching.
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 {

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

+14
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,20 @@ 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(Debug)]
157+
struct AStruct<T> {
158+
a: T,
159+
b: u32,
160+
}
161+
let actual = AStruct { a: 1, b: 3 };
162+
163+
verify_that!(actual, matches_pattern!(&AStruct { a: _, b: _ }))?;
164+
verify_that!(actual, matches_pattern!(AStruct { a: _, b: _ }))
165+
}
166+
153167
#[test]
154168
fn matches_struct_with_interleaved_underscore() -> Result<()> {
155169
#[derive(Debug)]

googletest_macro/src/matches_pattern.rs

+24-28
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,13 @@ fn parse_braced_pattern_args(
257257
})
258258
.collect();
259259

260+
let matcher = quote! {
261+
googletest::matchers::__internal_unstable_do_not_depend_on_these::is(
262+
stringify!(#struct_name),
263+
all!(#(#field_patterns),* )
264+
)
265+
};
266+
260267
// Do an exhaustiveness check only if the pattern doesn't end with `..` and has
261268
// any fields in the pattern. This latter part is required because
262269
// `matches_pattern!` also uses the brace notation for tuple structs when
@@ -268,36 +275,25 @@ fn parse_braced_pattern_args(
268275
// matches_pattern!(foo, Struct { bar(): eq(1) })
269276
// ```
270277
// 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
278+
if non_exhaustive || field_names.is_empty() {
279+
Ok(matcher)
273280
} 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-
}
281+
Ok(quote! {
282+
googletest::matchers::__internal_unstable_do_not_depend_on_these::compile_assert_and_match(
283+
|actual| {
284+
// Exhaustively check that all field names are specified.
285+
match actual {
286+
#struct_name { #(#field_names: _),* } => {},
287+
// The pattern below is unreachable if the type is a struct (as opposed to
288+
// an enum). Since the macro can't know which it is, we always include it
289+
// and just tell the compiler not to complain.
290+
#[allow(unreachable_patterns)]
291+
_ => {},
292+
}
293+
},
294+
#matcher)
290295
})
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-
})
296+
}
301297
}
302298

303299
////////////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)