diff options
Diffstat (limited to 'syn-mid/src')
-rw-r--r-- | syn-mid/src/arg.rs | 99 | ||||
-rw-r--r-- | syn-mid/src/lib.rs | 190 | ||||
-rw-r--r-- | syn-mid/src/macros.rs | 107 | ||||
-rw-r--r-- | syn-mid/src/pat.rs | 413 | ||||
-rw-r--r-- | syn-mid/src/path.rs | 50 |
5 files changed, 859 insertions, 0 deletions
diff --git a/syn-mid/src/arg.rs b/syn-mid/src/arg.rs new file mode 100644 index 0000000..593a1ac --- /dev/null +++ b/syn-mid/src/arg.rs @@ -0,0 +1,99 @@ +use syn::{Attribute, Lifetime, Token}; + +use super::PatType; + +ast_enum_of_structs! { + /// An argument in a function signature: the `n: usize` in `fn f(n: usize)`. + pub enum FnArg { + /// The `self` argument of an associated method, whether taken by value + /// or by reference. + Receiver(Receiver), + + /// A function argument accepted by pattern and type. + Typed(PatType), + } +} + +ast_struct! { + /// The `self` argument of an associated method, whether taken by value + /// or by reference. + pub struct Receiver { + pub attrs: Vec<Attribute>, + pub reference: Option<(Token![&], Option<Lifetime>)>, + pub mutability: Option<Token![mut]>, + pub self_token: Token![self], + } +} + +mod parsing { + use syn::{ + parse::{discouraged::Speculative, Parse, ParseStream, Result}, + Attribute, Token, + }; + + use super::{FnArg, PatType, Receiver}; + + impl Parse for FnArg { + fn parse(input: ParseStream<'_>) -> Result<Self> { + let attrs = input.call(Attribute::parse_outer)?; + + let ahead = input.fork(); + if let Ok(mut receiver) = ahead.parse::<Receiver>() { + if !ahead.peek(Token![:]) { + input.advance_to(&ahead); + receiver.attrs = attrs; + return Ok(FnArg::Receiver(receiver)); + } + } + + let mut typed = input.call(fn_arg_typed)?; + typed.attrs = attrs; + Ok(FnArg::Typed(typed)) + } + } + + impl Parse for Receiver { + fn parse(input: ParseStream<'_>) -> Result<Self> { + Ok(Self { + attrs: Vec::new(), + reference: { + if input.peek(Token![&]) { + Some((input.parse()?, input.parse()?)) + } else { + None + } + }, + mutability: input.parse()?, + self_token: input.parse()?, + }) + } + } + + fn fn_arg_typed(input: ParseStream<'_>) -> Result<PatType> { + Ok(PatType { + attrs: Vec::new(), + pat: input.parse()?, + colon_token: input.parse()?, + ty: Box::new(input.parse()?), + }) + } +} + +mod printing { + use proc_macro2::TokenStream; + use quote::{ToTokens, TokenStreamExt}; + + use super::Receiver; + + impl ToTokens for Receiver { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(&self.attrs); + if let Some((ampersand, lifetime)) = &self.reference { + ampersand.to_tokens(tokens); + lifetime.to_tokens(tokens); + } + self.mutability.to_tokens(tokens); + self.self_token.to_tokens(tokens); + } + } +} diff --git a/syn-mid/src/lib.rs b/syn-mid/src/lib.rs new file mode 100644 index 0000000..69bdec9 --- /dev/null +++ b/syn-mid/src/lib.rs @@ -0,0 +1,190 @@ +//! Providing the features between "full" and "derive" of syn. +//! +//! This crate provides the following two unique data structures. +//! +//! * [`syn_mid::ItemFn`] -- A function whose body is not parsed. +//! +//! ```text +//! fn process(n: usize) -> Result<()> { ... } +//! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ ^ +//! ``` +//! +//! * [`syn_mid::Block`] -- A block whose body is not parsed. +//! +//! ```text +//! { ... } +//! ^ ^ +//! ``` +//! +//! Other data structures are the same as data structures of [syn]. These are defined in this crate +//! because they cannot be used in [syn] without "full" feature. +//! +//! ## Optional features +//! +//! syn-mid in the default features aims to provide the features between "full" +//! and "derive" of [syn]. +//! +//! * **`clone-impls`** — Clone impls for all syntax tree types. +//! +//! [`syn_mid::ItemFn`]: struct.ItemFn.html +//! [`syn_mid::Block`]: struct.Block.html +//! [syn]: https://github.com/dtolnay/syn +//! + +#![doc(html_root_url = "https://docs.rs/syn-mid/0.4.0")] +#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))] +#![warn(unsafe_code)] +#![warn(rust_2018_idioms, unreachable_pub)] +#![warn(single_use_lifetimes)] +#![warn(clippy::all, clippy::pedantic)] +#![allow( + clippy::eval_order_dependence, + clippy::large_enum_variant, + clippy::module_name_repetitions, + clippy::use_self +)] + +// Many of the code contained in this crate are copies from https://github.com/dtolnay/syn. + +#[macro_use] +mod macros; + +mod arg; +mod pat; +mod path; + +pub use self::arg::*; +pub use self::pat::*; + +use proc_macro2::TokenStream; +use syn::{ + punctuated::Punctuated, token, Abi, Attribute, Generics, Ident, ReturnType, Token, Visibility, +}; + +ast_struct! { + /// A braced block containing Rust statements. + pub struct Block { + pub brace_token: token::Brace, + /// Statements in a block + pub stmts: TokenStream, + } +} + +ast_struct! { + /// A free-standing function: `fn process(n: usize) -> Result<()> { ... + /// }`. + pub struct ItemFn { + pub attrs: Vec<Attribute>, + pub vis: Visibility, + pub constness: Option<Token![const]>, + pub asyncness: Option<Token![async]>, + pub unsafety: Option<Token![unsafe]>, + pub abi: Option<Abi>, + pub fn_token: Token![fn], + pub ident: Ident, + pub generics: Generics, + pub paren_token: token::Paren, + pub inputs: Punctuated<FnArg, Token![,]>, + pub output: ReturnType, + pub block: Block, + } +} + +mod parsing { + use syn::{ + braced, parenthesized, + parse::{Parse, ParseStream, Result}, + Abi, Attribute, Generics, Ident, ReturnType, Token, Visibility, WhereClause, + }; + + use super::{Block, FnArg, ItemFn}; + + impl Parse for Block { + fn parse(input: ParseStream<'_>) -> Result<Self> { + let content; + Ok(Self { + brace_token: braced!(content in input), + stmts: content.parse()?, + }) + } + } + + impl Parse for ItemFn { + fn parse(input: ParseStream<'_>) -> Result<Self> { + let attrs = input.call(Attribute::parse_outer)?; + let vis: Visibility = input.parse()?; + let constness: Option<Token![const]> = input.parse()?; + let asyncness: Option<Token![async]> = input.parse()?; + let unsafety: Option<Token![unsafe]> = input.parse()?; + let abi: Option<Abi> = input.parse()?; + let fn_token: Token![fn] = input.parse()?; + let ident: Ident = input.parse()?; + let generics: Generics = input.parse()?; + + let content; + let paren_token = parenthesized!(content in input); + let inputs = content.parse_terminated(FnArg::parse)?; + + let output: ReturnType = input.parse()?; + let where_clause: Option<WhereClause> = input.parse()?; + + let block = input.parse()?; + + Ok(Self { + attrs, + vis, + constness, + asyncness, + unsafety, + abi, + fn_token, + ident, + generics: Generics { + where_clause, + ..generics + }, + paren_token, + inputs, + output, + block, + }) + } + } +} + +mod printing { + use proc_macro2::TokenStream; + use quote::{ToTokens, TokenStreamExt}; + + use super::{Block, ItemFn}; + + impl ToTokens for Block { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.brace_token.surround(tokens, |tokens| { + tokens.append_all(self.stmts.clone()); + }); + } + } + + impl ToTokens for ItemFn { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(&self.attrs); + self.vis.to_tokens(tokens); + self.constness.to_tokens(tokens); + self.asyncness.to_tokens(tokens); + self.unsafety.to_tokens(tokens); + self.abi.to_tokens(tokens); + self.fn_token.to_tokens(tokens); + self.ident.to_tokens(tokens); + self.generics.to_tokens(tokens); + self.paren_token.surround(tokens, |tokens| { + self.inputs.to_tokens(tokens); + }); + self.output.to_tokens(tokens); + self.generics.where_clause.to_tokens(tokens); + self.block.brace_token.surround(tokens, |tokens| { + tokens.append_all(self.block.stmts.clone()); + }); + } + } +} diff --git a/syn-mid/src/macros.rs b/syn-mid/src/macros.rs new file mode 100644 index 0000000..87be7b4 --- /dev/null +++ b/syn-mid/src/macros.rs @@ -0,0 +1,107 @@ +macro_rules! ast_struct { + ( + [$($attrs_pub:tt)*] + struct $name:ident $($rest:tt)* + ) => { + #[cfg_attr(feature = "clone-impls", derive(Clone))] + $($attrs_pub)* struct $name $($rest)* + }; + + ($($t:tt)*) => { + strip_attrs_pub!(ast_struct!($($t)*)); + }; +} + +macro_rules! ast_enum { + ( + [$($attrs_pub:tt)*] + enum $name:ident $($rest:tt)* + ) => ( + #[cfg_attr(feature = "clone-impls", derive(Clone))] + $($attrs_pub)* enum $name $($rest)* + ); + + ($($t:tt)*) => { + strip_attrs_pub!(ast_enum!($($t)*)); + }; +} + +macro_rules! ast_enum_of_structs { + ( + $(#[$enum_attr:meta])* + $pub:ident $enum:ident $name:ident $body:tt + ) => { + ast_enum!($(#[$enum_attr])* $pub $enum $name $body); + ast_enum_of_structs_impl!($pub $enum $name $body); + }; +} + +macro_rules! ast_enum_of_structs_impl { + ( + $pub:ident $enum:ident $name:ident { + $( + $(#[$variant_attr:meta])* + $variant:ident $( ($member:ident) )*, + )* + } + ) => { + check_keyword_matches!(pub $pub); + check_keyword_matches!(enum $enum); + + $( + $( + impl From<$member> for $name { + fn from(e: $member) -> $name { + $name::$variant(e) + } + } + )* + )* + + generate_to_tokens! { + () + tokens + $name { $($variant $($member)*,)* } + } + }; +} + +macro_rules! generate_to_tokens { + (($($arms:tt)*) $tokens:ident $name:ident { $variant:ident, $($next:tt)*}) => { + generate_to_tokens!( + ($($arms)* $name::$variant => {}) + $tokens $name { $($next)* } + ); + }; + + (($($arms:tt)*) $tokens:ident $name:ident { $variant:ident $member:ident, $($next:tt)*}) => { + generate_to_tokens!( + ($($arms)* $name::$variant(_e) => quote::ToTokens::to_tokens(_e, $tokens),) + $tokens $name { $($next)* } + ); + }; + + (($($arms:tt)*) $tokens:ident $name:ident {}) => { + impl quote::ToTokens for $name { + fn to_tokens(&self, $tokens: &mut proc_macro2::TokenStream) { + match self { + $($arms)* + } + } + } + }; +} + +macro_rules! strip_attrs_pub { + ($mac:ident!($(#[$m:meta])* $pub:ident $($t:tt)*)) => { + check_keyword_matches!(pub $pub); + + $mac!([$(#[$m])* $pub] $($t)*); + }; +} + +macro_rules! check_keyword_matches { + (struct struct) => {}; + (enum enum) => {}; + (pub pub) => {}; +} diff --git a/syn-mid/src/pat.rs b/syn-mid/src/pat.rs new file mode 100644 index 0000000..8f95381 --- /dev/null +++ b/syn-mid/src/pat.rs @@ -0,0 +1,413 @@ +use syn::{punctuated::Punctuated, token, Attribute, Ident, Member, Path, Token, Type}; + +ast_enum_of_structs! { + /// A pattern in a local binding, function signature, match expression, or + /// various other places. + /// + /// # Syntax tree enum + /// + /// This type is a [syntax tree enum]. + /// + /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums + pub enum Pat { + /// A pattern that binds a new variable: `ref mut binding @ SUBPATTERN`. + Ident(PatIdent), + + /// A path pattern like `Color::Red`. + Path(PatPath), + + /// A reference pattern: `&mut var`. + Reference(PatReference), + + /// A struct or struct variant pattern: `Variant { x, y, .. }`. + Struct(PatStruct), + + /// A tuple pattern: `(a, b)`. + Tuple(PatTuple), + + /// A tuple struct or tuple variant pattern: `Variant(x, y, .., z)`. + TupleStruct(PatTupleStruct), + + /// A type ascription pattern: `foo: f64`. + Type(PatType), + + /// A pattern that matches any value: `_`. + Wild(PatWild), + + #[doc(hidden)] + __Nonexhaustive, + } +} + +ast_struct! { + /// A pattern that binds a new variable: `ref mut binding @ SUBPATTERN`. + pub struct PatIdent { + pub attrs: Vec<Attribute>, + pub by_ref: Option<Token![ref]>, + pub mutability: Option<Token![mut]>, + pub ident: Ident, + } +} + +ast_struct! { + /// A path pattern like `Color::Red`. + pub struct PatPath { + pub attrs: Vec<Attribute>, + pub path: Path, + } +} + +ast_struct! { + /// A reference pattern: `&mut var`. + pub struct PatReference { + pub attrs: Vec<Attribute>, + pub and_token: Token![&], + pub mutability: Option<Token![mut]>, + pub pat: Box<Pat>, + } +} + +ast_struct! { + /// A struct or struct variant pattern: `Variant { x, y, .. }`. + pub struct PatStruct { + pub attrs: Vec<Attribute>, + pub path: Path, + pub brace_token: token::Brace, + pub fields: Punctuated<FieldPat, Token![,]>, + pub dot2_token: Option<Token![..]>, + } +} + +ast_struct! { + /// A tuple pattern: `(a, b)`. + pub struct PatTuple { + pub attrs: Vec<Attribute>, + pub paren_token: token::Paren, + pub elems: Punctuated<Pat, Token![,]>, + } +} + +ast_struct! { + /// A tuple struct or tuple variant pattern: `Variant(x, y, .., z)`. + pub struct PatTupleStruct { + pub attrs: Vec<Attribute>, + pub path: Path, + pub pat: PatTuple, + } +} + +ast_struct! { + /// A type ascription pattern: `foo: f64`. + pub struct PatType { + pub attrs: Vec<Attribute>, + pub pat: Box<Pat>, + pub colon_token: Token![:], + pub ty: Box<Type>, + } +} + +ast_struct! { + /// A pattern that matches any value: `_`. + pub struct PatWild { + pub attrs: Vec<Attribute>, + pub underscore_token: Token![_], + } +} + +ast_struct! { + /// A single field in a struct pattern. + /// + /// Patterns like the fields of Foo `{ x, ref y, ref mut z }` are treated + /// the same as `x: x, y: ref y, z: ref mut z` but there is no colon token. + pub struct FieldPat { + pub attrs: Vec<Attribute>, + pub member: Member, + pub colon_token: Option<Token![:]>, + pub pat: Box<Pat>, + } +} + +mod parsing { + use syn::{ + braced, + ext::IdentExt, + parenthesized, + parse::{Parse, ParseStream, Result}, + punctuated::Punctuated, + token, Ident, Member, Path, Token, + }; + + use crate::path; + + use super::{ + FieldPat, Pat, PatIdent, PatPath, PatReference, PatStruct, PatTuple, PatTupleStruct, + PatWild, + }; + + impl Parse for Pat { + fn parse(input: ParseStream<'_>) -> Result<Self> { + let lookahead = input.lookahead1(); + if lookahead.peek(Ident) + && ({ + input.peek2(Token![::]) + || input.peek2(token::Brace) + || input.peek2(token::Paren) + }) + || input.peek(Token![self]) && input.peek2(Token![::]) + || lookahead.peek(Token![::]) + || lookahead.peek(Token![<]) + || input.peek(Token![Self]) + || input.peek(Token![super]) + || input.peek(Token![extern]) + || input.peek(Token![crate]) + { + pat_path_or_struct(input) + } else if lookahead.peek(Token![_]) { + input.call(pat_wild).map(Pat::Wild) + } else if lookahead.peek(Token![ref]) + || lookahead.peek(Token![mut]) + || input.peek(Token![self]) + || input.peek(Ident) + { + input.call(pat_ident).map(Pat::Ident) + } else if lookahead.peek(Token![&]) { + input.call(pat_reference).map(Pat::Reference) + } else if lookahead.peek(token::Paren) { + input.call(pat_tuple).map(Pat::Tuple) + } else { + Err(lookahead.error()) + } + } + } + + fn pat_path_or_struct(input: ParseStream<'_>) -> Result<Pat> { + let path = path::parse_path(input)?; + + if input.peek(token::Brace) { + pat_struct(input, path).map(Pat::Struct) + } else if input.peek(token::Paren) { + pat_tuple_struct(input, path).map(Pat::TupleStruct) + } else { + Ok(Pat::Path(PatPath { + attrs: Vec::new(), + path, + })) + } + } + + fn pat_wild(input: ParseStream<'_>) -> Result<PatWild> { + Ok(PatWild { + attrs: Vec::new(), + underscore_token: input.parse()?, + }) + } + + fn pat_ident(input: ParseStream<'_>) -> Result<PatIdent> { + Ok(PatIdent { + attrs: Vec::new(), + by_ref: input.parse()?, + mutability: input.parse()?, + ident: input.call(Ident::parse_any)?, + }) + } + + fn pat_tuple_struct(input: ParseStream<'_>, path: Path) -> Result<PatTupleStruct> { + Ok(PatTupleStruct { + attrs: Vec::new(), + path, + pat: input.call(pat_tuple)?, + }) + } + + fn pat_struct(input: ParseStream<'_>, path: Path) -> Result<PatStruct> { + let content; + let brace_token = braced!(content in input); + + let mut fields = Punctuated::new(); + while !content.is_empty() && !content.peek(Token![..]) { + let value = content.call(field_pat)?; + fields.push_value(value); + if !content.peek(Token![,]) { + break; + } + let punct: Token![,] = content.parse()?; + fields.push_punct(punct); + } + + let dot2_token = if fields.empty_or_trailing() && content.peek(Token![..]) { + Some(content.parse()?) + } else { + None + }; + + Ok(PatStruct { + attrs: Vec::new(), + path, + brace_token, + fields, + dot2_token, + }) + } + + fn field_pat(input: ParseStream<'_>) -> Result<FieldPat> { + let boxed: Option<Token![box]> = input.parse()?; + let by_ref: Option<Token![ref]> = input.parse()?; + let mutability: Option<Token![mut]> = input.parse()?; + let member: Member = input.parse()?; + + if boxed.is_none() && by_ref.is_none() && mutability.is_none() && input.peek(Token![:]) + || is_unnamed(&member) + { + return Ok(FieldPat { + attrs: Vec::new(), + member, + colon_token: input.parse()?, + pat: input.parse()?, + }); + } + + let ident = match member { + Member::Named(ident) => ident, + Member::Unnamed(_) => unreachable!(), + }; + + let pat = Pat::Ident(PatIdent { + attrs: Vec::new(), + by_ref, + mutability, + ident: ident.clone(), + }); + + Ok(FieldPat { + member: Member::Named(ident), + pat: Box::new(pat), + attrs: Vec::new(), + colon_token: None, + }) + } + + fn pat_tuple(input: ParseStream<'_>) -> Result<PatTuple> { + let content; + let paren_token = parenthesized!(content in input); + + let mut elems = Punctuated::new(); + while !content.is_empty() { + let value: Pat = content.parse()?; + elems.push_value(value); + if content.is_empty() { + break; + } + let punct = content.parse()?; + elems.push_punct(punct); + } + + Ok(PatTuple { + attrs: Vec::new(), + paren_token, + elems, + }) + } + + fn pat_reference(input: ParseStream<'_>) -> Result<PatReference> { + Ok(PatReference { + attrs: Vec::new(), + and_token: input.parse()?, + mutability: input.parse()?, + pat: input.parse()?, + }) + } + + fn is_unnamed(member: &Member) -> bool { + match member { + Member::Named(_) => false, + Member::Unnamed(_) => true, + } + } +} + +mod printing { + use proc_macro2::TokenStream; + use quote::{ToTokens, TokenStreamExt}; + use syn::Token; + + use super::{ + FieldPat, PatIdent, PatPath, PatReference, PatStruct, PatTuple, PatTupleStruct, PatType, + PatWild, + }; + + impl ToTokens for PatWild { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.underscore_token.to_tokens(tokens); + } + } + + impl ToTokens for PatIdent { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.by_ref.to_tokens(tokens); + self.mutability.to_tokens(tokens); + self.ident.to_tokens(tokens); + } + } + + impl ToTokens for PatStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.path.to_tokens(tokens); + self.brace_token.surround(tokens, |tokens| { + self.fields.to_tokens(tokens); + // NOTE: We need a comma before the dot2 token if it is present. + if !self.fields.empty_or_trailing() && self.dot2_token.is_some() { + <Token![,]>::default().to_tokens(tokens); + } + self.dot2_token.to_tokens(tokens); + }); + } + } + + impl ToTokens for PatTupleStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.path.to_tokens(tokens); + self.pat.to_tokens(tokens); + } + } + + impl ToTokens for PatType { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(&self.attrs); + self.pat.to_tokens(tokens); + self.colon_token.to_tokens(tokens); + self.ty.to_tokens(tokens); + } + } + + impl ToTokens for PatPath { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.path.to_tokens(tokens) + } + } + + impl ToTokens for PatTuple { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.paren_token.surround(tokens, |tokens| { + self.elems.to_tokens(tokens); + }); + } + } + + impl ToTokens for PatReference { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.and_token.to_tokens(tokens); + self.mutability.to_tokens(tokens); + self.pat.to_tokens(tokens); + } + } + + impl ToTokens for FieldPat { + fn to_tokens(&self, tokens: &mut TokenStream) { + if let Some(colon_token) = &self.colon_token { + self.member.to_tokens(tokens); + colon_token.to_tokens(tokens); + } + self.pat.to_tokens(tokens); + } + } +} diff --git a/syn-mid/src/path.rs b/syn-mid/src/path.rs new file mode 100644 index 0000000..8093b53 --- /dev/null +++ b/syn-mid/src/path.rs @@ -0,0 +1,50 @@ +use syn::{ + ext::IdentExt, + parse::{ParseStream, Result}, + punctuated::Punctuated, + Ident, Path, PathArguments, PathSegment, Token, +}; + +fn parse_path_segment(input: ParseStream<'_>) -> Result<PathSegment> { + if input.peek(Token![super]) + || input.peek(Token![self]) + || input.peek(Token![crate]) + || input.peek(Token![extern]) + { + let ident = input.call(Ident::parse_any)?; + return Ok(PathSegment::from(ident)); + } + + let ident = if input.peek(Token![Self]) { + input.call(Ident::parse_any)? + } else { + input.parse()? + }; + + if input.peek(Token![::]) && input.peek3(Token![<]) { + Ok(PathSegment { + ident, + arguments: PathArguments::AngleBracketed(input.parse()?), + }) + } else { + Ok(PathSegment::from(ident)) + } +} + +pub(crate) fn parse_path(input: ParseStream<'_>) -> Result<Path> { + Ok(Path { + leading_colon: input.parse()?, + segments: { + let mut segments = Punctuated::new(); + let value = parse_path_segment(input)?; + segments.push_value(value); + while input.peek(Token![::]) { + let punct: Token![::] = input.parse()?; + segments.push_punct(punct); + let value = parse_path_segment(input)?; + segments.push_value(value); + } + segments + }, + }) +} |