diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aac63b02b0..7e2125d51c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## Changed +- Update ink! attribute argument syntax docs and simplify ink! attribute meta parsing implementation in ink_ir - [#1927](https://github.com/paritytech/ink/pull/1927) - Make `set_code_hash` generic - [#1906](https://github.com/paritytech/ink/pull/1906) ## Version 5.0.0-alpha diff --git a/crates/ink/ir/src/ast/meta.rs b/crates/ink/ir/src/ast/meta.rs index df1703afa9d..04878d91fed 100644 --- a/crates/ink/ir/src/ast/meta.rs +++ b/crates/ink/ir/src/ast/meta.rs @@ -23,7 +23,6 @@ use syn::{ Parse, ParseStream, }, - punctuated::Punctuated, spanned::Spanned, LitInt, Token, @@ -32,7 +31,7 @@ use syn::{ /// 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. +/// to be a literal, path, underscore (`_`) or `@` symbol. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Meta { /// A path, like `message`. @@ -43,7 +42,15 @@ pub enum Meta { impl Parse for Meta { fn parse(input: ParseStream) -> Result { - let path = input.call(parse_meta_path)?; + // Handles the `impl` argument. + if input.peek(Token![impl]) { + let ident = Ident::parse_any(input)?; + let path = syn::Path::from(ident); + return Ok(Self::Path(path)) + } + + // Handles all other arguments. + let path: syn::Path = input.parse()?; if input.peek(Token![=]) { MetaNameValue::parse_meta_name_value_after_path(path, input) .map(Meta::NameValue) @@ -65,7 +72,7 @@ impl ToTokens for Meta { /// 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. +/// allows the `value` to be an `@` symbol. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MetaNameValue { pub name: syn::Path, @@ -75,7 +82,7 @@ pub struct MetaNameValue { impl Parse for MetaNameValue { fn parse(input: ParseStream) -> Result { - let path = input.call(parse_meta_path)?; + let path = input.parse()?; Self::parse_meta_name_value_after_path(path, input) } } @@ -120,13 +127,16 @@ impl Parse for MetaValue { if input.peek(Token![_]) || input.peek(Token![@]) { return input.parse::().map(MetaValue::Symbol) } - if input.fork().peek(syn::Lit) { + if input.peek(syn::Lit) { return input.parse::().map(MetaValue::Lit) } - if input.fork().peek(Ident::peek_any) || input.fork().peek(Token![::]) { - return input.call(parse_meta_path).map(MetaValue::Path) + if input.peek(Ident::peek_any) || input.peek(Token![::]) { + return input.parse::().map(MetaValue::Path) } - Err(input.error("expected a literal, a path or a punct for a meta value")) + Err(input.error( + "expected a literal, a path, an underscore (`_`)\ + or an `@` symbol for a meta value", + )) } } @@ -193,36 +203,6 @@ impl ToTokens for Symbol { } } -/// 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 { - 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::*; @@ -232,6 +212,14 @@ mod tests { }; use quote::quote; + #[test] + fn impl_name_works() { + assert_eq!( + syn::parse2::(quote! { impl }).unwrap(), + Meta::Path(syn::Path::from(quote::format_ident!("impl"))) + ) + } + #[test] fn underscore_token_works() { assert_eq!( @@ -243,4 +231,16 @@ mod tests { }) ) } + + #[test] + fn at_token_works() { + assert_eq!( + syn::parse2::(quote! { selector = @ }).unwrap(), + Meta::NameValue(MetaNameValue { + name: syn::parse_quote! { selector }, + eq_token: syn::parse_quote! { = }, + value: MetaValue::Symbol(Symbol::AtSign(syn::parse_quote! { @ })), + }) + ) + } } diff --git a/crates/ink/ir/src/ast/mod.rs b/crates/ink/ir/src/ast/mod.rs index c955bd0339e..6d9904253e1 100644 --- a/crates/ink/ir/src/ast/mod.rs +++ b/crates/ink/ir/src/ast/mod.rs @@ -12,17 +12,42 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Types and abstractions for ink! definitions that require custom syntax. +//! Types and abstractions for ink! definitions that require custom +//! [`syn::parse::Parse`] implementations. //! //! # Note //! -//! In general we try not to require any sort of custom non-standard Rust -//! syntax. +//! In general we do not require any sort of custom non-standard Rust syntax. //! -//! At the time of this writing we currently only use this for the argument -//! parsing of ink! configuration header `#[ink(env = my::env::Types, ...)]` -//! in order to be able to parse identifiers in `name = value` segments for -//! the `value` part. +//! However, because the Rust attribute grammar is very flexible, +//! custom [`syn::parse::Parse`] implementations are typically required +//! for parsing structured arguments from attribute syntax that doesn't +//! exactly match the +//! ["meta item" attribute syntax](https://doc.rust-lang.org/reference/attributes.html#meta-item-attribute-syntax) +//! used by most +//! ["built-in" Rust attributes](https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index) +//! for which [`syn::Meta`] (and its related variant types) can be used directly. +//! +//! At the time of this writing, ink! attribute argument syntax deviates from +//! ["meta item" attribute syntax](https://doc.rust-lang.org/reference/attributes.html#meta-item-attribute-syntax) +//! by: +//! - allowing the `impl` keyword in "meta item" paths +//! (i.e. `#[ink(impl)]` which is a deviation from the +//! [simple path](https://doc.rust-lang.org/reference/paths.html#simple-paths) +//! grammar). +//! - allowing the `@` symbol as a `value` in `name-value` pairs +//! (i.e. `#[ink(selector = @)]` which is a deviation from the +//! [expression](https://doc.rust-lang.org/reference/expressions.html) +//! grammar followed by the `value` part). +//! +//! NOTE: Underscore (`_`) values in `name-value` pairs +//! (e.g. `#[ink(selector = _)]`) are technically allowed by +//! the "meta item" attribute syntax as they can be interpreted as +//! [underscore expressions](https://doc.rust-lang.org/reference/expressions/underscore-expr.html) +//! (same for path values - e.g `#[ink(env = my::env::Types)]` - +//! which are valid +//! [path expressions](https://doc.rust-lang.org/reference/expressions/path-expr.html) +//! in "meta item" attribute syntax). mod attr_args; mod meta;