diff --git a/NEWS.md b/NEWS.md index 76bc43a7..ad9046e1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -20,6 +20,9 @@ - Mutate functions returning `&[T]` and `&mut [T]` to return leaked vecs of values. +- Mutate `(A, B, C, ...)` into the product of all replacements for + `a, b, c, ...` + ## 23.9.0 - Fixed a bug causing an assertion failure when cargo-mutants was run from a diff --git a/book/src/mutants.md b/book/src/mutants.md index 4fe602a8..9dfea096 100644 --- a/book/src/mutants.md +++ b/book/src/mutants.md @@ -37,6 +37,7 @@ More mutation genres and patterns will be added in future releases. | `&[T]`, `&mut [T]`| Leaked empty and one-element vecs | | `&T` | `&...` (all replacements for T) | | `HttpResponse` | `HttpResponse::Ok().finish` | +| `(A, B, ...)` | `(a, b, ...)` for the product of all replacements of A, B, ... | | (any other) | `Default::default()` | `...` in the mutation patterns indicates that the type is recursively mutated. diff --git a/src/fnvalue.rs b/src/fnvalue.rs index 65030966..c8b7d5ef 100644 --- a/src/fnvalue.rs +++ b/src/fnvalue.rs @@ -182,7 +182,17 @@ fn type_replacements(type_: &Type, error_exprs: &[Expr]) -> impl Iterator { vec![quote! { () }] - // TODO: Also recurse into non-empty tuples. + } + Type::Tuple(TypeTuple { elems, .. }) => { + // Generate the cartesian product of replacements of every type within the tuple. + elems + .iter() + .map(|elem| type_replacements(elem, error_exprs).collect_vec()) + .multi_cartesian_product() + .map(|reps| { + quote! { ( #( #reps ),* ) } + }) + .collect_vec() } Type::Never(_) => { vec![] @@ -544,6 +554,31 @@ mod test { ); } + #[test] + fn tuple_combinations() { + check_replacements( + parse_quote! { -> (bool, usize) }, + &[], + &["(true, 0)", "(true, 1)", "(false, 0)", "(false, 1)"], + ) + } + + #[test] + fn tuple_combination_longer() { + check_replacements( + parse_quote! { -> (bool, Option) }, + &[], + &[ + "(true, None)", + "(true, Some(String::new()))", + r#"(true, Some("xyzzy".into()))"#, + "(false, None)", + "(false, Some(String::new()))", + r#"(false, Some("xyzzy".into()))"#, + ], + ) + } + fn check_replacements(return_type: ReturnType, error_exprs: &[Expr], expected: &[&str]) { assert_eq!( return_type_replacements(&return_type, error_exprs)