1#![allow( 2 clippy::manual_let_else, 3 clippy::too_many_lines, 4 clippy::uninlined_format_args 5)] 6 7#[macro_use] 8mod macros; 9 10use quote::quote; 11use syn::{DeriveInput, ItemFn, TypeParamBound, WhereClause, WherePredicate}; 12 13#[test] 14fn test_split_for_impl() { 15 let input = quote! { 16 struct S<'a, 'b: 'a, #[may_dangle] T: 'a = ()> where T: Debug; 17 }; 18 19 snapshot!(input as DeriveInput, @r###" 20 DeriveInput { 21 vis: Visibility::Inherited, 22 ident: "S", 23 generics: Generics { 24 lt_token: Some, 25 params: [ 26 GenericParam::Lifetime(LifetimeParam { 27 lifetime: Lifetime { 28 ident: "a", 29 }, 30 }), 31 Token![,], 32 GenericParam::Lifetime(LifetimeParam { 33 lifetime: Lifetime { 34 ident: "b", 35 }, 36 colon_token: Some, 37 bounds: [ 38 Lifetime { 39 ident: "a", 40 }, 41 ], 42 }), 43 Token![,], 44 GenericParam::Type(TypeParam { 45 attrs: [ 46 Attribute { 47 style: AttrStyle::Outer, 48 meta: Meta::Path { 49 segments: [ 50 PathSegment { 51 ident: "may_dangle", 52 }, 53 ], 54 }, 55 }, 56 ], 57 ident: "T", 58 colon_token: Some, 59 bounds: [ 60 TypeParamBound::Lifetime { 61 ident: "a", 62 }, 63 ], 64 eq_token: Some, 65 default: Some(Type::Tuple), 66 }), 67 ], 68 gt_token: Some, 69 where_clause: Some(WhereClause { 70 predicates: [ 71 WherePredicate::Type(PredicateType { 72 bounded_ty: Type::Path { 73 path: Path { 74 segments: [ 75 PathSegment { 76 ident: "T", 77 }, 78 ], 79 }, 80 }, 81 bounds: [ 82 TypeParamBound::Trait(TraitBound { 83 path: Path { 84 segments: [ 85 PathSegment { 86 ident: "Debug", 87 }, 88 ], 89 }, 90 }), 91 ], 92 }), 93 ], 94 }), 95 }, 96 data: Data::Struct { 97 fields: Fields::Unit, 98 semi_token: Some, 99 }, 100 } 101 "###); 102 103 let generics = input.generics; 104 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 105 106 let generated = quote! { 107 impl #impl_generics MyTrait for Test #ty_generics #where_clause {} 108 }; 109 let expected = quote! { 110 impl<'a, 'b: 'a, #[may_dangle] T: 'a> MyTrait 111 for Test<'a, 'b, T> 112 where 113 T: Debug 114 {} 115 }; 116 assert_eq!(generated.to_string(), expected.to_string()); 117 118 let turbofish = ty_generics.as_turbofish(); 119 let generated = quote! { 120 Test #turbofish 121 }; 122 let expected = quote! { 123 Test::<'a, 'b, T> 124 }; 125 assert_eq!(generated.to_string(), expected.to_string()); 126} 127 128#[test] 129fn test_ty_param_bound() { 130 let tokens = quote!('a); 131 snapshot!(tokens as TypeParamBound, @r###" 132 TypeParamBound::Lifetime { 133 ident: "a", 134 } 135 "###); 136 137 let tokens = quote!('_); 138 snapshot!(tokens as TypeParamBound, @r###" 139 TypeParamBound::Lifetime { 140 ident: "_", 141 } 142 "###); 143 144 let tokens = quote!(Debug); 145 snapshot!(tokens as TypeParamBound, @r###" 146 TypeParamBound::Trait(TraitBound { 147 path: Path { 148 segments: [ 149 PathSegment { 150 ident: "Debug", 151 }, 152 ], 153 }, 154 }) 155 "###); 156 157 let tokens = quote!(?Sized); 158 snapshot!(tokens as TypeParamBound, @r###" 159 TypeParamBound::Trait(TraitBound { 160 modifier: TraitBoundModifier::Maybe, 161 path: Path { 162 segments: [ 163 PathSegment { 164 ident: "Sized", 165 }, 166 ], 167 }, 168 }) 169 "###); 170} 171 172#[test] 173fn test_fn_precedence_in_where_clause() { 174 // This should parse as two separate bounds, `FnOnce() -> i32` and `Send` - not 175 // `FnOnce() -> (i32 + Send)`. 176 let input = quote! { 177 fn f<G>() 178 where 179 G: FnOnce() -> i32 + Send, 180 { 181 } 182 }; 183 184 snapshot!(input as ItemFn, @r###" 185 ItemFn { 186 vis: Visibility::Inherited, 187 sig: Signature { 188 ident: "f", 189 generics: Generics { 190 lt_token: Some, 191 params: [ 192 GenericParam::Type(TypeParam { 193 ident: "G", 194 }), 195 ], 196 gt_token: Some, 197 where_clause: Some(WhereClause { 198 predicates: [ 199 WherePredicate::Type(PredicateType { 200 bounded_ty: Type::Path { 201 path: Path { 202 segments: [ 203 PathSegment { 204 ident: "G", 205 }, 206 ], 207 }, 208 }, 209 bounds: [ 210 TypeParamBound::Trait(TraitBound { 211 path: Path { 212 segments: [ 213 PathSegment { 214 ident: "FnOnce", 215 arguments: PathArguments::Parenthesized { 216 output: ReturnType::Type( 217 Type::Path { 218 path: Path { 219 segments: [ 220 PathSegment { 221 ident: "i32", 222 }, 223 ], 224 }, 225 }, 226 ), 227 }, 228 }, 229 ], 230 }, 231 }), 232 Token![+], 233 TypeParamBound::Trait(TraitBound { 234 path: Path { 235 segments: [ 236 PathSegment { 237 ident: "Send", 238 }, 239 ], 240 }, 241 }), 242 ], 243 }), 244 Token![,], 245 ], 246 }), 247 }, 248 output: ReturnType::Default, 249 }, 250 block: Block { 251 stmts: [], 252 }, 253 } 254 "###); 255 256 let where_clause = input.sig.generics.where_clause.as_ref().unwrap(); 257 assert_eq!(where_clause.predicates.len(), 1); 258 259 let predicate = match &where_clause.predicates[0] { 260 WherePredicate::Type(pred) => pred, 261 _ => panic!("wrong predicate kind"), 262 }; 263 264 assert_eq!(predicate.bounds.len(), 2, "{:#?}", predicate.bounds); 265 266 let first_bound = &predicate.bounds[0]; 267 assert_eq!(quote!(#first_bound).to_string(), "FnOnce () -> i32"); 268 269 let second_bound = &predicate.bounds[1]; 270 assert_eq!(quote!(#second_bound).to_string(), "Send"); 271} 272 273#[test] 274fn test_where_clause_at_end_of_input() { 275 let input = quote! { 276 where 277 }; 278 279 snapshot!(input as WhereClause, @"WhereClause"); 280 281 assert_eq!(input.predicates.len(), 0); 282} 283