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