#![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 { 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::().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::($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, }, } "#); }