diff --git a/googletest/src/matchers/matches_pattern.rs b/googletest/src/matchers/matches_pattern.rs index 8ea0d12a..4d3951da 100644 --- a/googletest/src/matchers/matches_pattern.rs +++ b/googletest/src/matchers/matches_pattern.rs @@ -453,6 +453,15 @@ mod compile_fail_tests { /// ``` fn _dot_dot_supported_only_at_end_of_struct_pattern() {} + /// ```compile_fail + /// use ::googletest::prelude::*; + /// #[derive(Debug)] + /// struct Foo(u32, u32); + /// let actual = Foo(1, 2); + /// verify_that!(actual, matches_pattern!(Foo(eq(&1), .., ))); + /// ``` + fn _dot_dot_supported_only_at_end_of_tuple_struct_pattern() {} + /// ```compile_fail /// use ::googletest::prelude::*; /// #[derive(Debug)] @@ -472,4 +481,13 @@ mod compile_fail_tests { /// verify_that!(actual, matches_pattern!(Foo::Bar { a: eq(&1) })); /// ``` fn _unexhaustive_enum_struct_field_check_requires_dot_dot() {} + + /// ```compile_fail + /// use ::googletest::prelude::*; + /// #[derive(Debug)] + /// struct Foo(u32, u32, u32); + /// let actual = Foo(1, 2, 3); + /// verify_that!(actual, matches_pattern!(Foo(eq(&1), eq(&2) ))); + /// ``` + fn _unexhaustive_tuple_struct_field_check_requires_dot_dot() {} } diff --git a/googletest/tests/matches_pattern_test.rs b/googletest/tests/matches_pattern_test.rs index 75e15f6d..ce3d3a46 100644 --- a/googletest/tests/matches_pattern_test.rs +++ b/googletest/tests/matches_pattern_test.rs @@ -495,6 +495,28 @@ fn matches_tuple_struct_with_interleaved_underscore() -> Result<()> { verify_that!(actual, matches_pattern!(AStruct(eq(&1), _, eq(&3)))) } +#[test] +fn matches_tuple_struct_non_exhaustive() -> Result<()> { + #[allow(dead_code)] + #[derive(Debug)] + struct AStruct(i32, u32); + let actual = AStruct(1, 3); + + verify_that!(actual, matches_pattern!(&AStruct(_, ..)))?; + verify_that!(actual, matches_pattern!(AStruct(_, ..))) +} + +#[test] +fn matches_generic_tuple_struct_exhaustively() -> Result<()> { + #[allow(dead_code)] + #[derive(Debug)] + struct AStruct(T, u32); + let actual = AStruct(1, 3); + + verify_that!(actual, matches_pattern!(&AStruct(_, _)))?; + verify_that!(actual, matches_pattern!(AStruct(_, _))) +} + #[test] fn matches_enum_without_field() -> Result<()> { #[derive(Debug)] diff --git a/googletest_macro/src/matches_pattern.rs b/googletest_macro/src/matches_pattern.rs index 678b308a..93da2589 100644 --- a/googletest_macro/src/matches_pattern.rs +++ b/googletest_macro/src/matches_pattern.rs @@ -16,9 +16,7 @@ use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; use quote::quote; use syn::{ parse::{Parse, ParseStream, Parser as _}, - parse_macro_input, - punctuated::Punctuated, - Expr, ExprCall, Pat, Token, + parse_macro_input, Expr, ExprCall, Pat, Token, }; /// This is an implementation detail of `googletest::matches_pattern!`. It @@ -164,9 +162,10 @@ fn parse_tuple_pattern_args( struct_name: TokenStream, group_content: TokenStream, ) -> syn::Result { - let parser = Punctuated::::parse_terminated; - let fields = parser - .parse2(group_content)? + let (patterns, non_exhaustive) = + parse_list_terminated_pattern::.parse2(group_content)?; + let field_count = patterns.len(); + let field_patterns = patterns .into_iter() .enumerate() .filter_map(|(index, maybe_pattern)| maybe_pattern.0.map(|pattern| (index, pattern))) @@ -174,12 +173,35 @@ fn parse_tuple_pattern_args( let index = syn::Index::from(index); quote! { googletest::matchers::field!(#struct_name.#index, #ref_token #matcher) } }); - Ok(quote! { + + let matcher = quote! { googletest::matchers::__internal_unstable_do_not_depend_on_these::is( stringify!(#struct_name), - all!( #(#fields),* ) + all!( #(#field_patterns),* ) ) - }) + }; + + // Do an exhaustiveness check only if the pattern doesn't end with `..`. + if non_exhaustive { + Ok(matcher) + } else { + let empty_fields = std::iter::repeat(quote! { _ }).take(field_count); + Ok(quote! { + googletest::matchers::__internal_unstable_do_not_depend_on_these::compile_assert_and_match( + |actual| { + // Exhaustively check that all field names are specified. + match actual { + #struct_name ( #(#empty_fields),* ) => (), + // The pattern below is unreachable if the type is a struct (as opposed to + // an enum). Since the macro can't know which it is, we always include it + // and just tell the compiler not to complain. + #[allow(unreachable_patterns)] + _ => {}, + } + }, + #matcher) + }) + } } ////////////////////////////////////////////////////////////////////////////////