1 #![allow(
2 clippy::elidable_lifetime_names,
3 clippy::needless_lifetimes,
4 clippy::uninlined_format_args
5 )]
6
7 #[macro_use]
8 mod macros;
9
10 use proc_macro2::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
11 use quote::quote;
12 use syn::parse::{Parse, ParseStream};
13 use syn::{DeriveInput, Result, Visibility};
14
15 #[derive(Debug)]
16 struct VisRest {
17 vis: Visibility,
18 rest: TokenStream,
19 }
20
21 impl Parse for VisRest {
parse(input: ParseStream) -> Result<Self>22 fn parse(input: ParseStream) -> Result<Self> {
23 Ok(VisRest {
24 vis: input.parse()?,
25 rest: input.parse()?,
26 })
27 }
28 }
29
30 macro_rules! assert_vis_parse {
31 ($input:expr, Ok($p:pat)) => {
32 assert_vis_parse!($input, Ok($p) + "");
33 };
34
35 ($input:expr, Ok($p:pat) + $rest:expr) => {
36 let expected = $rest.parse::<TokenStream>().unwrap();
37 let parse: VisRest = syn::parse_str($input).unwrap();
38
39 match parse.vis {
40 $p => {}
41 _ => panic!("expected {}, got {:?}", stringify!($p), parse.vis),
42 }
43
44 // NOTE: Round-trips through `to_string` to avoid potential whitespace
45 // diffs.
46 assert_eq!(parse.rest.to_string(), expected.to_string());
47 };
48
49 ($input:expr, Err) => {
50 syn::parse2::<VisRest>($input.parse().unwrap()).unwrap_err();
51 };
52 }
53
54 #[test]
test_pub()55 fn test_pub() {
56 assert_vis_parse!("pub", Ok(Visibility::Public(_)));
57 }
58
59 #[test]
test_inherited()60 fn test_inherited() {
61 assert_vis_parse!("", Ok(Visibility::Inherited));
62 }
63
64 #[test]
test_in()65 fn test_in() {
66 assert_vis_parse!("pub(in foo::bar)", Ok(Visibility::Restricted(_)));
67 }
68
69 #[test]
test_pub_crate()70 fn test_pub_crate() {
71 assert_vis_parse!("pub(crate)", Ok(Visibility::Restricted(_)));
72 }
73
74 #[test]
test_pub_self()75 fn test_pub_self() {
76 assert_vis_parse!("pub(self)", Ok(Visibility::Restricted(_)));
77 }
78
79 #[test]
test_pub_super()80 fn test_pub_super() {
81 assert_vis_parse!("pub(super)", Ok(Visibility::Restricted(_)));
82 }
83
84 #[test]
test_missing_in()85 fn test_missing_in() {
86 assert_vis_parse!("pub(foo::bar)", Ok(Visibility::Public(_)) + "(foo::bar)");
87 }
88
89 #[test]
test_missing_in_path()90 fn test_missing_in_path() {
91 assert_vis_parse!("pub(in)", Err);
92 }
93
94 #[test]
test_crate_path()95 fn test_crate_path() {
96 assert_vis_parse!(
97 "pub(crate::A, crate::B)",
98 Ok(Visibility::Public(_)) + "(crate::A, crate::B)"
99 );
100 }
101
102 #[test]
test_junk_after_in()103 fn test_junk_after_in() {
104 assert_vis_parse!("pub(in some::path @@garbage)", Err);
105 }
106
107 #[test]
test_inherited_vis_named_field()108 fn test_inherited_vis_named_field() {
109 // mimics `struct S { $vis $field: () }` where $vis is empty
110 let tokens = TokenStream::from_iter([
111 TokenTree::Ident(Ident::new("struct", Span::call_site())),
112 TokenTree::Ident(Ident::new("S", Span::call_site())),
113 TokenTree::Group(Group::new(
114 Delimiter::Brace,
115 TokenStream::from_iter([
116 TokenTree::Group(Group::new(Delimiter::None, TokenStream::new())),
117 TokenTree::Group(Group::new(Delimiter::None, quote!(f))),
118 TokenTree::Punct(Punct::new(':', Spacing::Alone)),
119 TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())),
120 ]),
121 )),
122 ]);
123
124 snapshot!(tokens as DeriveInput, @r#"
125 DeriveInput {
126 vis: Visibility::Inherited,
127 ident: "S",
128 generics: Generics,
129 data: Data::Struct {
130 fields: Fields::Named {
131 named: [
132 Field {
133 vis: Visibility::Inherited,
134 ident: Some("f"),
135 colon_token: Some,
136 ty: Type::Tuple,
137 },
138 ],
139 },
140 },
141 }
142 "#);
143 }
144
145 #[test]
test_inherited_vis_unnamed_field()146 fn test_inherited_vis_unnamed_field() {
147 // mimics `struct S($vis $ty);` where $vis is empty
148 let tokens = TokenStream::from_iter([
149 TokenTree::Ident(Ident::new("struct", Span::call_site())),
150 TokenTree::Ident(Ident::new("S", Span::call_site())),
151 TokenTree::Group(Group::new(
152 Delimiter::Parenthesis,
153 TokenStream::from_iter([
154 TokenTree::Group(Group::new(Delimiter::None, TokenStream::new())),
155 TokenTree::Group(Group::new(Delimiter::None, quote!(str))),
156 ]),
157 )),
158 TokenTree::Punct(Punct::new(';', Spacing::Alone)),
159 ]);
160
161 snapshot!(tokens as DeriveInput, @r#"
162 DeriveInput {
163 vis: Visibility::Inherited,
164 ident: "S",
165 generics: Generics,
166 data: Data::Struct {
167 fields: Fields::Unnamed {
168 unnamed: [
169 Field {
170 vis: Visibility::Inherited,
171 ty: Type::Group {
172 elem: Type::Path {
173 path: Path {
174 segments: [
175 PathSegment {
176 ident: "str",
177 },
178 ],
179 },
180 },
181 },
182 },
183 ],
184 },
185 semi_token: Some,
186 },
187 }
188 "#);
189 }
190