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