#![allow( clippy::manual_let_else, clippy::too_many_lines, clippy::uninlined_format_args )] #[macro_use] mod macros; use quote::quote; use syn::{DeriveInput, ItemFn, TypeParamBound, WhereClause, WherePredicate}; #[test] fn test_split_for_impl() { let input = quote! { struct S<'a, 'b: 'a, #[may_dangle] T: 'a = ()> where T: Debug; }; snapshot!(input as DeriveInput, @r###" DeriveInput { vis: Visibility::Inherited, ident: "S", generics: Generics { lt_token: Some, params: [ GenericParam::Lifetime(LifetimeParam { lifetime: Lifetime { ident: "a", }, }), Token![,], GenericParam::Lifetime(LifetimeParam { lifetime: Lifetime { ident: "b", }, colon_token: Some, bounds: [ Lifetime { ident: "a", }, ], }), Token![,], GenericParam::Type(TypeParam { attrs: [ Attribute { style: AttrStyle::Outer, meta: Meta::Path { segments: [ PathSegment { ident: "may_dangle", }, ], }, }, ], ident: "T", colon_token: Some, bounds: [ TypeParamBound::Lifetime { ident: "a", }, ], eq_token: Some, default: Some(Type::Tuple), }), ], gt_token: Some, where_clause: Some(WhereClause { predicates: [ WherePredicate::Type(PredicateType { bounded_ty: Type::Path { path: Path { segments: [ PathSegment { ident: "T", }, ], }, }, bounds: [ TypeParamBound::Trait(TraitBound { path: Path { segments: [ PathSegment { ident: "Debug", }, ], }, }), ], }), ], }), }, data: Data::Struct { fields: Fields::Unit, semi_token: Some, }, } "###); let generics = input.generics; let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let generated = quote! { impl #impl_generics MyTrait for Test #ty_generics #where_clause {} }; let expected = quote! { impl<'a, 'b: 'a, #[may_dangle] T: 'a> MyTrait for Test<'a, 'b, T> where T: Debug {} }; assert_eq!(generated.to_string(), expected.to_string()); let turbofish = ty_generics.as_turbofish(); let generated = quote! { Test #turbofish }; let expected = quote! { Test::<'a, 'b, T> }; assert_eq!(generated.to_string(), expected.to_string()); } #[test] fn test_ty_param_bound() { let tokens = quote!('a); snapshot!(tokens as TypeParamBound, @r###" TypeParamBound::Lifetime { ident: "a", } "###); let tokens = quote!('_); snapshot!(tokens as TypeParamBound, @r###" TypeParamBound::Lifetime { ident: "_", } "###); let tokens = quote!(Debug); snapshot!(tokens as TypeParamBound, @r###" TypeParamBound::Trait(TraitBound { path: Path { segments: [ PathSegment { ident: "Debug", }, ], }, }) "###); let tokens = quote!(?Sized); snapshot!(tokens as TypeParamBound, @r###" TypeParamBound::Trait(TraitBound { modifier: TraitBoundModifier::Maybe, path: Path { segments: [ PathSegment { ident: "Sized", }, ], }, }) "###); } #[test] fn test_fn_precedence_in_where_clause() { // This should parse as two separate bounds, `FnOnce() -> i32` and `Send` - not // `FnOnce() -> (i32 + Send)`. let input = quote! { fn f() where G: FnOnce() -> i32 + Send, { } }; snapshot!(input as ItemFn, @r###" ItemFn { vis: Visibility::Inherited, sig: Signature { ident: "f", generics: Generics { lt_token: Some, params: [ GenericParam::Type(TypeParam { ident: "G", }), ], gt_token: Some, where_clause: Some(WhereClause { predicates: [ WherePredicate::Type(PredicateType { bounded_ty: Type::Path { path: Path { segments: [ PathSegment { ident: "G", }, ], }, }, bounds: [ TypeParamBound::Trait(TraitBound { path: Path { segments: [ PathSegment { ident: "FnOnce", arguments: PathArguments::Parenthesized { output: ReturnType::Type( Type::Path { path: Path { segments: [ PathSegment { ident: "i32", }, ], }, }, ), }, }, ], }, }), Token![+], TypeParamBound::Trait(TraitBound { path: Path { segments: [ PathSegment { ident: "Send", }, ], }, }), ], }), Token![,], ], }), }, output: ReturnType::Default, }, block: Block { stmts: [], }, } "###); let where_clause = input.sig.generics.where_clause.as_ref().unwrap(); assert_eq!(where_clause.predicates.len(), 1); let predicate = match &where_clause.predicates[0] { WherePredicate::Type(pred) => pred, _ => panic!("wrong predicate kind"), }; assert_eq!(predicate.bounds.len(), 2, "{:#?}", predicate.bounds); let first_bound = &predicate.bounds[0]; assert_eq!(quote!(#first_bound).to_string(), "FnOnce () -> i32"); let second_bound = &predicate.bounds[1]; assert_eq!(quote!(#second_bound).to_string(), "Send"); } #[test] fn test_where_clause_at_end_of_input() { let input = quote! { where }; snapshot!(input as WhereClause, @"WhereClause"); assert_eq!(input.predicates.len(), 0); }