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