1 | #[macro_use ] |
2 | mod macros; |
3 | |
4 | use proc_macro2::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree}; |
5 | use quote::quote; |
6 | use std::iter::FromIterator; |
7 | use syn::{Expr, ExprRange}; |
8 | |
9 | #[test] |
10 | fn test_expr_parse() { |
11 | let tokens = quote!(..100u32); |
12 | snapshot!(tokens as Expr, @r###" |
13 | Expr::Range { |
14 | limits: HalfOpen, |
15 | to: Some(Expr::Lit { |
16 | lit: 100u32, |
17 | }), |
18 | } |
19 | "### ); |
20 | |
21 | let tokens = quote!(..100u32); |
22 | snapshot!(tokens as ExprRange, @r###" |
23 | ExprRange { |
24 | limits: HalfOpen, |
25 | to: Some(Expr::Lit { |
26 | lit: 100u32, |
27 | }), |
28 | } |
29 | "### ); |
30 | } |
31 | |
32 | #[test] |
33 | fn test_await() { |
34 | // Must not parse as Expr::Field. |
35 | let tokens = quote!(fut.await); |
36 | |
37 | snapshot!(tokens as Expr, @r###" |
38 | Expr::Await { |
39 | base: Expr::Path { |
40 | path: Path { |
41 | segments: [ |
42 | PathSegment { |
43 | ident: "fut", |
44 | arguments: None, |
45 | }, |
46 | ], |
47 | }, |
48 | }, |
49 | } |
50 | "### ); |
51 | } |
52 | |
53 | #[rustfmt::skip] |
54 | #[test] |
55 | fn test_tuple_multi_index() { |
56 | let expected = snapshot!("tuple.0.0" as Expr, @r###" |
57 | Expr::Field { |
58 | base: Expr::Field { |
59 | base: Expr::Path { |
60 | path: Path { |
61 | segments: [ |
62 | PathSegment { |
63 | ident: "tuple", |
64 | arguments: None, |
65 | }, |
66 | ], |
67 | }, |
68 | }, |
69 | member: Unnamed(Index { |
70 | index: 0, |
71 | }), |
72 | }, |
73 | member: Unnamed(Index { |
74 | index: 0, |
75 | }), |
76 | } |
77 | "### ); |
78 | |
79 | for &input in &[ |
80 | "tuple .0.0" , |
81 | "tuple. 0.0" , |
82 | "tuple.0 .0" , |
83 | "tuple.0. 0" , |
84 | "tuple . 0 . 0" , |
85 | ] { |
86 | assert_eq!(expected, syn::parse_str(input).unwrap()); |
87 | } |
88 | |
89 | for tokens in vec![ |
90 | quote!(tuple.0.0), |
91 | quote!(tuple .0.0), |
92 | quote!(tuple. 0.0), |
93 | quote!(tuple.0 .0), |
94 | quote!(tuple.0. 0), |
95 | quote!(tuple . 0 . 0), |
96 | ] { |
97 | assert_eq!(expected, syn::parse2(tokens).unwrap()); |
98 | } |
99 | } |
100 | |
101 | #[test] |
102 | fn test_macro_variable_func() { |
103 | // mimics the token stream corresponding to `$fn()` |
104 | let tokens = TokenStream::from_iter(vec![ |
105 | TokenTree::Group(Group::new(Delimiter::None, quote! { f })), |
106 | TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())), |
107 | ]); |
108 | |
109 | snapshot!(tokens as Expr, @r###" |
110 | Expr::Call { |
111 | func: Expr::Group { |
112 | expr: Expr::Path { |
113 | path: Path { |
114 | segments: [ |
115 | PathSegment { |
116 | ident: "f", |
117 | arguments: None, |
118 | }, |
119 | ], |
120 | }, |
121 | }, |
122 | }, |
123 | } |
124 | "### ); |
125 | |
126 | let tokens = TokenStream::from_iter(vec![ |
127 | TokenTree::Punct(Punct::new('#' , Spacing::Alone)), |
128 | TokenTree::Group(Group::new(Delimiter::Bracket, quote! { outside })), |
129 | TokenTree::Group(Group::new(Delimiter::None, quote! { #[inside] f })), |
130 | TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())), |
131 | ]); |
132 | |
133 | snapshot!(tokens as Expr, @r###" |
134 | Expr::Call { |
135 | attrs: [ |
136 | Attribute { |
137 | style: Outer, |
138 | path: Path { |
139 | segments: [ |
140 | PathSegment { |
141 | ident: "outside", |
142 | arguments: None, |
143 | }, |
144 | ], |
145 | }, |
146 | tokens: TokenStream(``), |
147 | }, |
148 | ], |
149 | func: Expr::Group { |
150 | expr: Expr::Path { |
151 | attrs: [ |
152 | Attribute { |
153 | style: Outer, |
154 | path: Path { |
155 | segments: [ |
156 | PathSegment { |
157 | ident: "inside", |
158 | arguments: None, |
159 | }, |
160 | ], |
161 | }, |
162 | tokens: TokenStream(``), |
163 | }, |
164 | ], |
165 | path: Path { |
166 | segments: [ |
167 | PathSegment { |
168 | ident: "f", |
169 | arguments: None, |
170 | }, |
171 | ], |
172 | }, |
173 | }, |
174 | }, |
175 | } |
176 | "### ); |
177 | } |
178 | |
179 | #[test] |
180 | fn test_macro_variable_macro() { |
181 | // mimics the token stream corresponding to `$macro!()` |
182 | let tokens = TokenStream::from_iter(vec![ |
183 | TokenTree::Group(Group::new(Delimiter::None, quote! { m })), |
184 | TokenTree::Punct(Punct::new('!' , Spacing::Alone)), |
185 | TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())), |
186 | ]); |
187 | |
188 | snapshot!(tokens as Expr, @r###" |
189 | Expr::Macro { |
190 | mac: Macro { |
191 | path: Path { |
192 | segments: [ |
193 | PathSegment { |
194 | ident: "m", |
195 | arguments: None, |
196 | }, |
197 | ], |
198 | }, |
199 | delimiter: Paren, |
200 | tokens: TokenStream(``), |
201 | }, |
202 | } |
203 | "### ); |
204 | } |
205 | |
206 | #[test] |
207 | fn test_macro_variable_struct() { |
208 | // mimics the token stream corresponding to `$struct {}` |
209 | let tokens = TokenStream::from_iter(vec![ |
210 | TokenTree::Group(Group::new(Delimiter::None, quote! { S })), |
211 | TokenTree::Group(Group::new(Delimiter::Brace, TokenStream::new())), |
212 | ]); |
213 | |
214 | snapshot!(tokens as Expr, @r###" |
215 | Expr::Struct { |
216 | path: Path { |
217 | segments: [ |
218 | PathSegment { |
219 | ident: "S", |
220 | arguments: None, |
221 | }, |
222 | ], |
223 | }, |
224 | } |
225 | "### ); |
226 | } |
227 | |
228 | #[test] |
229 | fn test_macro_variable_match_arm() { |
230 | // mimics the token stream corresponding to `match v { _ => $expr }` |
231 | let tokens = TokenStream::from_iter(vec![ |
232 | TokenTree::Ident(Ident::new("match" , Span::call_site())), |
233 | TokenTree::Ident(Ident::new("v" , Span::call_site())), |
234 | TokenTree::Group(Group::new( |
235 | Delimiter::Brace, |
236 | TokenStream::from_iter(vec![ |
237 | TokenTree::Punct(Punct::new('_' , Spacing::Alone)), |
238 | TokenTree::Punct(Punct::new('=' , Spacing::Joint)), |
239 | TokenTree::Punct(Punct::new('>' , Spacing::Alone)), |
240 | TokenTree::Group(Group::new(Delimiter::None, quote! { #[a] () })), |
241 | ]), |
242 | )), |
243 | ]); |
244 | |
245 | snapshot!(tokens as Expr, @r###" |
246 | Expr::Match { |
247 | expr: Expr::Path { |
248 | path: Path { |
249 | segments: [ |
250 | PathSegment { |
251 | ident: "v", |
252 | arguments: None, |
253 | }, |
254 | ], |
255 | }, |
256 | }, |
257 | arms: [ |
258 | Arm { |
259 | pat: Pat::Wild, |
260 | body: Expr::Group { |
261 | expr: Expr::Tuple { |
262 | attrs: [ |
263 | Attribute { |
264 | style: Outer, |
265 | path: Path { |
266 | segments: [ |
267 | PathSegment { |
268 | ident: "a", |
269 | arguments: None, |
270 | }, |
271 | ], |
272 | }, |
273 | tokens: TokenStream(``), |
274 | }, |
275 | ], |
276 | }, |
277 | }, |
278 | }, |
279 | ], |
280 | } |
281 | "### ); |
282 | } |
283 | |
284 | // https://github.com/dtolnay/syn/issues/1019 |
285 | #[test] |
286 | fn test_closure_vs_rangefull() { |
287 | #[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/4808 |
288 | let tokens = quote!(|| .. .method()); |
289 | snapshot!(tokens as Expr, @r###" |
290 | Expr::MethodCall { |
291 | receiver: Expr::Closure { |
292 | output: Default, |
293 | body: Expr::Range { |
294 | limits: HalfOpen, |
295 | }, |
296 | }, |
297 | method: "method", |
298 | } |
299 | "### ); |
300 | } |
301 | |
302 | #[test] |
303 | fn test_postfix_operator_after_cast() { |
304 | syn::parse_str::<Expr>("|| &x as T[0]" ).unwrap_err(); |
305 | syn::parse_str::<Expr>("|| () as ()()" ).unwrap_err(); |
306 | } |
307 | |