diff --git a/derive/Cargo.toml b/derive/Cargo.toml index f14da8d9..fced5707 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -16,5 +16,5 @@ proc-macro = true [dependencies] quote = "1.0" -syn = { version = "1.0", features = ["derive"] } +syn = { version = "1.0", features = ["derive", "visit-mut"] } proc-macro2 = "1.0" diff --git a/derive/src/lib.rs b/derive/src/lib.rs index b66f0517..ab2811ef 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -35,8 +35,10 @@ use syn::{ Error, Result, }, + parse_quote, punctuated::Punctuated, token::Comma, + visit_mut::VisitMut, Data, DataEnum, DataStruct, @@ -45,6 +47,7 @@ use syn::{ ExprLit, Field, Fields, + Lifetime, Lit, Variant, }; @@ -69,7 +72,11 @@ fn generate_type(input: TokenStream2) -> Result { let ident = &ast.ident; trait_bounds::add(ident, &mut ast.generics, &ast.data)?; - let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + ast.generics + .lifetimes_mut() + .for_each(|l| *l = parse_quote!('static)); + + let (_, ty_generics, where_clause) = ast.generics.split_for_impl(); let generic_type_ids = ast.generics.type_params().map(|ty| { let ty_ident = &ty.ident; quote! { @@ -83,9 +90,9 @@ fn generate_type(input: TokenStream2) -> Result { Data::Enum(ref e) => generate_variant_type(e), Data::Union(_) => return Err(Error::new_spanned(input, "Unions not supported")), }; - + let generic_types = ast.generics.type_params(); let type_info_impl = quote! { - impl #impl_generics ::scale_info::TypeInfo for #ident #ty_generics #where_clause { + impl <#( #generic_types ),*> ::scale_info::TypeInfo for #ident #ty_generics #where_clause { type Identity = Self; fn type_info() -> ::scale_info::Type { ::scale_info::Type::builder() @@ -107,6 +114,17 @@ fn generate_fields(fields: &FieldsList) -> Vec { .iter() .map(|f| { let (ty, ident) = (&f.ty, &f.ident); + // Replace any field lifetime params with `static to prevent "unnecessary lifetime parameter" + // warning. Any lifetime parameters are specified as 'static in the type of the impl. + struct StaticLifetimesReplace; + impl VisitMut for StaticLifetimesReplace { + fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) { + *lifetime = parse_quote!('static) + } + } + let mut ty = ty.clone(); + StaticLifetimesReplace.visit_type_mut(&mut ty); + let type_name = clean_type_string("e!(#ty).to_string()); if let Some(i) = ident { @@ -139,6 +157,7 @@ fn clean_type_string(input: &str) -> String { .replace(" <", "<") .replace("< ", "<") .replace(" >", ">") + .replace("& \'", "&'") } fn generate_composite_type(data_struct: &DataStruct) -> TokenStream2 { diff --git a/derive/src/trait_bounds.rs b/derive/src/trait_bounds.rs index fd4ba247..3b7b2b6c 100644 --- a/derive/src/trait_bounds.rs +++ b/derive/src/trait_bounds.rs @@ -28,29 +28,34 @@ use syn::{ /// associated types (e.g. `T::A: TypeInfo`), correctly dealing with /// self-referential types. pub fn add(input_ident: &Ident, generics: &mut Generics, data: &syn::Data) -> Result<()> { - let ty_params = generics.type_params_mut().fold(Vec::new(), |mut acc, p| { - p.bounds.push(parse_quote!(::scale_info::TypeInfo)); - p.bounds.push(parse_quote!('static)); - acc.push(p.ident.clone()); - acc - }); + let ty_params_ids = generics + .type_params() + .map(|type_param| type_param.ident.clone()) + .collect::>(); - if ty_params.is_empty() { + if ty_params_ids.is_empty() { return Ok(()) } - let types = collect_types_to_bind(input_ident, data, &ty_params)?; - - if !types.is_empty() { - let where_clause = generics.make_where_clause(); + let types = collect_types_to_bind(input_ident, data, &ty_params_ids)?; + let type_params = generics.type_params().cloned().collect::>(); + let where_clause = generics.make_where_clause(); - types.into_iter().for_each(|ty| { - where_clause - .predicates - .push(parse_quote!(#ty : ::scale_info::TypeInfo + 'static)) - }); - } + types.into_iter().for_each(|ty| { + where_clause + .predicates + .push(parse_quote!(#ty : ::scale_info::TypeInfo + 'static)) + }); + type_params.into_iter().for_each(|type_param| { + let ident = type_param.ident; + let mut bounds = type_param.bounds; + bounds.push(parse_quote!(::scale_info::TypeInfo)); + bounds.push(parse_quote!('static)); + where_clause + .predicates + .push(parse_quote!( #ident : #bounds)); + }); Ok(()) } diff --git a/test_suite/tests/derive.rs b/test_suite/tests/derive.rs index 6ffaf6af..a4da8f49 100644 --- a/test_suite/tests/derive.rs +++ b/test_suite/tests/derive.rs @@ -186,8 +186,9 @@ fn associated_types_derive_without_bounds() { } #[allow(unused)] #[derive(TypeInfo)] - struct Assoc { + struct Assoc<'bar, T: Types> { a: T::A, + b: &'bar u64, } #[derive(TypeInfo)] @@ -199,7 +200,11 @@ fn associated_types_derive_without_bounds() { let struct_type = Type::builder() .path(Path::new("Assoc", "derive")) .type_params(tuple_meta_type!(ConcreteTypes)) - .composite(Fields::named().field_of::("a", "T::A")); + .composite( + Fields::named() + .field_of::("a", "T::A") + .field_of::("b", "&'static u64"), + ); assert_type!(Assoc, struct_type); } @@ -224,8 +229,8 @@ fn whitespace_scrubbing_works() { fn ui_tests() { let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/fail_missing_derive.rs"); - t.compile_fail("tests/ui/fail_non_static_lifetime.rs"); t.compile_fail("tests/ui/fail_unions.rs"); + t.pass("tests/ui/pass_non_static_lifetime.rs"); t.pass("tests/ui/pass_self_referential.rs"); t.pass("tests/ui/pass_basic_generic_type.rs"); t.pass("tests/ui/pass_complex_generic_self_referential_type.rs"); diff --git a/test_suite/tests/ui/pass_non_static_lifetime.rs b/test_suite/tests/ui/pass_non_static_lifetime.rs new file mode 100644 index 00000000..7b84f281 --- /dev/null +++ b/test_suite/tests/ui/pass_non_static_lifetime.rs @@ -0,0 +1,12 @@ +use scale_info::TypeInfo; + +#[derive(TypeInfo)] +struct Me<'a> { + _me: &'a Me<'a>, +} + +fn assert_type_info() {} + +fn main() { + assert_type_info::(); +}