From eb9b918fa78137796bb5fd77a3988295e69a4005 Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Mon, 12 Jun 2023 07:33:33 -0700 Subject: [PATCH] Recognize any T or T<'a, A> --- NEWS.md | 3 +++ src/visit.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/NEWS.md b/NEWS.md index 693a832b..c028df6a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,9 @@ - Mutate `BinaryHeap`, `BTreeSet`, `HashSet`, `LinkedList`, and `VecDeque` to generate empty and one-element containers. +- Generically recognize functions returning `T` or `T<'a, A>'` and try + to construct them from an `A`. + ## 23.6.0 - Generate `Box::leak(Box::new(...))` as a mutation of functions returning diff --git a/src/visit.rs b/src/visit.rs index 9103f982..f35519da 100644 --- a/src/visit.rs +++ b/src/visit.rs @@ -333,6 +333,9 @@ fn type_replacements(type_: &Type, error_exprs: &[Expr]) -> Vec { } else if path.is_ident("String") { reps.push(quote! { String::new() }); reps.push(quote! { "xyzzy".into() }); + } else if path.is_ident("str") { + reps.push(quote! { "" }); + reps.push(quote! { "xyzzy" }); } else if path_is_unsigned(path) { reps.push(quote! { 0 }); reps.push(quote! { 1 }); @@ -408,6 +411,23 @@ fn type_replacements(type_: &Type, error_exprs: &[Expr]) -> Vec { quote! { #collection_type::from_iter([#rep]) } }), ); + } else if let Some((collection_type, inner_type)) = maybe_collection_or_container(path) + { + // Something like `T` or `T<'a, A>`, when we don't know exactly how + // to call it, but we strongly suspect that you could construct it from + // an `A`. For example, `Cow`. + reps.push(quote! { #collection_type::new() }); + reps.extend( + type_replacements(inner_type, error_exprs) + .into_iter() + .flat_map(|rep| { + [ + quote! { #collection_type::from_iter([#rep]) }, + quote! { #collection_type::new(#rep) }, + quote! { #collection_type::from(#rep) }, + ] + }), + ); } else { reps.push(quote! { Default::default() }); } @@ -536,6 +556,27 @@ fn known_collection(path: &Path) -> Option<(&Ident, &Type)> { None } +/// Match a type with one type argument, which might be a container or collection. +fn maybe_collection_or_container(path: &Path) -> Option<(&Ident, &Type)> { + let last = path.segments.last()?; + if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) = + &last.arguments + { + let type_args: Vec<_> = args + .iter() + .filter_map(|a| match a { + GenericArgument::Type(t) => Some(t), + _ => None, + }) + .collect(); + // TODO: Return the path with args stripped out. + if type_args.len() == 1 { + return Some((&last.ident, type_args.first().unwrap())); + } + } + None +} + fn path_is_float(path: &Path) -> bool { ["f32", "f64"].iter().any(|s| path.is_ident(s)) } @@ -959,6 +1000,22 @@ mod test { ); } + #[test] + fn cow_replacement() { + assert_eq!( + replace(&parse_quote! { -> Cow<'static, str> }, &[]), + &[ + "Cow::new()", + "Cow::from_iter([\"\"])", + "Cow::new(\"\")", + "Cow::from(\"\")", + "Cow::from_iter([\"xyzzy\"])", + "Cow::new(\"xyzzy\")", + "Cow::from(\"xyzzy\")", + ] + ); + } + fn replace(return_type: &ReturnType, error_exprs: &[Expr]) -> Vec { return_type_replacements(return_type, error_exprs) .into_iter()