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