Skip to content

refactor: break SolInput to its own crate #578

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 20, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }
30 changes: 30 additions & 0 deletions crates/sol-macro-input/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"]
Original file line number Diff line number Diff line change
@@ -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<Item = &Attribute> {
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<Item = &Attribute> {
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<Item = Path> + '_ {
derives(attrs).flat_map(|attr| {
attr.parse_args_with(Punctuated::<Path, Token![,]>::parse_terminated).unwrap_or_default()
@@ -59,34 +66,46 @@ pub fn derives_mapped(attrs: &[Attribute]) -> impl Iterator<Item = Path> + '_ {
// 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<bool>,
/// `#[sol(abi)]`
pub abi: Option<bool>,
/// `#[sol(all_derives)]`
pub all_derives: Option<bool>,
/// `#[sol(extra_methods)]`
pub extra_methods: Option<bool>,
/// `#[sol(docs)]`
pub docs: Option<bool>,

/// `#[sol(alloy_sol_types = alloy_core::sol_types)]`
pub alloy_sol_types: Option<Path>,
/// `#[sol(alloy_contract = alloy_contract)]`
pub alloy_contract: Option<Path>,

// TODO: Implement
/// UNIMPLEMENTED: `#[sol(rename = "new_name")]`
pub rename: Option<LitStr>,
// TODO: Implement
/// UNIMPLMENTED: `#[sol(rename_all = "camelCase")]`
pub rename_all: Option<CasingStyle>,

/// `#[sol(bytecode = "0x1234")]`
pub bytecode: Option<LitStr>,
/// `#[sol(deployed_bytecode = "0x1234")]`
pub deployed_bytecode: Option<LitStr>,

/// UDVT only `#[sol(type_check = "my_function")]`
pub type_check: Option<LitStr>,
}

impl SolAttrs {
/// Parse the `#[sol(...)]` attributes from a list of attributes.
pub fn parse(attrs: &[Attribute]) -> Result<(Self, Vec<Attribute>)> {
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<Attribute>)> {
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 {
9 changes: 9 additions & 0 deletions crates/sol-macro-input/src/expander.rs
Original file line number Diff line number Diff line change
@@ -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<TokenStream>;
}
Original file line number Diff line number Diff line change
@@ -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<Self> {
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<Attribute>,
/// Path to the input, if any.
pub path: Option<PathBuf>,
/// The input kind.
pub kind: SolInputKind,
}

@@ -127,39 +135,4 @@ impl SolInput {
Ok(Self { attrs, path, kind })
}
}

pub fn expand(self) -> Result<TokenStream> {
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
})
}
}
110 changes: 66 additions & 44 deletions crates/sol-macro/src/json.rs → crates/sol-macro-input/src/json.rs
Original file line number Diff line number Diff line change
@@ -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<Attribute>) -> Result<TokenStream> {
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<Self> {
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)))
30 changes: 30 additions & 0 deletions crates/sol-macro-input/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move above mods?

6 changes: 3 additions & 3 deletions crates/sol-macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"]
20 changes: 11 additions & 9 deletions crates/sol-macro/src/expand/contract.rs
Original file line number Diff line number Diff line change
@@ -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<TokenStream> {
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<TokenS
enum_expander.expand(ToExpand::Events(&events), attrs)
});

let mod_descr_doc = (docs && attr::docs_str(&mod_attrs).trim().is_empty())
.then(|| attr::mk_doc("Module containing a contract's types and functions."));
let mod_iface_doc = (docs && !attr::docs_str(&mod_attrs).contains("```solidity\n"))
.then(|| attr::mk_doc(format!("\n\n```solidity\n{contract}\n```")));
let mod_descr_doc = (docs && docs_str(&mod_attrs).trim().is_empty())
.then(|| mk_doc("Module containing a contract's types and functions."));
let mod_iface_doc = (docs && !docs_str(&mod_attrs).contains("```solidity\n"))
.then(|| mk_doc(format!("\n\n```solidity\n{contract}\n```")));

let abi = abi.then(|| {
if_json! {
@@ -257,15 +259,15 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, contract: &ItemContract) -> Result<TokenS
Returns a new instance of the contract, if the deployment was successful.\n\
\n\
For more fine-grained control over the deployment process, use [`deploy_builder`] instead.";
let deploy_doc = attr::mk_doc(deploy_doc_str);
let deploy_doc = mk_doc(deploy_doc_str);

let deploy_builder_doc_str =
"Creates a `RawCallBuilder` for deploying this contract using the given `provider`\n\
and constructor arguments, if any.\n\
\n\
This is a simple wrapper around creating a `RawCallBuilder` with the data set to\n\
the bytecode concatenated with the constructor's ABI-encoded arguments.";
let deploy_builder_doc = attr::mk_doc(deploy_builder_doc_str);
let deploy_builder_doc = mk_doc(deploy_builder_doc_str);

let (params, args) = option_unzip(constructor.and_then(|c| {
if c.parameters.is_empty() {
10 changes: 5 additions & 5 deletions crates/sol-macro/src/expand/enum.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! [`ItemEnum`] expansion.
use super::ExpCtxt;
use crate::attr;
use alloy_sol_macro_input::{derives_mapped, mk_doc, ContainsSolAttrs};
use ast::{ItemEnum, Spanned};
use proc_macro2::TokenStream;
use quote::quote;
@@ -20,9 +20,9 @@ use syn::Result;
/// }
/// ```
pub(super) fn expand(cx: &ExpCtxt<'_>, enumm: &ItemEnum) -> Result<TokenStream> {
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<TokenStream>
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<TokenStream>
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
8 changes: 4 additions & 4 deletions crates/sol-macro/src/expand/error.rs
Original file line number Diff line number Diff line change
@@ -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<TokenStream> {
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<TokenStream>
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```"
))
7 changes: 3 additions & 4 deletions crates/sol-macro/src/expand/event.rs
Original file line number Diff line number Diff line change
@@ -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<TokenStream> {
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, &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);
@@ -109,7 +108,7 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, event: &ItemEvent) -> Result<TokenStream>

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```"
))
16 changes: 8 additions & 8 deletions crates/sol-macro/src/expand/function.rs
Original file line number Diff line number Diff line change
@@ -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<TokenStream> {
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<TokenS
cx.assert_resolved(returns)?;
}

let (sol_attrs, mut call_attrs) = crate::attr::SolAttrs::parse(attrs)?;
let (sol_attrs, mut call_attrs) = function.split_attrs()?;
let mut return_attrs = call_attrs.clone();
cx.derives(&mut call_attrs, parameters, true);
if !returns.is_empty() {
@@ -69,13 +69,13 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, function: &ItemFunction) -> Result<TokenS

let call_doc = docs.then(|| {
let selector = hex::encode_prefixed(selector.array.as_slice());
attr::mk_doc(format!(
mk_doc(format!(
"Function with signature `{signature}` and selector `{selector}`.\n\
```solidity\n{function}\n```"
))
});
let return_doc = docs.then(|| {
attr::mk_doc(format!(
mk_doc(format!(
"Container type for the return parameters of the [`{signature}`]({call_name}) function."
))
});
@@ -159,9 +159,9 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, function: &ItemFunction) -> Result<TokenS
}

fn expand_constructor(cx: &ExpCtxt<'_>, constructor: &ItemFunction) -> Result<TokenStream> {
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<To
let tokenize_impl = expand_tokenize(parameters, cx);

let call_doc = docs.then(|| {
attr::mk_doc(format!(
mk_doc(format!(
"Constructor`.\n\
```solidity\n{constructor}\n```"
))
4 changes: 2 additions & 2 deletions crates/sol-macro/src/expand/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! Functions which generate Rust code from the Solidity AST.
use crate::{
attr::{self, SolAttrs},
expand::ty::expand_rust_type,
utils::{self, ExprArray},
};
use alloy_sol_macro_input::{ContainsSolAttrs, SolAttrs};
use ast::{
EventParameter, File, Item, ItemError, ItemEvent, ItemFunction, Parameters, SolIdent, SolPath,
Spanned, Type, VariableDeclaration, Visit,
@@ -124,7 +124,7 @@ impl<'ast> 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);

10 changes: 6 additions & 4 deletions crates/sol-macro/src/expand/struct.rs
Original file line number Diff line number Diff line change
@@ -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<TokenStream> {
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<TokenStream> {
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
5 changes: 3 additions & 2 deletions crates/sol-macro/src/expand/udt.rs
Original file line number Diff line number Diff line change
@@ -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<TokenStream> {
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);
63 changes: 52 additions & 11 deletions crates/sol-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<P: Provider> { ... }`
/// - `pub fn new(...) -> {name}Instance<P>` + 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<proc_macro2::TokenStream> {
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
})
}
}