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