Skip to content

Commit

Permalink
Upgrade to syn v2 (#1731)
Browse files Browse the repository at this point in the history
* syn 2.0

* TraitItemMethod -> TraitItemFn

* Token bang

* ImplItemMethod -> ImplItemFn

* Token![pub]

* Remove ItemMacro2

* WIP fix Attribute Span

* WIP updates

* WIP updates

* Method to Fn

* Handle remaining attributes

* Temp fix for extracting cfg attrs

* Fix some other errors

* Add test for wildcard selector (doesn't work :()

* WIP impl parse for AttributeFrag

* Fmt

* Import skeleton parse impl

* WIP

* Copy across parse impl

* Parse terminated

* WIP fix attr parsing

* WIP fix attr parsing

* Remove attr property

* Utilize attr_args meta for parsing

* Format error messages

* Extract selector parsing to TryFrom

* Handle parsing wildcard selector

* Factor out namesapce parsing

* Introduce helper methods

* suggestions

* fix parsing of cfg attrs

* remove expand.rs

* add error message

* start working on more detailed errors

* fix tests

* rework docs parsing

* changelog entry

* some refactoring

* fmt

---------

Co-authored-by: Andrew Jones <ascjones@gmail.com>
  • Loading branch information
German and ascjones authored Mar 28, 2023
1 parent 3f11e0e commit b436933
Show file tree
Hide file tree
Showing 44 changed files with 596 additions and 521 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
- Upgraded `syn` to version `2` - [#1731](https://github.com/paritytech/ink/pull/1731)

## Version 4.1.0

### Added
Expand Down
2 changes: 1 addition & 1 deletion crates/e2e/macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ derive_more = "0.99.17"
env_logger = "0.10.0"
log = "0.4.17"
serde_json = "1.0.89"
syn = "1"
syn = "2"
proc-macro2 = "1"
quote = "1"
which = "4.4.0"
2 changes: 1 addition & 1 deletion crates/ink/codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ name = "ink_codegen"
ink_primitives = { version = "4.1.0", path = "../../primitives" }
ir = { version = "4.1.0", package = "ink_ir", path = "../ir", default-features = false }
quote = "1"
syn = { version = "1.0", features = ["parsing", "full", "extra-traits"] }
syn = { version = "2.0", features = ["parsing", "full", "extra-traits"] }
proc-macro2 = "1.0"
derive_more = { version = "0.99", default-features = false, features = ["from"] }
itertools = "0.10"
Expand Down
2 changes: 1 addition & 1 deletion crates/ink/ir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ name = "ink_ir"

[dependencies]
quote = "1"
syn = { version = "1.0", features = ["parsing", "full", "visit", "extra-traits"] }
syn = { version = "2.0", features = ["parsing", "full", "visit", "extra-traits"] }
proc-macro2 = "1.0"
itertools = { version = "0.10", default-features = false }
either = { version = "1.5", default-features = false }
Expand Down
112 changes: 2 additions & 110 deletions crates/ink/ir/src/ast/attr_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use proc_macro2::{
Ident,
TokenStream as TokenStream2,
};
use quote::ToTokens;
use super::MetaNameValue;
use syn::{
ext::IdentExt as _,
parse::{
Parse,
ParseStream,
},
punctuated::Punctuated,
spanned::Spanned,
Token,
};

Expand All @@ -37,24 +31,6 @@ pub struct AttributeArgs {
args: Punctuated<MetaNameValue, Token![,]>,
}

/// A name-value pair within an attribute, like `feature = "nightly"`.
///
/// The only difference from `syn::MetaNameValue` is that this additionally
/// allows the `value` to be a plain identifier or path.
#[derive(Debug, PartialEq, Eq)]
pub struct MetaNameValue {
pub name: syn::Path,
pub eq_token: syn::token::Eq,
pub value: PathOrLit,
}

/// Either a path or a literal.
#[derive(Debug, PartialEq, Eq)]
pub enum PathOrLit {
Path(syn::Path),
Lit(syn::Lit),
}

impl IntoIterator for AttributeArgs {
type Item = MetaNameValue;
type IntoIter = syn::punctuated::IntoIter<MetaNameValue>;
Expand All @@ -72,94 +48,10 @@ impl Parse for AttributeArgs {
}
}

impl Parse for MetaNameValue {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
let path = input.call(Self::parse_meta_path)?;
Self::parse_meta_name_value_after_path(path, input)
}
}

impl ToTokens for PathOrLit {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
Self::Lit(lit) => lit.to_tokens(tokens),
Self::Path(path) => path.to_tokens(tokens),
}
}
}

impl ToTokens for MetaNameValue {
fn to_tokens(&self, tokens: &mut TokenStream2) {
self.name.to_tokens(tokens);
self.eq_token.to_tokens(tokens);
self.value.to_tokens(tokens);
}
}

impl MetaNameValue {
/// Like [`syn::Path::parse_mod_style`] but accepts keywords in the path.
///
/// # Note
///
/// This code was taken from the `syn` implementation for a very similar
/// syntactical pattern.
fn parse_meta_path(input: ParseStream) -> Result<syn::Path, syn::Error> {
Ok(syn::Path {
leading_colon: input.parse()?,
segments: {
let mut segments = Punctuated::new();
while input.peek(Ident::peek_any) {
let ident = Ident::parse_any(input)?;
segments.push_value(syn::PathSegment::from(ident));
if !input.peek(syn::Token![::]) {
break
}
let punct = input.parse()?;
segments.push_punct(punct);
}
if segments.is_empty() {
return Err(input.error("expected path"))
} else if segments.trailing_punct() {
return Err(input.error("expected path segment"))
}
segments
},
})
}

fn parse_meta_name_value_after_path(
name: syn::Path,
input: ParseStream,
) -> Result<MetaNameValue, syn::Error> {
let span = name.span();
Ok(MetaNameValue {
name,
eq_token: input.parse().map_err(|_error| {
format_err!(
span,
"ink! config options require an argument separated by '='",
)
})?,
value: input.parse()?,
})
}
}

impl Parse for PathOrLit {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
if input.fork().peek(syn::Lit) {
return input.parse::<syn::Lit>().map(PathOrLit::Lit)
}
if input.fork().peek(Ident::peek_any) || input.fork().peek(Token![::]) {
return input.parse::<syn::Path>().map(PathOrLit::Path)
}
Err(input.error("cannot parse into either literal or path"))
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::ast::PathOrLit;
use quote::quote;

impl AttributeArgs {
Expand Down
211 changes: 211 additions & 0 deletions crates/ink/ir/src/ast/meta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// Copyright 2018-2022 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use proc_macro2::{
Ident,
TokenStream as TokenStream2,
};
use quote::ToTokens;
use syn::{
ext::IdentExt as _,
parse::{
Parse,
ParseStream,
},
punctuated::Punctuated,
spanned::Spanned,
LitInt,
Token,
};

/// Content of a compile-time structured attribute.
///
/// This is a subset of `syn::Meta` that allows the `value` of a name-value pair
/// to be a plain identifier or path.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Meta {
/// A path, like `message`.
Path(syn::Path),
/// A name-value pair, like `feature = "nightly"`.
NameValue(MetaNameValue),
}

impl Parse for Meta {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
let path = input.call(parse_meta_path)?;
if input.peek(Token![=]) {
MetaNameValue::parse_meta_name_value_after_path(path, input)
.map(Meta::NameValue)
} else {
Ok(Meta::Path(path))
}
}
}

impl ToTokens for Meta {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
Self::Path(path) => path.to_tokens(tokens),
Self::NameValue(name_value) => name_value.to_tokens(tokens),
}
}
}

/// A name-value pair within an attribute, like `feature = "nightly"`.
///
/// The only difference from `syn::MetaNameValue` is that this additionally
/// allows the `value` to be a plain identifier or path.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MetaNameValue {
pub name: syn::Path,
pub eq_token: syn::token::Eq,
pub value: PathOrLit,
}

impl Parse for MetaNameValue {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
let path = input.call(parse_meta_path)?;
Self::parse_meta_name_value_after_path(path, input)
}
}

impl ToTokens for MetaNameValue {
fn to_tokens(&self, tokens: &mut TokenStream2) {
self.name.to_tokens(tokens);
self.eq_token.to_tokens(tokens);
self.value.to_tokens(tokens);
}
}

impl MetaNameValue {
fn parse_meta_name_value_after_path(
name: syn::Path,
input: ParseStream,
) -> Result<MetaNameValue, syn::Error> {
let span = name.span();
Ok(MetaNameValue {
name,
eq_token: input.parse().map_err(|_error| {
format_err!(
span,
"ink! config options require an argument separated by '='",
)
})?,
value: input.parse()?,
})
}
}

/// Either a path or a literal.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PathOrLit {
Path(syn::Path),
Lit(syn::Lit),
}

impl Parse for PathOrLit {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
if input.fork().peek(syn::Lit) {
return input.parse::<syn::Lit>().map(PathOrLit::Lit)
}
if input.fork().peek(Ident::peek_any) || input.fork().peek(Token![::]) {
return input.call(parse_meta_path).map(PathOrLit::Path)
}
Err(input.error("cannot parse into either literal or path"))
}
}

impl ToTokens for PathOrLit {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
Self::Lit(lit) => lit.to_tokens(tokens),
Self::Path(path) => path.to_tokens(tokens),
}
}
}

impl PathOrLit {
/// Returns the value of the literal if it is a boolean literal.
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Lit(syn::Lit::Bool(lit_bool)) => Some(lit_bool.value),
_ => None,
}
}

/// Returns the value of the literal if it is a string literal.
pub fn as_string(&self) -> Option<String> {
match self {
Self::Lit(syn::Lit::Str(lit_str)) => Some(lit_str.value()),
_ => None,
}
}

/// Returns the the literal if it is an integer literal.
pub fn as_lit_int(&self) -> Option<&LitInt> {
match self {
Self::Lit(syn::Lit::Int(lit_int)) => Some(lit_int),
_ => None,
}
}
}

/// Like [`syn::Path::parse_mod_style`] but accepts keywords in the path.
///
/// # Note
///
/// This code was taken from the `syn` implementation for a very similar
/// syntactical pattern.
fn parse_meta_path(input: ParseStream) -> Result<syn::Path, syn::Error> {
Ok(syn::Path {
leading_colon: input.parse()?,
segments: {
let mut segments = Punctuated::new();
while input.peek(Ident::peek_any) {
let ident = Ident::parse_any(input)?;
segments.push_value(syn::PathSegment::from(ident));
if !input.peek(syn::Token![::]) {
break
}
let punct = input.parse()?;
segments.push_punct(punct);
}
if segments.is_empty() {
return Err(input.error("expected path"))
} else if segments.trailing_punct() {
return Err(input.error("expected path segment"))
}
segments
},
})
}

#[cfg(test)]
mod tests {
use super::*;
use crate::ast::PathOrLit;
use quote::quote;

#[test]
fn underscore_token_works() {
assert_eq!(
syn::parse2::<Meta>(quote! { selector = _ }).unwrap(),
Meta::NameValue(MetaNameValue {
name: syn::parse_quote! { selector },
eq_token: syn::parse_quote! { = },
value: PathOrLit::Path(syn::Path::from(quote::format_ident!("_"))),
})
)
}
}
Loading

0 comments on commit b436933

Please sign in to comment.