Skip to content

Commit

Permalink
Upgrade to syn 2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
ruifengx authored and Kobzol committed Jul 5, 2023
1 parent e51dc42 commit 2f50927
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 58 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ include = [
]

[dependencies]
syn = { version = "1", features = ["full", "visit-mut"] }
syn = { version = "2", features = ["full", "visit-mut"] }
quote = "1"
proc-macro2 = "1"

Expand Down
83 changes: 37 additions & 46 deletions src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ struct CallMethodAttribute {

impl syn::parse::Parse for CallMethodAttribute {
fn parse(input: ParseStream) -> Result<Self, Error> {
let content;
syn::parenthesized!(content in input);
Ok(CallMethodAttribute {
name: content.parse()?,
name: input.parse()?,
})
}
}
Expand All @@ -23,10 +21,8 @@ struct GenerateAwaitAttribute {

impl syn::parse::Parse for GenerateAwaitAttribute {
fn parse(input: ParseStream) -> Result<Self, Error> {
let content;
syn::parenthesized!(content in input);
Ok(GenerateAwaitAttribute {
literal: content.parse()?,
literal: input.parse()?,
})
}
}
Expand All @@ -37,23 +33,16 @@ struct IntoAttribute {

impl syn::parse::Parse for IntoAttribute {
fn parse(input: ParseStream) -> Result<Self, Error> {
if input.peek(syn::token::Paren) {
let content;
syn::parenthesized!(content in input);

let type_path: TypePath = content.parse().map_err(|error| {
Error::new(
input.span(),
format!("{error}\nExpected type name, e.g. #[into(u32)]"),
)
})?;
let type_path: TypePath = input.parse().map_err(|error| {
Error::new(
input.span(),
format!("{error}\nExpected type name, e.g. #[into(u32)]"),
)
})?;

Ok(IntoAttribute {
type_path: Some(type_path),
})
} else {
Ok(IntoAttribute { type_path: None })
}
Ok(IntoAttribute {
type_path: Some(type_path),
})
}
}

Expand All @@ -63,10 +52,7 @@ pub struct TraitTarget {

impl syn::parse::Parse for TraitTarget {
fn parse(input: ParseStream) -> Result<Self, Error> {
let content;
syn::parenthesized!(content in input);

let type_path: TypePath = content.parse().map_err(|error| {
let type_path: TypePath = input.parse().map_err(|error| {
Error::new(
input.span(),
format!("{error}\nExpected trait path, e.g. #[through(foo::MyTrait)]"),
Expand Down Expand Up @@ -102,50 +88,55 @@ fn parse_attributes(
.map(|attribute| {
let parsed = if let syn::AttrStyle::Outer = attribute.style {
let name = attribute
.path
.path()
.get_ident()
.map(|i| i.to_string())
.unwrap_or_default();
match name.as_str() {
"call" => {
let target = syn::parse2::<CallMethodAttribute>(attribute.tokens.clone())
let target = attribute
.parse_args::<CallMethodAttribute>()
.expect("Cannot parse `call` attribute");
Some(ParsedAttribute::TargetMethod(target.name))
}
"into" => {
let into = syn::parse2::<IntoAttribute>(attribute.tokens.clone())
.expect("Cannot parse `into` attribute");
let into = match &attribute.meta {
Meta::NameValue(_) => {
panic!("Cannot parse `into` attribute: expected parentheses")
}
Meta::Path(_) => IntoAttribute { type_path: None },
Meta::List(meta) => meta
.parse_args::<IntoAttribute>()
.expect("Cannot parse `into` attribute"),
};
Some(ParsedAttribute::ReturnExpression(ReturnExpression::Into(
into.type_path,
)))
}
"try_into" => {
let meta = attribute
.parse_meta()
.expect("Invalid `try_into` arguments");

if let Meta::List(list) = meta {
if let Some(syn::NestedMeta::Meta(Meta::Path(path))) =
list.nested.first()
{
if path.is_ident("unwrap") {
if let Meta::List(meta) = &attribute.meta {
meta.parse_nested_meta(|meta| {
if meta.path.is_ident("unwrap") {
panic!(
"Replace #[try_into(unwrap)] with\n#[try_into]\n#[unwrap]",
);
}
}
Ok(())
})
.expect("Invalid `try_into` arguments");
}
Some(ParsedAttribute::ReturnExpression(ReturnExpression::TryInto))
}
"unwrap" => Some(ParsedAttribute::ReturnExpression(ReturnExpression::Unwrap)),
"await" => {
let generate =
syn::parse2::<GenerateAwaitAttribute>(attribute.tokens.clone())
.expect("Cannot parse `await` attribute");
let generate = attribute
.parse_args::<GenerateAwaitAttribute>()
.expect("Cannot parse `await` attribute");
Some(ParsedAttribute::Await(generate.literal.value))
}
"through" => Some(ParsedAttribute::ThroughTrait(
syn::parse2::<TraitTarget>(attribute.tokens.clone())
attribute
.parse_args::<TraitTarget>()
.expect("Cannot parse `through` attribute"),
)),
_ => None,
Expand Down Expand Up @@ -177,10 +168,10 @@ pub struct MethodAttributes<'a> {
/// - try_into => generates a `try_into()` call after the delegated expression
/// - await => generates an `.await` expression after the delegated expression
/// - unwrap => generates a `unwrap()` call after the delegated expression
/// - throuhg => generates a UFCS call (`Target::method(&<expr>, ...)`) around the delegated expression
/// - through => generates a UFCS call (`Target::method(&<expr>, ...)`) around the delegated expression
pub fn parse_method_attributes<'a>(
attrs: &'a [Attribute],
method: &syn::TraitItemMethod,
method: &syn::TraitItemFn,
) -> MethodAttributes<'a> {
let mut target_method: Option<syn::Ident> = None;
let mut expressions: Vec<ReturnExpression> = vec![];
Expand Down
104 changes: 93 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,7 @@ enum DelegatedInput {
}

fn get_argument_modifier(attribute: syn::Attribute) -> Result<ArgumentModifier, Error> {
let meta = attribute.parse_meta()?;
if let Meta::Path(mut path) = meta {
if let Meta::Path(mut path) = attribute.meta {
if path.segments.len() == 1 {
let segment = path.segments.pop().unwrap();
if segment.value().arguments.is_empty() {
Expand All @@ -357,7 +356,7 @@ fn get_argument_modifier(attribute: syn::Attribute) -> Result<ArgumentModifier,
}
};

panic!("The attribute argument has to be `from` or `as_ref`, like this: `#[from] a: u32`.")
panic!("The attribute argument has to be `into` or `as_ref`, like this: `#[into] a: u32`.")
}

impl syn::parse::Parse for DelegatedInput {
Expand All @@ -370,7 +369,7 @@ impl syn::parse::Parse for DelegatedInput {
Ok(Self::Argument(expression))
} else {
let (input, modifier) = if lookahead.peek(syn::token::Pound) {
let mut attributes = input.call(syn::Attribute::parse_outer)?;
let mut attributes = input.call(tolerant_outer_attributes)?;
if attributes.len() > 1 {
panic!("You can specify at most a single attribute for each parameter in a delegated method");
}
Expand All @@ -392,7 +391,7 @@ impl syn::parse::Parse for DelegatedInput {
}

struct DelegatedMethod {
method: syn::TraitItemMethod,
method: syn::TraitItemFn,
attributes: Vec<syn::Attribute>,
visibility: syn::Visibility,
arguments: syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>,
Expand Down Expand Up @@ -451,7 +450,7 @@ fn parse_input_into_argument_expression(

impl syn::parse::Parse for DelegatedMethod {
fn parse(input: ParseStream) -> Result<Self, Error> {
let attributes = input.call(syn::Attribute::parse_outer)?;
let attributes = input.call(tolerant_outer_attributes)?;
let visibility = input.call(syn::Visibility::parse)?;

// Unchanged from Parse from TraitItemMethod
Expand All @@ -475,8 +474,7 @@ impl syn::parse::Parse for DelegatedMethod {
// retrieve the expressions inside of the brackets as well as variable
// identifiers of ordinary inputs. The arguments must preserve the order
// of the inputs.
let delegated_inputs =
content.parse_terminated::<DelegatedInput, syn::Token![,]>(DelegatedInput::parse)?;
let delegated_inputs = content.parse_terminated(DelegatedInput::parse, syn::Token![,])?;
let mut inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]> =
syn::punctuated::Punctuated::new();
let mut arguments: syn::punctuated::Punctuated<syn::Expr, syn::Token![,]> =
Expand Down Expand Up @@ -601,7 +599,7 @@ impl syn::parse::Parse for DelegatedMethod {
};

// This needs to be populated from scratch because of the signature above.
let method = syn::TraitItemMethod {
let method = syn::TraitItemFn {
// All attributes are attached to `DelegatedMethod`, since they
// presumably pertain to the process of delegation, not the
// signature of the delegator.
Expand All @@ -628,7 +626,7 @@ struct DelegatedSegment {

impl syn::parse::Parse for DelegatedSegment {
fn parse(input: ParseStream) -> Result<Self, Error> {
let attributes = input.call(syn::Attribute::parse_outer)?;
let attributes = input.call(tolerant_outer_attributes)?;
let segment_attrs = parse_segment_attributes(&attributes);

if let Ok(keyword) = input.parse::<kw::target>() {
Expand Down Expand Up @@ -678,7 +676,7 @@ impl syn::parse::Parse for DelegationBlock {
fn has_inline_attribute(attrs: &[&syn::Attribute]) -> bool {
attrs.iter().any(|attr| {
if let syn::AttrStyle::Outer = attr.style {
attr.path.is_ident("inline")
attr.path().is_ident("inline")
} else {
false
}
Expand Down Expand Up @@ -798,3 +796,87 @@ pub fn delegate(tokens: TokenStream) -> TokenStream {
};
result.into()
}

// we cannot use `Attributes::parse_outer` directly, because it does not allow keywords to appear
// in meta path positions, i.e., it does not accept `#[await(true)]`.
// related issue: /~https://github.com/dtolnay/syn/issues/1458
fn tolerant_outer_attributes(input: ParseStream) -> syn::Result<Vec<syn::Attribute>> {
use proc_macro2::{Delimiter, TokenTree};
use syn::{
bracketed,
ext::IdentExt,
parse::discouraged::Speculative,
token::{Brace, Bracket, Paren},
AttrStyle, Attribute, Expr, ExprLit, Lit, MacroDelimiter, MetaList, MetaNameValue, Path,
Result, Token,
};

fn tolerant_attr(input: ParseStream) -> Result<Attribute> {
let content;
Ok(Attribute {
pound_token: input.parse()?,
style: AttrStyle::Outer,
bracket_token: bracketed!(content in input),
meta: content.call(tolerant_meta)?,
})
}

// adapted from `impl Parse for Meta`
fn tolerant_meta(input: ParseStream) -> Result<Meta> {
let path = Path::from(input.call(Ident::parse_any)?);
if input.peek(Paren) || input.peek(Bracket) || input.peek(Brace) {
// adapted from the private `syn::attr::parse_meta_after_path`
input.step(|cursor| {
if let Some((TokenTree::Group(g), rest)) = cursor.token_tree() {
let span = g.delim_span();
let delimiter = match g.delimiter() {
Delimiter::Parenthesis => MacroDelimiter::Paren(Paren(span)),
Delimiter::Brace => MacroDelimiter::Brace(Brace(span)),
Delimiter::Bracket => MacroDelimiter::Bracket(Bracket(span)),
Delimiter::None => {
return Err(cursor.error("expected delimiter"));
}
};
Ok((
Meta::List(MetaList {
path,
delimiter,
tokens: g.stream(),
}),
rest,
))
} else {
Err(cursor.error("expected delimiter"))
}
})
} else if input.peek(Token![=]) {
// adapted from the private `syn::attr::parse_meta_name_value_after_path`
let eq_token = input.parse()?;
let ahead = input.fork();
let value = match ahead.parse::<Option<Lit>>()? {
// this branch is probably for speeding up the parsing for doc comments etc.
Some(lit) if ahead.is_empty() => {
input.advance_to(&ahead);
Expr::Lit(ExprLit {
attrs: Vec::new(),
lit,
})
}
_ => input.parse()?,
};
Ok(Meta::NameValue(MetaNameValue {
path,
eq_token,
value,
}))
} else {
Ok(Meta::Path(path))
}
}

let mut attrs = Vec::new();
while input.peek(Token![#]) {
attrs.push(input.call(tolerant_attr)?);
}
Ok(attrs)
}

0 comments on commit 2f50927

Please sign in to comment.