diff --git a/Cargo.toml b/Cargo.toml index 5e72f6bcf..aa098e247 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ alloy-dyn-abi = { version = "0.6.4", path = "crates/dyn-abi", default-features = alloy-json-abi = { version = "0.6.4", path = "crates/json-abi", default-features = false } alloy-primitives = { version = "0.6.4", path = "crates/primitives", default-features = false } alloy-sol-macro = { version = "0.6.4", path = "crates/sol-macro", default-features = false } +alloy-sol-macro-input = { version = "0.6.4", path = "crates/sol-macro-input", default-features = false } alloy-sol-type-parser = { version = "0.6.4", path = "crates/sol-type-parser", default-features = false } alloy-sol-types = { version = "0.6.4", path = "crates/sol-types", default-features = false } syn-solidity = { version = "0.6.4", path = "crates/syn-solidity", default-features = false } diff --git a/crates/sol-macro-input/Cargo.toml b/crates/sol-macro-input/Cargo.toml new file mode 100644 index 000000000..6e46ab7c5 --- /dev/null +++ b/crates/sol-macro-input/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "alloy-sol-macro-input" +description = "Input types for sol!-like macros" +keywords = ["ethereum", "abi", "encoding", "evm", "solidity"] +categories = ["encoding", "cryptography::cryptocurrencies"] +homepage = "https://github.com/alloy-rs/core/tree/main/crates/sol-macro-input" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +exclude.workspace = true + +[dependencies] +dunce = "1.0.4" +heck = "0.5.0" +hex.workspace = true +proc-macro2.workspace = true +syn.workspace = true +syn-solidity.workspace = true +quote.workspace = true + +# json +alloy-json-abi = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } + +[features] +json = ["dep:alloy-json-abi", "dep:serde_json"] diff --git a/crates/sol-macro/src/attr.rs b/crates/sol-macro-input/src/attr.rs similarity index 84% rename from crates/sol-macro/src/attr.rs rename to crates/sol-macro-input/src/attr.rs index 6af32e3d7..a7ab4dd3d 100644 --- a/crates/sol-macro/src/attr.rs +++ b/crates/sol-macro-input/src/attr.rs @@ -6,18 +6,22 @@ use syn::{punctuated::Punctuated, Attribute, Error, LitBool, LitStr, Path, Resul const DUPLICATE_ERROR: &str = "duplicate attribute"; const UNKNOWN_ERROR: &str = "unknown `sol` attribute"; +/// Wraps the argument in a doc attribute. pub fn mk_doc(s: impl quote::ToTokens) -> TokenStream { quote!(#[doc = #s]) } +/// Returns `true` if the attribute is `#[doc = "..."]`. pub fn is_doc(attr: &Attribute) -> bool { attr.path().is_ident("doc") } +/// Returns `true` if the attribute is `#[derive(...)]`. pub fn is_derive(attr: &Attribute) -> bool { attr.path().is_ident("derive") } +/// Returns an iterator over all the `#[doc = "..."]` attributes. pub fn docs(attrs: &[Attribute]) -> impl Iterator { attrs.iter().filter(|a| is_doc(a)) } @@ -45,10 +49,13 @@ pub fn docs_str(attrs: &[Attribute]) -> String { doc } +/// Returns an iterator over all the `#[derive(...)]` attributes. pub fn derives(attrs: &[Attribute]) -> impl Iterator { attrs.iter().filter(|a| is_derive(a)) } +/// Returns an iterator over all the rust `::` paths in the `#[derive(...)]` +/// attributes. pub fn derives_mapped(attrs: &[Attribute]) -> impl Iterator + '_ { derives(attrs).flat_map(|attr| { attr.parse_args_with(Punctuated::::parse_terminated).unwrap_or_default() @@ -59,34 +66,46 @@ pub fn derives_mapped(attrs: &[Attribute]) -> impl Iterator + '_ { // 1. add a field to this struct, // 2. add a match arm in the `parse` function below, // 3. add test cases in the `tests` module at the bottom of this file, -// 4. implement the attribute in the `expand` module, -// 5. document the attribute in the [`crate::sol!`] macro docs. +// 4. implement the attribute in your `SolInputExpander` implementation, +// 5. document the attribute in the [`sol!`] macro docs. /// `#[sol(...)]` attributes. -/// See [`crate::sol!`] for a list of all possible attributes. #[derive(Debug, Default, PartialEq, Eq)] pub struct SolAttrs { + /// `#[sol(rpc)]` pub rpc: Option, + /// `#[sol(abi)]` pub abi: Option, + /// `#[sol(all_derives)]` pub all_derives: Option, + /// `#[sol(extra_methods)]` pub extra_methods: Option, + /// `#[sol(docs)]` pub docs: Option, + /// `#[sol(alloy_sol_types = alloy_core::sol_types)]` pub alloy_sol_types: Option, + /// `#[sol(alloy_contract = alloy_contract)]` pub alloy_contract: Option, // TODO: Implement + /// UNIMPLEMENTED: `#[sol(rename = "new_name")]` pub rename: Option, // TODO: Implement + /// UNIMPLMENTED: `#[sol(rename_all = "camelCase")]` pub rename_all: Option, + /// `#[sol(bytecode = "0x1234")]` pub bytecode: Option, + /// `#[sol(deployed_bytecode = "0x1234")]` pub deployed_bytecode: Option, + /// UDVT only `#[sol(type_check = "my_function")]` pub type_check: Option, } impl SolAttrs { + /// Parse the `#[sol(...)]` attributes from a list of attributes. pub fn parse(attrs: &[Attribute]) -> Result<(Self, Vec)> { let mut this = Self::default(); let mut others = Vec::with_capacity(attrs.len()); @@ -165,6 +184,66 @@ impl SolAttrs { } } +/// Trait for items that contain `#[sol(...)]` attributes among other +/// attributes. This is usually a shortcut for [`SolAttrs::parse`]. +pub trait ContainsSolAttrs { + /// Get the list of attributes. + fn attrs(&self) -> &[Attribute]; + + /// Parse the `#[sol(...)]` attributes from the list of attributes. + fn split_attrs(&self) -> syn::Result<(SolAttrs, Vec)> { + SolAttrs::parse(self.attrs()) + } +} + +impl ContainsSolAttrs for syn_solidity::File { + fn attrs(&self) -> &[Attribute] { + &self.attrs + } +} + +impl ContainsSolAttrs for syn_solidity::ItemContract { + fn attrs(&self) -> &[Attribute] { + &self.attrs + } +} + +impl ContainsSolAttrs for syn_solidity::ItemEnum { + fn attrs(&self) -> &[Attribute] { + &self.attrs + } +} + +impl ContainsSolAttrs for syn_solidity::ItemError { + fn attrs(&self) -> &[Attribute] { + &self.attrs + } +} + +impl ContainsSolAttrs for syn_solidity::ItemEvent { + fn attrs(&self) -> &[Attribute] { + &self.attrs + } +} + +impl ContainsSolAttrs for syn_solidity::ItemFunction { + fn attrs(&self) -> &[Attribute] { + &self.attrs + } +} + +impl ContainsSolAttrs for syn_solidity::ItemStruct { + fn attrs(&self) -> &[Attribute] { + &self.attrs + } +} + +impl ContainsSolAttrs for syn_solidity::ItemUdt { + fn attrs(&self) -> &[Attribute] { + &self.attrs + } +} + /// Defines the casing for the attributes long representation. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum CasingStyle { diff --git a/crates/sol-macro-input/src/expander.rs b/crates/sol-macro-input/src/expander.rs new file mode 100644 index 000000000..0a8e66016 --- /dev/null +++ b/crates/sol-macro-input/src/expander.rs @@ -0,0 +1,9 @@ +use proc_macro2::TokenStream; + +use crate::SolInput; + +/// Expands a `SolInput` into a `TokenStream`. +pub trait SolInputExpander { + /// Expand a `SolInput` into a `TokenStream`. + fn expand(&mut self, input: &SolInput) -> syn::Result; +} diff --git a/crates/sol-macro/src/input.rs b/crates/sol-macro-input/src/input.rs similarity index 77% rename from crates/sol-macro/src/input.rs rename to crates/sol-macro-input/src/input.rs index d6e525f2d..78815e11d 100644 --- a/crates/sol-macro/src/input.rs +++ b/crates/sol-macro-input/src/input.rs @@ -1,21 +1,23 @@ use ast::Spanned; -use proc_macro2::TokenStream; -use quote::quote; use std::path::PathBuf; use syn::{ parse::{discouraged::Speculative, Parse, ParseStream}, Attribute, Error, Ident, LitStr, Result, Token, }; +/// Parsed input for `sol!`-like macro expanders. This enum represents a `Sol` file, a JSON ABI, or +/// a Solidity type. #[derive(Clone, Debug)] pub enum SolInputKind { - Sol(ast::File), + /// Solidity type. Type(ast::Type), + /// Solidity file or snippet. + Sol(ast::File), + /// JSON ABI file #[cfg(feature = "json")] Json(Ident, alloy_json_abi::ContractObject), } -// doesn't parse Json impl Parse for SolInputKind { fn parse(input: ParseStream<'_>) -> Result { let fork = input.fork(); @@ -37,10 +39,16 @@ impl Parse for SolInputKind { } } +/// Parsed input for `sol!`-like macro expanders. This struct represents a list +/// of expandable items parsed from either solidity code snippets, or from a +/// JSON abi. #[derive(Clone, Debug)] pub struct SolInput { + /// Attributes attached to the input, of the form `#[...]`. pub attrs: Vec, + /// Path to the input, if any. pub path: Option, + /// The input kind. pub kind: SolInputKind, } @@ -127,39 +135,4 @@ impl SolInput { Ok(Self { attrs, path, kind }) } } - - pub fn expand(self) -> Result { - let Self { attrs, path, kind } = self; - let include = path.map(|p| { - let p = p.to_str().unwrap(); - quote! { const _: &'static [u8] = ::core::include_bytes!(#p); } - }); - - let tokens = match kind { - SolInputKind::Sol(mut file) => { - file.attrs.extend(attrs); - crate::expand::expand(file) - } - SolInputKind::Type(ty) => { - let (sol_attrs, rest) = crate::attr::SolAttrs::parse(&attrs)?; - if !rest.is_empty() { - return Err(Error::new_spanned( - rest.first().unwrap(), - "only `#[sol]` attributes are allowed here", - )); - } - - let mut crates = crate::expand::ExternCrates::default(); - crates.fill(&sol_attrs); - Ok(crate::expand::expand_type(&ty, &crates)) - } - #[cfg(feature = "json")] - SolInputKind::Json(name, json) => crate::json::expand(name, json, attrs), - }?; - - Ok(quote! { - #include - #tokens - }) - } } diff --git a/crates/sol-macro/src/json.rs b/crates/sol-macro-input/src/json.rs similarity index 79% rename from crates/sol-macro/src/json.rs rename to crates/sol-macro-input/src/json.rs index f5f348ce5..a5a4bd9af 100644 --- a/crates/sol-macro/src/json.rs +++ b/crates/sol-macro-input/src/json.rs @@ -1,54 +1,76 @@ +use crate::{SolInput, SolInputKind}; + use alloy_json_abi::{ContractObject, JsonAbi, ToSolConfig}; use proc_macro2::{Ident, TokenStream}; use quote::{quote, TokenStreamExt}; -use syn::{Attribute, Result}; - -pub fn expand(name: Ident, json: ContractObject, attrs: Vec) -> Result { - let ContractObject { abi, bytecode, deployed_bytecode } = json; - - let mut abi = abi.ok_or_else(|| syn::Error::new(name.span(), "ABI not found in JSON"))?; - let sol = abi_to_sol(&name, &mut abi); - let sol_interface_tokens = tokens_for_sol(&name, &sol)?; - let bytecode = bytecode.map(|bytes| { - let s = bytes.to_string(); - quote!(bytecode = #s,) - }); - let deployed_bytecode = deployed_bytecode.map(|bytes| { - let s = bytes.to_string(); - quote!(deployed_bytecode = #s) - }); - - let doc_str = format!( - "\n\n\ -Generated by the following Solidity interface... -```solidity -{sol} -``` - -...which was generated by the following JSON ABI: -```json -{json_s} -```", - json_s = serde_json::to_string_pretty(&abi).unwrap() - ); - let tokens = quote! { - #(#attrs)* - #[doc = #doc_str] - #[sol(#bytecode #deployed_bytecode)] - #sol_interface_tokens - }; +use syn::Result; - let ast = syn::parse2(tokens).map_err(|e| { - let msg = format!( - "failed to parse ABI-generated tokens into a Solidity AST: {e}.\n\ - This is a bug. We would appreciate a bug report: \ - https://github.com/alloy-rs/core/issues/new/choose" +impl SolInput { + /// Normalize JSON ABI inputs into Sol inputs. + pub fn normalize_json(self) -> Result { + if !matches!(self.kind, SolInputKind::Json(..)) { + return Ok(self); + } + + // irrefutable, as we just checked + let SolInput { + attrs, + path, + kind: SolInputKind::Json(name, ContractObject { abi, bytecode, deployed_bytecode }), + } = self + else { + panic!() + }; + + let mut abi = abi.ok_or_else(|| syn::Error::new(name.span(), "ABI not found in JSON"))?; + let sol = abi_to_sol(&name, &mut abi); + let sol_interface_tokens = tokens_for_sol(&name, &sol)?; + let bytecode = bytecode.map(|bytes| { + let s = bytes.to_string(); + quote!(bytecode = #s,) + }); + let deployed_bytecode = deployed_bytecode.map(|bytes| { + let s = bytes.to_string(); + quote!(deployed_bytecode = #s) + }); + + let attrs_iter = attrs.iter(); + let doc_str = format!( + "\n\n\ + Generated by the following Solidity interface... + ```solidity + {sol} + ``` + + ...which was generated by the following JSON ABI: + ```json + {json_s} + ```", + json_s = serde_json::to_string_pretty(&abi).unwrap() ); - syn::Error::new(name.span(), msg) - })?; - crate::expand::expand(ast) + let tokens = quote! { + #(#attrs_iter)* + #[doc = #doc_str] + #[sol(#bytecode #deployed_bytecode)] + #sol_interface_tokens + }; + + let ast: ast::File = syn::parse2(tokens).map_err(|e| { + let msg = format!( + "failed to parse ABI-generated tokens into a Solidity AST: {e}.\n\ + This is a bug. We would appreciate a bug report: \ + https://github.com/alloy-rs/core/issues/new/choose" + ); + syn::Error::new(name.span(), msg) + })?; + + let kind = SolInputKind::Sol(ast); + Ok(SolInput { attrs, path, kind }) + } } +// doesn't parse Json + fn abi_to_sol(name: &Ident, abi: &mut JsonAbi) -> String { abi.dedup(); abi.to_sol(&name.to_string(), Some(ToSolConfig::new().print_constructors(true))) diff --git a/crates/sol-macro-input/src/lib.rs b/crates/sol-macro-input/src/lib.rs new file mode 100644 index 000000000..1402105ee --- /dev/null +++ b/crates/sol-macro-input/src/lib.rs @@ -0,0 +1,30 @@ +//! This crate contains inputs to the `sol!` macro. It sits in-between +//! the `sol-macro` and `syn-solidity` crates, and contains an intermediate +//! representation of Solidity items. These items are then expanded into +//! Rust code by the `alloy-sol-macro` crate. +//! +//! This crate is not meant to be used directly, but rather is a tool for +//! writing macros that generate Rust code from Solidity code. +#![doc( + html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg", + html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico" +)] +#![warn(missing_copy_implementations, missing_debug_implementations, missing_docs, rustdoc::all)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![deny(unused_must_use, rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +/// Tools for working with `#[...]` attributes. +mod attr; +pub use attr::{derives_mapped, docs_str, mk_doc, ContainsSolAttrs, SolAttrs}; + +mod input; +pub use input::{SolInput, SolInputKind}; + +mod expander; +pub use expander::SolInputExpander; + +#[cfg(feature = "json")] +mod json; + +extern crate syn_solidity as ast; diff --git a/crates/sol-macro/Cargo.toml b/crates/sol-macro/Cargo.toml index 063ad79d8..2e68dbe6d 100644 --- a/crates/sol-macro/Cargo.toml +++ b/crates/sol-macro/Cargo.toml @@ -21,13 +21,14 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] +alloy-sol-macro-input.workspace = true + syn-solidity = { workspace = true, features = ["visit", "visit-mut"] } proc-macro2.workspace = true quote.workspace = true syn = { workspace = true, features = ["extra-traits"] } -dunce = "1.0" heck = "0.4" hex.workspace = true indexmap = "2" @@ -36,7 +37,6 @@ tiny-keccak = { workspace = true, features = ["keccak"] } # json alloy-json-abi = { workspace = true, optional = true } -serde_json = { workspace = true, optional = true } [features] -json = ["dep:alloy-json-abi", "dep:serde_json"] +json = ["dep:alloy-json-abi", "alloy-sol-macro-input/json"] diff --git a/crates/sol-macro/src/expand/contract.rs b/crates/sol-macro/src/expand/contract.rs index 66acfbbb5..1dfcf4357 100644 --- a/crates/sol-macro/src/expand/contract.rs +++ b/crates/sol-macro/src/expand/contract.rs @@ -1,7 +1,8 @@ //! [`ItemContract`] expansion. use super::{anon_name, ty, ExpCtxt}; -use crate::{attr, utils::ExprArray}; +use crate::utils::ExprArray; +use alloy_sol_macro_input::{docs_str, mk_doc, ContainsSolAttrs}; use ast::{Item, ItemContract, ItemError, ItemEvent, ItemFunction, SolIdent, Spanned}; use heck::ToSnakeCase; use proc_macro2::{Ident, TokenStream}; @@ -28,9 +29,10 @@ use syn::{parse_quote, Attribute, Result}; /// } /// ``` pub(super) fn expand(cx: &ExpCtxt<'_>, contract: &ItemContract) -> Result { - let ItemContract { attrs, name, body, .. } = contract; + let ItemContract { name, body, .. } = contract; + + let (sol_attrs, attrs) = contract.split_attrs()?; - let (sol_attrs, attrs) = attr::SolAttrs::parse(attrs)?; let extra_methods = sol_attrs.extra_methods.or(cx.attrs.extra_methods).unwrap_or(false); let rpc = sol_attrs.rpc.or(cx.attrs.rpc).unwrap_or(false); let abi = sol_attrs.abi.or(cx.attrs.abi).unwrap_or(false); @@ -158,10 +160,10 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, contract: &ItemContract) -> Result, contract: &ItemContract) -> Result, enumm: &ItemEnum) -> Result { - let ItemEnum { name, variants, attrs, .. } = enumm; + let ItemEnum { name, variants, .. } = enumm; - let (sol_attrs, mut attrs) = crate::attr::SolAttrs::parse(attrs)?; + let (sol_attrs, mut attrs) = enumm.split_attrs()?; cx.derives(&mut attrs, [], false); let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true); @@ -41,7 +41,7 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, enumm: &ItemEnum) -> Result let invalid_variant = has_invalid_variant.then(|| { let comma = (!variants.trailing_punct()).then(syn::token::Comma::default); - let has_serde = attr::derives_mapped(&attrs).any(|path| { + let has_serde = derives_mapped(&attrs).any(|path| { let Some(last) = path.segments.last() else { return false; }; @@ -70,7 +70,7 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, enumm: &ItemEnum) -> Result let uint8 = quote!(alloy_sol_types::sol_data::Uint<8>); let uint8_st = quote!(<#uint8 as alloy_sol_types::SolType>); - let doc = docs.then(|| attr::mk_doc(format!("```solidity\n{enumm}\n```"))); + let doc = docs.then(|| mk_doc(format!("```solidity\n{enumm}\n```"))); let tokens = quote! { #(#attrs)* #doc diff --git a/crates/sol-macro/src/expand/error.rs b/crates/sol-macro/src/expand/error.rs index 06fa22bf0..4fb5c6ecd 100644 --- a/crates/sol-macro/src/expand/error.rs +++ b/crates/sol-macro/src/expand/error.rs @@ -1,7 +1,7 @@ //! [`ItemError`] expansion. use super::{expand_fields, expand_from_into_tuples, expand_tokenize, ExpCtxt}; -use crate::attr; +use alloy_sol_macro_input::{mk_doc, ContainsSolAttrs}; use ast::ItemError; use proc_macro2::TokenStream; use quote::quote; @@ -19,10 +19,10 @@ use syn::Result; /// } /// ``` pub(super) fn expand(cx: &ExpCtxt<'_>, error: &ItemError) -> Result { - let ItemError { parameters: params, name, attrs, .. } = error; + let ItemError { parameters: params, name, .. } = error; cx.assert_resolved(params)?; - let (sol_attrs, mut attrs) = crate::attr::SolAttrs::parse(attrs)?; + let (sol_attrs, mut attrs) = error.split_attrs()?; cx.derives(&mut attrs, params, true); let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true); let abi = sol_attrs.abi.or(cx.attrs.abi).unwrap_or(false); @@ -38,7 +38,7 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, error: &ItemError) -> Result let fields = expand_fields(params, cx); let doc = docs.then(|| { let selector = hex::encode_prefixed(selector.array.as_slice()); - attr::mk_doc(format!( + mk_doc(format!( "Custom error with signature `{signature}` and selector `{selector}`.\n\ ```solidity\n{error}\n```" )) diff --git a/crates/sol-macro/src/expand/event.rs b/crates/sol-macro/src/expand/event.rs index fc11a4285..5538d6c3b 100644 --- a/crates/sol-macro/src/expand/event.rs +++ b/crates/sol-macro/src/expand/event.rs @@ -1,7 +1,7 @@ //! [`ItemEvent`] expansion. use super::{anon_name, expand_event_tokenize, expand_tuple_types, expand_type, ty, ExpCtxt}; -use crate::attr; +use alloy_sol_macro_input::{mk_doc, ContainsSolAttrs}; use ast::{EventParameter, ItemEvent, SolIdent, Spanned}; use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; @@ -19,10 +19,9 @@ use syn::Result; /// } /// ``` pub(super) fn expand(cx: &ExpCtxt<'_>, event: &ItemEvent) -> Result { - let ItemEvent { attrs, .. } = event; let params = event.params(); - let (sol_attrs, mut attrs) = crate::attr::SolAttrs::parse(attrs)?; + let (sol_attrs, mut attrs) = event.split_attrs()?; cx.derives(&mut attrs, ¶ms, true); let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true); let abi = sol_attrs.abi.or(cx.attrs.abi).unwrap_or(false); @@ -109,7 +108,7 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, event: &ItemEvent) -> Result let doc = docs.then(|| { let selector = hex::encode_prefixed(selector.array.as_slice()); - attr::mk_doc(format!( + mk_doc(format!( "Event with signature `{signature}` and selector `{selector}`.\n\ ```solidity\n{event}\n```" )) diff --git a/crates/sol-macro/src/expand/function.rs b/crates/sol-macro/src/expand/function.rs index 987b7b8ff..6a45da6f4 100644 --- a/crates/sol-macro/src/expand/function.rs +++ b/crates/sol-macro/src/expand/function.rs @@ -1,7 +1,7 @@ //! [`ItemFunction`] expansion. use super::{expand_fields, expand_from_into_tuples, expand_tokenize, expand_tuple_types, ExpCtxt}; -use crate::attr; +use alloy_sol_macro_input::{mk_doc, ContainsSolAttrs}; use ast::{FunctionKind, ItemFunction, Spanned}; use proc_macro2::TokenStream; use quote::{format_ident, quote}; @@ -24,7 +24,7 @@ use syn::Result; /// } /// ``` pub(super) fn expand(cx: &ExpCtxt<'_>, function: &ItemFunction) -> Result { - let ItemFunction { attrs, parameters, returns, name, kind, .. } = function; + let ItemFunction { parameters, returns, name, kind, .. } = function; if matches!(kind, FunctionKind::Constructor(_)) { return expand_constructor(cx, function); @@ -42,7 +42,7 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, function: &ItemFunction) -> Result, function: &ItemFunction) -> Result, function: &ItemFunction) -> Result, constructor: &ItemFunction) -> Result { - let ItemFunction { attrs, parameters, .. } = constructor; + let ItemFunction { parameters, .. } = constructor; - let (sol_attrs, call_attrs) = crate::attr::SolAttrs::parse(attrs)?; + let (sol_attrs, call_attrs) = constructor.split_attrs()?; let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true); let alloy_sol_types = &cx.crates.sol_types; @@ -173,7 +173,7 @@ fn expand_constructor(cx: &ExpCtxt<'_>, constructor: &ItemFunction) -> Result ExpCtxt<'ast> { // resolve impl<'ast> ExpCtxt<'ast> { fn parse_file_attributes(&mut self) -> Result<()> { - let (attrs, others) = attr::SolAttrs::parse(&self.ast.attrs)?; + let (attrs, others) = self.ast.split_attrs()?; self.attrs = attrs; self.crates.fill(&self.attrs); diff --git a/crates/sol-macro/src/expand/struct.rs b/crates/sol-macro/src/expand/struct.rs index 270c5586d..a4e486e5c 100644 --- a/crates/sol-macro/src/expand/struct.rs +++ b/crates/sol-macro/src/expand/struct.rs @@ -1,6 +1,7 @@ //! [`ItemStruct`] expansion. -use super::{attr, expand_fields, expand_from_into_tuples, expand_tokenize, expand_type, ExpCtxt}; +use super::{expand_fields, expand_from_into_tuples, expand_tokenize, expand_type, ExpCtxt}; +use alloy_sol_macro_input::{mk_doc, ContainsSolAttrs}; use ast::{Item, ItemStruct, Spanned, Type}; use proc_macro2::TokenStream; use quote::quote; @@ -24,9 +25,10 @@ use syn::Result; /// } /// ``` pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result { - let ItemStruct { name, fields, attrs, .. } = s; + let ItemStruct { name, fields, .. } = s; + + let (sol_attrs, mut attrs) = s.split_attrs()?; - let (sol_attrs, mut attrs) = crate::attr::SolAttrs::parse(attrs)?; cx.derives(&mut attrs, fields, true); let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true); @@ -58,7 +60,7 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result { let name_s = name.as_string(); let fields = expand_fields(fields, cx); - let doc = docs.then(|| attr::mk_doc(format!("```solidity\n{s}\n```"))); + let doc = docs.then(|| mk_doc(format!("```solidity\n{s}\n```"))); let tokens = quote! { #(#attrs)* #doc diff --git a/crates/sol-macro/src/expand/udt.rs b/crates/sol-macro/src/expand/udt.rs index 52256f733..342362e70 100644 --- a/crates/sol-macro/src/expand/udt.rs +++ b/crates/sol-macro/src/expand/udt.rs @@ -2,15 +2,16 @@ use super::{ty::expand_rust_type, ExpCtxt}; use crate::expand::expand_type; +use alloy_sol_macro_input::ContainsSolAttrs; use ast::ItemUdt; use proc_macro2::TokenStream; use quote::quote; use syn::Result; pub(super) fn expand(cx: &ExpCtxt<'_>, udt: &ItemUdt) -> Result { - let ItemUdt { name, ty, attrs, .. } = udt; + let ItemUdt { name, ty, .. } = udt; - let (sol_attrs, mut attrs) = crate::attr::SolAttrs::parse(attrs)?; + let (sol_attrs, mut attrs) = udt.split_attrs()?; cx.type_derives(&mut attrs, std::iter::once(ty), true); let underlying_sol = expand_type(ty, &cx.crates); diff --git a/crates/sol-macro/src/lib.rs b/crates/sol-macro/src/lib.rs index 120a446ff..15fead089 100644 --- a/crates/sol-macro/src/lib.rs +++ b/crates/sol-macro/src/lib.rs @@ -20,20 +20,17 @@ extern crate proc_macro_error; extern crate syn_solidity as ast; +use alloy_sol_macro_input::{SolAttrs, SolInput, SolInputExpander, SolInputKind}; use proc_macro::TokenStream; +use quote::quote; use syn::parse_macro_input; -mod attr; mod expand; -mod input; mod utils; #[cfg(feature = "json")] mod verbatim; -#[cfg(feature = "json")] -mod json; - /// Generate types that implement [`alloy-sol-types`] traits, which can be used /// for type-safe [ABI] and [EIP-712] serialization to interface with Ethereum /// smart contracts. @@ -95,11 +92,11 @@ mod json; /// construct `eth_call`s to an on-chain contract through Ethereum JSON RPC, similar to the /// default behavior of [`abigen`]. This makes use of the [`alloy-contract`](https://github.com/alloy-rs/alloy) /// crate. -/// +/// /// N.B: at the time of writing, the `alloy-contract` crate is not yet released on `crates.io`, /// and its API is completely unstable and subject to change, so this feature is not yet /// recommended for use. -/// +/// /// Generates: /// - `struct {name}Instance { ... }` /// - `pub fn new(...) -> {name}Instance

` + getters and setters @@ -233,8 +230,52 @@ mod json; #[proc_macro] #[proc_macro_error] pub fn sol(input: TokenStream) -> TokenStream { - parse_macro_input!(input as input::SolInput) - .expand() - .unwrap_or_else(syn::Error::into_compile_error) - .into() + let input = parse_macro_input!(input as alloy_sol_macro_input::SolInput); + + SolMacroExpander.expand(&input).unwrap_or_else(syn::Error::into_compile_error).into() +} + +struct SolMacroExpander; + +impl SolInputExpander for SolMacroExpander { + fn expand(&mut self, input: &SolInput) -> syn::Result { + let input = input.clone(); + // Convert JSON input to Solidity input + + #[cfg(feature = "json")] + let input = input.normalize_json()?; + + let SolInput { attrs, path, kind } = input; + let include = path.map(|p| { + let p = p.to_str().unwrap(); + quote! { const _: &'static [u8] = ::core::include_bytes!(#p); } + }); + + let tokens = match kind { + SolInputKind::Sol(mut file) => { + file.attrs.extend(attrs); + crate::expand::expand(file) + } + SolInputKind::Type(ty) => { + let (sol_attrs, rest) = SolAttrs::parse(&attrs)?; + if !rest.is_empty() { + return Err(syn::Error::new_spanned( + rest.first().unwrap(), + "only `#[sol]` attributes are allowed here", + )); + } + + let mut crates = crate::expand::ExternCrates::default(); + crates.fill(&sol_attrs); + Ok(crate::expand::expand_type(&ty, &crates)) + } + #[cfg(feature = "json")] + SolInputKind::Json(_, _) => unreachable!("input already normalized"), + }?; + + Ok(quote! { + #include + #tokens + }) + } }