| #![allow(clippy::needless_lifetimes, clippy::uninlined_format_args)] |
| |
| #[macro_use] |
| mod macros; |
| |
| use proc_macro2::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree}; |
| use quote::quote; |
| use syn::parse::{Parse, ParseStream}; |
| use syn::{DeriveInput, Result, Visibility}; |
| |
| #[derive(Debug)] |
| struct VisRest { |
| vis: Visibility, |
| rest: TokenStream, |
| } |
| |
| impl Parse for VisRest { |
| fn parse(input: ParseStream) -> Result<Self> { |
| Ok(VisRest { |
| vis: input.parse()?, |
| rest: input.parse()?, |
| }) |
| } |
| } |
| |
| macro_rules! assert_vis_parse { |
| ($input:expr, Ok($p:pat)) => { |
| assert_vis_parse!($input, Ok($p) + ""); |
| }; |
| |
| ($input:expr, Ok($p:pat) + $rest:expr) => { |
| let expected = $rest.parse::<TokenStream>().unwrap(); |
| let parse: VisRest = syn::parse_str($input).unwrap(); |
| |
| match parse.vis { |
| $p => {} |
| _ => panic!("expected {}, got {:?}", stringify!($p), parse.vis), |
| } |
| |
| // NOTE: Round-trips through `to_string` to avoid potential whitespace |
| // diffs. |
| assert_eq!(parse.rest.to_string(), expected.to_string()); |
| }; |
| |
| ($input:expr, Err) => { |
| syn::parse2::<VisRest>($input.parse().unwrap()).unwrap_err(); |
| }; |
| } |
| |
| #[test] |
| fn test_pub() { |
| assert_vis_parse!("pub", Ok(Visibility::Public(_))); |
| } |
| |
| #[test] |
| fn test_inherited() { |
| assert_vis_parse!("", Ok(Visibility::Inherited)); |
| } |
| |
| #[test] |
| fn test_in() { |
| assert_vis_parse!("pub(in foo::bar)", Ok(Visibility::Restricted(_))); |
| } |
| |
| #[test] |
| fn test_pub_crate() { |
| assert_vis_parse!("pub(crate)", Ok(Visibility::Restricted(_))); |
| } |
| |
| #[test] |
| fn test_pub_self() { |
| assert_vis_parse!("pub(self)", Ok(Visibility::Restricted(_))); |
| } |
| |
| #[test] |
| fn test_pub_super() { |
| assert_vis_parse!("pub(super)", Ok(Visibility::Restricted(_))); |
| } |
| |
| #[test] |
| fn test_missing_in() { |
| assert_vis_parse!("pub(foo::bar)", Ok(Visibility::Public(_)) + "(foo::bar)"); |
| } |
| |
| #[test] |
| fn test_missing_in_path() { |
| assert_vis_parse!("pub(in)", Err); |
| } |
| |
| #[test] |
| fn test_crate_path() { |
| assert_vis_parse!( |
| "pub(crate::A, crate::B)", |
| Ok(Visibility::Public(_)) + "(crate::A, crate::B)" |
| ); |
| } |
| |
| #[test] |
| fn test_junk_after_in() { |
| assert_vis_parse!("pub(in some::path @@garbage)", Err); |
| } |
| |
| #[test] |
| fn test_inherited_vis_named_field() { |
| // mimics `struct S { $vis $field: () }` where $vis is empty |
| let tokens = TokenStream::from_iter([ |
| TokenTree::Ident(Ident::new("struct", Span::call_site())), |
| TokenTree::Ident(Ident::new("S", Span::call_site())), |
| TokenTree::Group(Group::new( |
| Delimiter::Brace, |
| TokenStream::from_iter([ |
| TokenTree::Group(Group::new(Delimiter::None, TokenStream::new())), |
| TokenTree::Group(Group::new(Delimiter::None, quote!(f))), |
| TokenTree::Punct(Punct::new(':', Spacing::Alone)), |
| TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())), |
| ]), |
| )), |
| ]); |
| |
| snapshot!(tokens as DeriveInput, @r#" |
| DeriveInput { |
| vis: Visibility::Inherited, |
| ident: "S", |
| generics: Generics, |
| data: Data::Struct { |
| fields: Fields::Named { |
| named: [ |
| Field { |
| vis: Visibility::Inherited, |
| ident: Some("f"), |
| colon_token: Some, |
| ty: Type::Tuple, |
| }, |
| ], |
| }, |
| }, |
| } |
| "#); |
| } |
| |
| #[test] |
| fn test_inherited_vis_unnamed_field() { |
| // mimics `struct S($vis $ty);` where $vis is empty |
| let tokens = TokenStream::from_iter([ |
| TokenTree::Ident(Ident::new("struct", Span::call_site())), |
| TokenTree::Ident(Ident::new("S", Span::call_site())), |
| TokenTree::Group(Group::new( |
| Delimiter::Parenthesis, |
| TokenStream::from_iter([ |
| TokenTree::Group(Group::new(Delimiter::None, TokenStream::new())), |
| TokenTree::Group(Group::new(Delimiter::None, quote!(str))), |
| ]), |
| )), |
| TokenTree::Punct(Punct::new(';', Spacing::Alone)), |
| ]); |
| |
| snapshot!(tokens as DeriveInput, @r#" |
| DeriveInput { |
| vis: Visibility::Inherited, |
| ident: "S", |
| generics: Generics, |
| data: Data::Struct { |
| fields: Fields::Unnamed { |
| unnamed: [ |
| Field { |
| vis: Visibility::Inherited, |
| ty: Type::Group { |
| elem: Type::Path { |
| path: Path { |
| segments: [ |
| PathSegment { |
| ident: "str", |
| }, |
| ], |
| }, |
| }, |
| }, |
| }, |
| ], |
| }, |
| semi_token: Some, |
| }, |
| } |
| "#); |
| } |