From f2ab8bb157d79c10e32005bddd24bbac6e23f64d Mon Sep 17 00:00:00 2001 From: michele Date: Sun, 9 May 2021 19:03:41 +0200 Subject: [PATCH] Close #116 Implemented transitive reference and lifetimes clean up --- src/refident.rs | 10 ++ src/render/fixture.rs | 4 +- src/render/mod.rs | 85 +----------- src/render/test.rs | 66 +-------- src/utils.rs | 301 +++++++++++++++++++++++++++++++++++++++++- 5 files changed, 311 insertions(+), 155 deletions(-) diff --git a/src/refident.rs b/src/refident.rs index 8df5add..e3083f1 100644 --- a/src/refident.rs +++ b/src/refident.rs @@ -65,3 +65,13 @@ impl MaybeType for FnArg { } } } + +impl MaybeIdent for syn::GenericParam { + fn maybe_ident(&self) -> Option<&Ident> { + match self { + syn::GenericParam::Type(syn::TypeParam { ident, .. }) + | syn::GenericParam::Const(syn::ConstParam { ident, .. }) => Some(ident), + syn::GenericParam::Lifetime(syn::LifetimeDef { lifetime, .. }) => Some(&lifetime.ident), + } + } +} diff --git a/src/render/fixture.rs b/src/render/fixture.rs index 1e22633..81f934c 100644 --- a/src/render/fixture.rs +++ b/src/render/fixture.rs @@ -3,10 +3,10 @@ use syn::{parse_quote, Ident, ItemFn}; use quote::quote; -use super::{generics_clean_up, inject, render_exec_call}; -use crate::parse::fixture::FixtureInfo; +use super::{inject, render_exec_call}; use crate::resolver::{self, Resolver}; use crate::utils::{fn_args, fn_args_idents}; +use crate::{parse::fixture::FixtureInfo, utils::generics_clean_up}; pub(crate) fn render<'a>(fixture: ItemFn, info: FixtureInfo) -> TokenStream { let name = &fixture.sig.ident; diff --git a/src/render/mod.rs b/src/render/mod.rs index 2d415fe..60b97e6 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -6,10 +6,7 @@ use std::collections::HashMap; use syn::token::Async; use proc_macro2::{Span, TokenStream}; -use syn::{ - parse_quote, Attribute, Expr, FnArg, Generics, Ident, ItemFn, Path, ReturnType, Stmt, Type, - WherePredicate, -}; +use syn::{parse_quote, Attribute, Expr, FnArg, Ident, ItemFn, Path, ReturnType, Stmt}; use quote::{format_ident, quote}; @@ -266,86 +263,6 @@ fn trace_arguments<'a>( } } -fn where_predicate_bounded_type(wp: &WherePredicate) -> Option<&Type> { - match wp { - syn::WherePredicate::Type(pt) => Some(&pt.bounded_ty), - _ => None, - } -} - -//noinspection RsTypeCheck -fn generics_clean_up<'a>( - original: &Generics, - inputs: impl Iterator, - output: &ReturnType, -) -> syn::Generics { - use syn::visit::Visit; - #[derive(Default, Debug)] - struct Used(std::collections::HashSet); - impl<'ast> syn::visit::Visit<'ast> for Used { - fn visit_type_path(&mut self, i: &'ast syn::TypePath) { - if let Some(id) = i.path.get_ident() { - self.0.insert(id.clone()); - } - } - fn visit_type_array(&mut self, i: &'ast syn::TypeArray) { - let ep = match &i.len { - syn::Expr::Path(ep) => ep, - _ => return, - }; - if let Some(id) = ep.path.get_ident() { - self.0.insert(id.clone()); - } - } - } - let mut outs: Used = Default::default(); - outs.visit_return_type(output); - inputs.for_each(|fn_arg| outs.visit_fn_arg(fn_arg)); - let mut result: Generics = original.clone(); - result.params = result - .params - .into_iter() - .filter(|p| match p { - syn::GenericParam::Type(syn::TypeParam { ident, .. }) - | syn::GenericParam::Const(syn::ConstParam { ident, .. }) => outs.0.contains(ident), - syn::GenericParam::Lifetime(_) => true, - }) - .collect(); - result.where_clause.as_mut().map(|mut w| { - w.predicates = w - .predicates - .clone() - .into_iter() - .filter(|wp| { - where_predicate_bounded_type(wp) - .and_then(|t| first_type_path_segment_ident(t)) - .map(|t| outs.0.contains(t)) - .unwrap_or(true) - }) - .collect() - }); - result -} - -// If type is not self and doesn't starts with :: return the first ident -// of its path segment: only if is a simple path. -// If type is a simple ident just return the this ident. That is useful to -// find the base type for associate type indication -fn first_type_path_segment_ident(t: &Type) -> Option<&Ident> { - match t { - Type::Path(tp) if tp.qself.is_none() && tp.path.leading_colon.is_none() => tp - .path - .segments - .iter() - .nth(0) - .and_then(|ps| match ps.arguments { - syn::PathArguments::None => Some(&ps.ident), - _ => None, - }), - _ => None, - } -} - struct TestCaseRender<'a> { name: Ident, attrs: &'a [syn::Attribute], diff --git a/src/render/test.rs b/src/render/test.rs index 698bb00..88969cb 100644 --- a/src/render/test.rs +++ b/src/render/test.rs @@ -1501,68 +1501,4 @@ mod complete_should { assert_eq!(attrs, &f.attrs[3..]); } } -} - -mod generics_clean_up_should { - use super::{assert_eq, *}; - - #[test] - fn remove_generics_not_in_output() { - // Should remove all generics parameters that are not present in output - let item_fn: ItemFn = r#" - pub fn test, B, F, H: Iterator>() -> (H, B, String, &str) - where F: ToString, - B: Borrow - - { } - "# - .ast(); - - let expected: ItemFn = r#" - pub fn test>() -> (H, B, String, &str) - where B: Borrow - { } - "# - .ast(); - - let cleaned = generics_clean_up( - &item_fn.sig.generics, - item_fn.sig.inputs.iter(), - &item_fn.sig.output, - ); - - assert_eq!(expected.sig.generics, cleaned); - } - - #[test] - fn not_remove_generics_used_in_arguments() { - // Should remove all generics parameters that are not present in output - let item_fn: ItemFn = r#" - pub fn test, B, F, H: Iterator> - (h: H, it: impl Iterator, j: &[B]) - where F: ToString, - B: Borrow - { } - "# - .ast(); - - let expected: ItemFn = r#" - pub fn test, B, H: Iterator> - (h: H, it: impl Iterator, j: &[B]) - where - B: Borrow - { } - "# - .ast(); - - let cleaned = generics_clean_up( - &item_fn.sig.generics, - item_fn.sig.inputs.iter(), - &item_fn.sig.output, - ); - - dbg!(item_fn.sig.inputs); - - assert_eq!(expected.sig.generics, cleaned); - } -} +} \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs index 699119f..100821c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,9 +1,10 @@ -use crate::refident::MaybeIdent; -use quote::format_ident; - /// Contains some unsorted functions used across others modules /// -use syn::{Attribute, Expr, FnArg, Ident, ItemFn}; +use quote::format_ident; +use std::collections::{HashMap, HashSet}; + +use crate::refident::MaybeIdent; +use syn::{Attribute, Expr, FnArg, Generics, Ident, ItemFn, ReturnType, Type, WherePredicate}; /// Return an iterator over fn arguments items. /// @@ -51,12 +52,215 @@ impl> IsLiteralExpression for E { } } +// Recoursive search id by reference till find one in ends +fn _is_used( + visited: &mut HashSet, + id: &Ident, + references: &HashMap>, + ends: &HashSet, +) -> bool { + if visited.contains(id) { + return false; + } + visited.insert(id.clone()); + if ends.contains(id) { + return true; + } + if references.contains_key(id) { + for refered in references.get(id).unwrap() { + if _is_used(visited, refered, references, ends) { + return true; + } + } + } + false +} + +// Recoursive search id by reference till find one in ends +fn is_used(id: &Ident, references: &HashMap>, ends: &HashSet) -> bool { + let mut visited = Default::default(); + _is_used(&mut visited, id, references, ends) +} + +impl MaybeIdent for syn::WherePredicate { + fn maybe_ident(&self) -> Option<&Ident> { + match self { + WherePredicate::Type(syn::PredicateType { bounded_ty: t, .. }) => { + first_type_path_segment_ident(t) + } + WherePredicate::Lifetime(syn::PredicateLifetime { lifetime, .. }) => { + Some(&lifetime.ident) + } + WherePredicate::Eq(_) => None, + } + } +} + +#[derive(Default)] +struct SearchSimpleTypeName(HashSet); + +impl SearchSimpleTypeName { + fn take(self) -> HashSet { + self.0 + } + + fn visit_inputs<'a>(&mut self, inputs: impl Iterator) { + use syn::visit::Visit; + inputs.for_each(|fn_arg| self.visit_fn_arg(fn_arg)); + } + fn visit_output(&mut self, output: &ReturnType) { + use syn::visit::Visit; + self.visit_return_type(output); + } + + fn collect_from_type_param(tp: &syn::TypeParam) -> Self { + let mut s: Self = Default::default(); + use syn::visit::Visit; + s.visit_type_param(tp); + s + } + + fn collect_from_where_predicate(wp: &syn::WherePredicate) -> Self { + let mut s: Self = Default::default(); + use syn::visit::Visit; + s.visit_where_predicate(wp); + s + } +} + +impl<'ast> syn::visit::Visit<'ast> for SearchSimpleTypeName { + fn visit_path(&mut self, p: &'ast syn::Path) { + if let Some(id) = p.get_ident() { + self.0.insert(id.clone()); + } + syn::visit::visit_path(self, p) + } + + fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) { + self.0.insert(i.ident.clone()); + syn::visit::visit_lifetime(self, i) + } +} + +// Take generics definitions and where clauses and return the +// a map from simple types (lifetime names or type with just names) +// to a set of all simple types that use it as some costrain. +fn extract_references_map(generics: &Generics) -> HashMap> { + let mut references = HashMap::>::default(); + // Extracts references from types param + generics.type_params().for_each(|tp| { + SearchSimpleTypeName::collect_from_type_param(tp) + .take() + .into_iter() + .for_each(|id| { + references.entry(id).or_default().insert(tp.ident.clone()); + }); + }); + // Extracts references from where clauses + generics + .where_clause + .iter() + .flat_map(|wc| wc.predicates.iter()) + .filter_map(|wp| wp.maybe_ident().map(|id| (id, wp))) + .for_each(|(ref_ident, wp)| { + SearchSimpleTypeName::collect_from_where_predicate(wp) + .take() + .into_iter() + .for_each(|id| { + references.entry(id).or_default().insert(ref_ident.clone()); + }); + }); + references +} + +// Return a hash set that contains all types and lifetimes referenced +// in input/output expressed by a single ident. +fn references_ident_types<'a>( + generics: &Generics, + inputs: impl Iterator, + output: &ReturnType, +) -> HashSet { + let mut used: SearchSimpleTypeName = Default::default(); + used.visit_output(output); + used.visit_inputs(inputs); + let references = extract_references_map(generics); + let mut used = used.take(); + let input_output = used.clone(); + // Extend the input output collected ref with the transitive ones: + used.extend( + generics + .params + .iter() + .filter_map(MaybeIdent::maybe_ident) + .filter(|&id| is_used(id, &references, &input_output)) + .cloned(), + ); + used +} + +fn filtered_predicates(mut wc: syn::WhereClause, valids: &HashSet) -> syn::WhereClause { + wc.predicates = wc + .predicates + .clone() + .into_iter() + .filter(|wp| { + wp.maybe_ident() + .map(|t| valids.contains(t)) + .unwrap_or_default() + }) + .collect(); + wc +} + +fn filtered_generics<'a>( + params: impl Iterator + 'a, + valids: &'a HashSet, +) -> impl Iterator + 'a { + params.filter(move |p| match p.maybe_ident() { + Some(id) => valids.contains(id), + None => false, + }) +} + +//noinspection RsTypeCheck +pub(crate) fn generics_clean_up<'a>( + original: &Generics, + inputs: impl Iterator, + output: &ReturnType, +) -> syn::Generics { + let used = references_ident_types(original, inputs, output); + let mut result: Generics = original.clone(); + result.params = filtered_generics(result.params.into_iter(), &used).collect(); + result.where_clause = result.where_clause.map(|wc| filtered_predicates(wc, &used)); + result +} + +// If type is not self and doesn't starts with :: return the first ident +// of its path segment: only if is a simple path. +// If type is a simple ident just return the this ident. That is useful to +// find the base type for associate type indication +fn first_type_path_segment_ident(t: &Type) -> Option<&Ident> { + match t { + Type::Path(tp) if tp.qself.is_none() && tp.path.leading_colon.is_none() => tp + .path + .segments + .iter() + .nth(0) + .and_then(|ps| match ps.arguments { + syn::PathArguments::None => Some(&ps.ident), + _ => None, + }), + _ => None, + } +} + #[cfg(test)] mod test { use syn::parse_quote; use super::*; use crate::test::{assert_eq, *}; + use mytest::rstest; #[test] fn fn_args_idents_should() { @@ -79,4 +283,93 @@ mod test { assert!(fn_args_has_ident(&item_fn, &ident("first"))); assert!(!fn_args_has_ident(&item_fn, &ident("third"))); } + + #[rstest] + #[case::base("fn foo(a: A) -> B {}", &["A", "B"])] + #[case::use_const_in_array("fn foo(a: A) -> [u32; B] {}", &["A", "B", "u32"])] + #[case::in_type_args("fn foo(a: A) -> SomeType {}", &["A", "B"])] + #[case::in_type_args("fn foo(a: SomeType, b: SomeType) {}", &["A", "B"])] + #[case::pointers("fn foo(a: *const A, b: &B) {}", &["A", "B"])] + #[case::lifetime("fn foo<'a, A, B, C>(a: A, b: &'a B) {}", &["a", "A", "B"])] + #[case::transitive_lifetime("fn foo<'a, A, B, C>(a: A, b: B) where B: Iterator + 'a {}", &["a", "A", "B"])] + #[case::associated("fn foo<'a, A:Copy, C>(b: impl Iterator + 'a) {}", &["a", "A"])] + #[case::transitive_in_defs("fn foo>(b: B) {}", &["A", "B"])] + #[case::transitive_in_where("fn foo(b: B) where B: Iterator {}", &["A", "B"])] + #[case::transitive_const("fn foo(b: B) where B: Some {}", &["A", "B"])] + #[case::transitive_lifetime("fn foo<'a, A, B, C>(a: A, b: B) where B: Iterator + 'a {}", &["a", "A", "B"])] + #[case::transitive_lifetime(r#"fn foo<'a, 'b, 'c, 'd, A, B, C> + (a: A, b: B) + where B: Iterator + 'c, + 'c: 'a + 'b {}"#, &["a", "b", "c", "A", "B"])] + fn references_ident_types_should(#[case] f: &str, #[case] expected: &[&str]) { + let f: ItemFn = f.ast(); + let used = references_ident_types(&f.sig.generics, f.sig.inputs.iter(), &f.sig.output); + + let expected = to_idents!(expected) + .into_iter() + .collect::>(); + + assert_eq!(expected, used); + } + + #[rstest] + #[case::remove_not_in_output( + r#"fn test, B, F, H: Iterator>() -> (H, B, String, &str) + where F: ToString, + B: Borrow + {}"#, + r#"fn test>() -> (H, B, String, &str) + where B: Borrow + {}"# + )] + #[case::not_remove_used_in_arguments( + r#"fn test, B, F, H: Iterator> + (h: H, it: impl Iterator, j: &[B]) + where F: ToString, + B: Borrow + {}"#, + r#"fn test, B, H: Iterator> + (h: H, it: impl Iterator, j: &[B]) + where + B: Borrow + {}"# + )] + #[case::dont_remove_transitive( + r#"fn test(a: A) where + B: AsRef, + A: Iterator, + D: ArsRef {}"#, + r#"fn test(a: A) where + B: AsRef, + A: Iterator {}"# + )] + #[case::remove_unused_lifetime( + "fn test<'a, 'b, 'c, 'd, 'e, 'f, 'g, A>(a: &'a uint32, b: impl AsRef + 'b) where 'b: 'c + 'd, A: Copy + 'e, 'f: 'g {}", + "fn test<'a, 'b, 'c, 'd, 'e, A>(a: &'a uint32, b: impl AsRef + 'b) where 'b: 'c + 'd, A: Copy + 'e {}" + )] + #[case::remove_unused_const( + r#"fn test + (a: [u32; A], b: SomeType, c: T) where + T: Iterator, + O: AsRef + {}"#, + r#"fn test + (a: [u32; A], b: SomeType, c: T) where + T: Iterator + {}"# + )] + fn generics_cleaner(#[case] code: &str, #[case] expected: &str) { + // Should remove all generics parameters that are not present in output + let item_fn: ItemFn = code.ast(); + + let expected: ItemFn = expected.ast(); + + let cleaned = generics_clean_up( + &item_fn.sig.generics, + item_fn.sig.inputs.iter(), + &item_fn.sig.output, + ); + + assert_eq!(expected.sig.generics, cleaned); + } }