Skip to content

Commit

Permalink
refactor: break SolInput to its own crate (#578)
Browse files Browse the repository at this point in the history
* refactor: break SolInputs to its own crate

* fix: explicit quote import

* nits: cargo.toml keys for new package

* nit: newline

* refator: push attr down

* nit: remove empty file

* refactor: introduce ContainsSolAttrs

* docs: delete broken link

* fix: migrate udt and fix docs
  • Loading branch information
prestwich authored Mar 20, 2024
1 parent 6ace151 commit d13ea47
Show file tree
Hide file tree
Showing 17 changed files with 327 additions and 138 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
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
Expand Up @@ -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))
}
Expand Down Expand Up @@ -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()
Expand All @@ -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());
Expand Down Expand Up @@ -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 {
Expand Down
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();
Expand All @@ -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,
}

Expand Down Expand Up @@ -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
})
}
}
Loading

0 comments on commit d13ea47

Please sign in to comment.