1 | #![allow ( |
2 | clippy::assertions_on_result_states, |
3 | clippy::manual_let_else, |
4 | clippy::too_many_lines, |
5 | clippy::uninlined_format_args |
6 | )] |
7 | |
8 | #[macro_use ] |
9 | mod macros; |
10 | |
11 | use quote::quote; |
12 | use syn::{Data, DeriveInput}; |
13 | |
14 | #[test] |
15 | fn test_unit() { |
16 | let input = quote! { |
17 | struct Unit; |
18 | }; |
19 | |
20 | snapshot!(input as DeriveInput, @r###" |
21 | DeriveInput { |
22 | vis: Visibility::Inherited, |
23 | ident: "Unit", |
24 | generics: Generics, |
25 | data: Data::Struct { |
26 | fields: Fields::Unit, |
27 | semi_token: Some, |
28 | }, |
29 | } |
30 | "### ); |
31 | } |
32 | |
33 | #[test] |
34 | fn test_struct() { |
35 | let input = quote! { |
36 | #[derive(Debug, Clone)] |
37 | pub struct Item { |
38 | pub ident: Ident, |
39 | pub attrs: Vec<Attribute> |
40 | } |
41 | }; |
42 | |
43 | snapshot!(input as DeriveInput, @r###" |
44 | DeriveInput { |
45 | attrs: [ |
46 | Attribute { |
47 | style: AttrStyle::Outer, |
48 | meta: Meta::List { |
49 | path: Path { |
50 | segments: [ |
51 | PathSegment { |
52 | ident: "derive", |
53 | }, |
54 | ], |
55 | }, |
56 | delimiter: MacroDelimiter::Paren, |
57 | tokens: TokenStream(`Debug , Clone`), |
58 | }, |
59 | }, |
60 | ], |
61 | vis: Visibility::Public, |
62 | ident: "Item", |
63 | generics: Generics, |
64 | data: Data::Struct { |
65 | fields: Fields::Named { |
66 | named: [ |
67 | Field { |
68 | vis: Visibility::Public, |
69 | ident: Some("ident"), |
70 | colon_token: Some, |
71 | ty: Type::Path { |
72 | path: Path { |
73 | segments: [ |
74 | PathSegment { |
75 | ident: "Ident", |
76 | }, |
77 | ], |
78 | }, |
79 | }, |
80 | }, |
81 | Field { |
82 | vis: Visibility::Public, |
83 | ident: Some("attrs"), |
84 | colon_token: Some, |
85 | ty: Type::Path { |
86 | path: Path { |
87 | segments: [ |
88 | PathSegment { |
89 | ident: "Vec", |
90 | arguments: PathArguments::AngleBracketed { |
91 | args: [ |
92 | GenericArgument::Type(Type::Path { |
93 | path: Path { |
94 | segments: [ |
95 | PathSegment { |
96 | ident: "Attribute", |
97 | }, |
98 | ], |
99 | }, |
100 | }), |
101 | ], |
102 | }, |
103 | }, |
104 | ], |
105 | }, |
106 | }, |
107 | }, |
108 | ], |
109 | }, |
110 | }, |
111 | } |
112 | "### ); |
113 | |
114 | snapshot!(&input.attrs[0].meta, @r###" |
115 | Meta::List { |
116 | path: Path { |
117 | segments: [ |
118 | PathSegment { |
119 | ident: "derive", |
120 | }, |
121 | ], |
122 | }, |
123 | delimiter: MacroDelimiter::Paren, |
124 | tokens: TokenStream(`Debug , Clone`), |
125 | } |
126 | "### ); |
127 | } |
128 | |
129 | #[test] |
130 | fn test_union() { |
131 | let input = quote! { |
132 | union MaybeUninit<T> { |
133 | uninit: (), |
134 | value: T |
135 | } |
136 | }; |
137 | |
138 | snapshot!(input as DeriveInput, @r###" |
139 | DeriveInput { |
140 | vis: Visibility::Inherited, |
141 | ident: "MaybeUninit", |
142 | generics: Generics { |
143 | lt_token: Some, |
144 | params: [ |
145 | GenericParam::Type(TypeParam { |
146 | ident: "T", |
147 | }), |
148 | ], |
149 | gt_token: Some, |
150 | }, |
151 | data: Data::Union { |
152 | fields: FieldsNamed { |
153 | named: [ |
154 | Field { |
155 | vis: Visibility::Inherited, |
156 | ident: Some("uninit"), |
157 | colon_token: Some, |
158 | ty: Type::Tuple, |
159 | }, |
160 | Field { |
161 | vis: Visibility::Inherited, |
162 | ident: Some("value"), |
163 | colon_token: Some, |
164 | ty: Type::Path { |
165 | path: Path { |
166 | segments: [ |
167 | PathSegment { |
168 | ident: "T", |
169 | }, |
170 | ], |
171 | }, |
172 | }, |
173 | }, |
174 | ], |
175 | }, |
176 | }, |
177 | } |
178 | "### ); |
179 | } |
180 | |
181 | #[test] |
182 | #[cfg (feature = "full" )] |
183 | fn test_enum() { |
184 | let input = quote! { |
185 | /// See the std::result module documentation for details. |
186 | #[must_use] |
187 | pub enum Result<T, E> { |
188 | Ok(T), |
189 | Err(E), |
190 | Surprise = 0isize, |
191 | |
192 | // Smuggling data into a proc_macro_derive, |
193 | // in the style of https://github.com/dtolnay/proc-macro-hack |
194 | ProcMacroHack = (0, "data" ).0 |
195 | } |
196 | }; |
197 | |
198 | snapshot!(input as DeriveInput, @r###" |
199 | DeriveInput { |
200 | attrs: [ |
201 | Attribute { |
202 | style: AttrStyle::Outer, |
203 | meta: Meta::NameValue { |
204 | path: Path { |
205 | segments: [ |
206 | PathSegment { |
207 | ident: "doc", |
208 | }, |
209 | ], |
210 | }, |
211 | value: Expr::Lit { |
212 | lit: " See the std::result module documentation for details.", |
213 | }, |
214 | }, |
215 | }, |
216 | Attribute { |
217 | style: AttrStyle::Outer, |
218 | meta: Meta::Path { |
219 | segments: [ |
220 | PathSegment { |
221 | ident: "must_use", |
222 | }, |
223 | ], |
224 | }, |
225 | }, |
226 | ], |
227 | vis: Visibility::Public, |
228 | ident: "Result", |
229 | generics: Generics { |
230 | lt_token: Some, |
231 | params: [ |
232 | GenericParam::Type(TypeParam { |
233 | ident: "T", |
234 | }), |
235 | GenericParam::Type(TypeParam { |
236 | ident: "E", |
237 | }), |
238 | ], |
239 | gt_token: Some, |
240 | }, |
241 | data: Data::Enum { |
242 | variants: [ |
243 | Variant { |
244 | ident: "Ok", |
245 | fields: Fields::Unnamed { |
246 | unnamed: [ |
247 | Field { |
248 | vis: Visibility::Inherited, |
249 | ty: Type::Path { |
250 | path: Path { |
251 | segments: [ |
252 | PathSegment { |
253 | ident: "T", |
254 | }, |
255 | ], |
256 | }, |
257 | }, |
258 | }, |
259 | ], |
260 | }, |
261 | }, |
262 | Variant { |
263 | ident: "Err", |
264 | fields: Fields::Unnamed { |
265 | unnamed: [ |
266 | Field { |
267 | vis: Visibility::Inherited, |
268 | ty: Type::Path { |
269 | path: Path { |
270 | segments: [ |
271 | PathSegment { |
272 | ident: "E", |
273 | }, |
274 | ], |
275 | }, |
276 | }, |
277 | }, |
278 | ], |
279 | }, |
280 | }, |
281 | Variant { |
282 | ident: "Surprise", |
283 | fields: Fields::Unit, |
284 | discriminant: Some(Expr::Lit { |
285 | lit: 0isize, |
286 | }), |
287 | }, |
288 | Variant { |
289 | ident: "ProcMacroHack", |
290 | fields: Fields::Unit, |
291 | discriminant: Some(Expr::Field { |
292 | base: Expr::Tuple { |
293 | elems: [ |
294 | Expr::Lit { |
295 | lit: 0, |
296 | }, |
297 | Expr::Lit { |
298 | lit: "data", |
299 | }, |
300 | ], |
301 | }, |
302 | member: Member::Unnamed(Index { |
303 | index: 0, |
304 | }), |
305 | }), |
306 | }, |
307 | ], |
308 | }, |
309 | } |
310 | "### ); |
311 | |
312 | let meta_items: Vec<_> = input.attrs.into_iter().map(|attr| attr.meta).collect(); |
313 | |
314 | snapshot!(meta_items, @r###" |
315 | [ |
316 | Meta::NameValue { |
317 | path: Path { |
318 | segments: [ |
319 | PathSegment { |
320 | ident: "doc", |
321 | }, |
322 | ], |
323 | }, |
324 | value: Expr::Lit { |
325 | lit: " See the std::result module documentation for details.", |
326 | }, |
327 | }, |
328 | Meta::Path { |
329 | segments: [ |
330 | PathSegment { |
331 | ident: "must_use", |
332 | }, |
333 | ], |
334 | }, |
335 | ] |
336 | "### ); |
337 | } |
338 | |
339 | #[test] |
340 | fn test_attr_with_non_mod_style_path() { |
341 | let input = quote! { |
342 | #[inert <T>] |
343 | struct S; |
344 | }; |
345 | |
346 | syn::parse2::<DeriveInput>(input).unwrap_err(); |
347 | } |
348 | |
349 | #[test] |
350 | fn test_attr_with_mod_style_path_with_self() { |
351 | let input = quote! { |
352 | #[foo::self] |
353 | struct S; |
354 | }; |
355 | |
356 | snapshot!(input as DeriveInput, @r###" |
357 | DeriveInput { |
358 | attrs: [ |
359 | Attribute { |
360 | style: AttrStyle::Outer, |
361 | meta: Meta::Path { |
362 | segments: [ |
363 | PathSegment { |
364 | ident: "foo", |
365 | }, |
366 | PathSegment { |
367 | ident: "self", |
368 | }, |
369 | ], |
370 | }, |
371 | }, |
372 | ], |
373 | vis: Visibility::Inherited, |
374 | ident: "S", |
375 | generics: Generics, |
376 | data: Data::Struct { |
377 | fields: Fields::Unit, |
378 | semi_token: Some, |
379 | }, |
380 | } |
381 | "### ); |
382 | |
383 | snapshot!(&input.attrs[0].meta, @r###" |
384 | Meta::Path { |
385 | segments: [ |
386 | PathSegment { |
387 | ident: "foo", |
388 | }, |
389 | PathSegment { |
390 | ident: "self", |
391 | }, |
392 | ], |
393 | } |
394 | "### ); |
395 | } |
396 | |
397 | #[test] |
398 | fn test_pub_restricted() { |
399 | // Taken from tests/rust/src/test/ui/resolve/auxiliary/privacy-struct-ctor.rs |
400 | let input = quote! { |
401 | pub(in m) struct Z(pub(in m::n) u8); |
402 | }; |
403 | |
404 | snapshot!(input as DeriveInput, @r###" |
405 | DeriveInput { |
406 | vis: Visibility::Restricted { |
407 | in_token: Some, |
408 | path: Path { |
409 | segments: [ |
410 | PathSegment { |
411 | ident: "m", |
412 | }, |
413 | ], |
414 | }, |
415 | }, |
416 | ident: "Z", |
417 | generics: Generics, |
418 | data: Data::Struct { |
419 | fields: Fields::Unnamed { |
420 | unnamed: [ |
421 | Field { |
422 | vis: Visibility::Restricted { |
423 | in_token: Some, |
424 | path: Path { |
425 | segments: [ |
426 | PathSegment { |
427 | ident: "m", |
428 | }, |
429 | PathSegment { |
430 | ident: "n", |
431 | }, |
432 | ], |
433 | }, |
434 | }, |
435 | ty: Type::Path { |
436 | path: Path { |
437 | segments: [ |
438 | PathSegment { |
439 | ident: "u8", |
440 | }, |
441 | ], |
442 | }, |
443 | }, |
444 | }, |
445 | ], |
446 | }, |
447 | semi_token: Some, |
448 | }, |
449 | } |
450 | "### ); |
451 | } |
452 | |
453 | #[test] |
454 | fn test_pub_restricted_crate() { |
455 | let input = quote! { |
456 | pub(crate) struct S; |
457 | }; |
458 | |
459 | snapshot!(input as DeriveInput, @r###" |
460 | DeriveInput { |
461 | vis: Visibility::Restricted { |
462 | path: Path { |
463 | segments: [ |
464 | PathSegment { |
465 | ident: "crate", |
466 | }, |
467 | ], |
468 | }, |
469 | }, |
470 | ident: "S", |
471 | generics: Generics, |
472 | data: Data::Struct { |
473 | fields: Fields::Unit, |
474 | semi_token: Some, |
475 | }, |
476 | } |
477 | "### ); |
478 | } |
479 | |
480 | #[test] |
481 | fn test_pub_restricted_super() { |
482 | let input = quote! { |
483 | pub(super) struct S; |
484 | }; |
485 | |
486 | snapshot!(input as DeriveInput, @r###" |
487 | DeriveInput { |
488 | vis: Visibility::Restricted { |
489 | path: Path { |
490 | segments: [ |
491 | PathSegment { |
492 | ident: "super", |
493 | }, |
494 | ], |
495 | }, |
496 | }, |
497 | ident: "S", |
498 | generics: Generics, |
499 | data: Data::Struct { |
500 | fields: Fields::Unit, |
501 | semi_token: Some, |
502 | }, |
503 | } |
504 | "### ); |
505 | } |
506 | |
507 | #[test] |
508 | fn test_pub_restricted_in_super() { |
509 | let input = quote! { |
510 | pub(in super) struct S; |
511 | }; |
512 | |
513 | snapshot!(input as DeriveInput, @r###" |
514 | DeriveInput { |
515 | vis: Visibility::Restricted { |
516 | in_token: Some, |
517 | path: Path { |
518 | segments: [ |
519 | PathSegment { |
520 | ident: "super", |
521 | }, |
522 | ], |
523 | }, |
524 | }, |
525 | ident: "S", |
526 | generics: Generics, |
527 | data: Data::Struct { |
528 | fields: Fields::Unit, |
529 | semi_token: Some, |
530 | }, |
531 | } |
532 | "### ); |
533 | } |
534 | |
535 | #[test] |
536 | fn test_fields_on_unit_struct() { |
537 | let input = quote! { |
538 | struct S; |
539 | }; |
540 | |
541 | snapshot!(input as DeriveInput, @r###" |
542 | DeriveInput { |
543 | vis: Visibility::Inherited, |
544 | ident: "S", |
545 | generics: Generics, |
546 | data: Data::Struct { |
547 | fields: Fields::Unit, |
548 | semi_token: Some, |
549 | }, |
550 | } |
551 | "### ); |
552 | |
553 | let data = match input.data { |
554 | Data::Struct(data) => data, |
555 | _ => panic!("expected a struct" ), |
556 | }; |
557 | |
558 | assert_eq!(0, data.fields.iter().count()); |
559 | } |
560 | |
561 | #[test] |
562 | fn test_fields_on_named_struct() { |
563 | let input = quote! { |
564 | struct S { |
565 | foo: i32, |
566 | pub bar: String, |
567 | } |
568 | }; |
569 | |
570 | snapshot!(input as DeriveInput, @r###" |
571 | DeriveInput { |
572 | vis: Visibility::Inherited, |
573 | ident: "S", |
574 | generics: Generics, |
575 | data: Data::Struct { |
576 | fields: Fields::Named { |
577 | named: [ |
578 | Field { |
579 | vis: Visibility::Inherited, |
580 | ident: Some("foo"), |
581 | colon_token: Some, |
582 | ty: Type::Path { |
583 | path: Path { |
584 | segments: [ |
585 | PathSegment { |
586 | ident: "i32", |
587 | }, |
588 | ], |
589 | }, |
590 | }, |
591 | }, |
592 | Field { |
593 | vis: Visibility::Public, |
594 | ident: Some("bar"), |
595 | colon_token: Some, |
596 | ty: Type::Path { |
597 | path: Path { |
598 | segments: [ |
599 | PathSegment { |
600 | ident: "String", |
601 | }, |
602 | ], |
603 | }, |
604 | }, |
605 | }, |
606 | ], |
607 | }, |
608 | }, |
609 | } |
610 | "### ); |
611 | |
612 | let data = match input.data { |
613 | Data::Struct(data) => data, |
614 | _ => panic!("expected a struct" ), |
615 | }; |
616 | |
617 | snapshot!(data.fields.into_iter().collect::<Vec<_>>(), @r###" |
618 | [ |
619 | Field { |
620 | vis: Visibility::Inherited, |
621 | ident: Some("foo"), |
622 | colon_token: Some, |
623 | ty: Type::Path { |
624 | path: Path { |
625 | segments: [ |
626 | PathSegment { |
627 | ident: "i32", |
628 | }, |
629 | ], |
630 | }, |
631 | }, |
632 | }, |
633 | Field { |
634 | vis: Visibility::Public, |
635 | ident: Some("bar"), |
636 | colon_token: Some, |
637 | ty: Type::Path { |
638 | path: Path { |
639 | segments: [ |
640 | PathSegment { |
641 | ident: "String", |
642 | }, |
643 | ], |
644 | }, |
645 | }, |
646 | }, |
647 | ] |
648 | "### ); |
649 | } |
650 | |
651 | #[test] |
652 | fn test_fields_on_tuple_struct() { |
653 | let input = quote! { |
654 | struct S(i32, pub String); |
655 | }; |
656 | |
657 | snapshot!(input as DeriveInput, @r###" |
658 | DeriveInput { |
659 | vis: Visibility::Inherited, |
660 | ident: "S", |
661 | generics: Generics, |
662 | data: Data::Struct { |
663 | fields: Fields::Unnamed { |
664 | unnamed: [ |
665 | Field { |
666 | vis: Visibility::Inherited, |
667 | ty: Type::Path { |
668 | path: Path { |
669 | segments: [ |
670 | PathSegment { |
671 | ident: "i32", |
672 | }, |
673 | ], |
674 | }, |
675 | }, |
676 | }, |
677 | Field { |
678 | vis: Visibility::Public, |
679 | ty: Type::Path { |
680 | path: Path { |
681 | segments: [ |
682 | PathSegment { |
683 | ident: "String", |
684 | }, |
685 | ], |
686 | }, |
687 | }, |
688 | }, |
689 | ], |
690 | }, |
691 | semi_token: Some, |
692 | }, |
693 | } |
694 | "### ); |
695 | |
696 | let data = match input.data { |
697 | Data::Struct(data) => data, |
698 | _ => panic!("expected a struct" ), |
699 | }; |
700 | |
701 | snapshot!(data.fields.iter().collect::<Vec<_>>(), @r###" |
702 | [ |
703 | Field { |
704 | vis: Visibility::Inherited, |
705 | ty: Type::Path { |
706 | path: Path { |
707 | segments: [ |
708 | PathSegment { |
709 | ident: "i32", |
710 | }, |
711 | ], |
712 | }, |
713 | }, |
714 | }, |
715 | Field { |
716 | vis: Visibility::Public, |
717 | ty: Type::Path { |
718 | path: Path { |
719 | segments: [ |
720 | PathSegment { |
721 | ident: "String", |
722 | }, |
723 | ], |
724 | }, |
725 | }, |
726 | }, |
727 | ] |
728 | "### ); |
729 | } |
730 | |
731 | #[test] |
732 | fn test_ambiguous_crate() { |
733 | let input = quote! { |
734 | // The field type is `(crate::X)` not `crate (::X)`. |
735 | struct S(crate::X); |
736 | }; |
737 | |
738 | snapshot!(input as DeriveInput, @r###" |
739 | DeriveInput { |
740 | vis: Visibility::Inherited, |
741 | ident: "S", |
742 | generics: Generics, |
743 | data: Data::Struct { |
744 | fields: Fields::Unnamed { |
745 | unnamed: [ |
746 | Field { |
747 | vis: Visibility::Inherited, |
748 | ty: Type::Path { |
749 | path: Path { |
750 | segments: [ |
751 | PathSegment { |
752 | ident: "crate", |
753 | }, |
754 | PathSegment { |
755 | ident: "X", |
756 | }, |
757 | ], |
758 | }, |
759 | }, |
760 | }, |
761 | ], |
762 | }, |
763 | semi_token: Some, |
764 | }, |
765 | } |
766 | "### ); |
767 | } |
768 | |