Skip to content
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

refactor: break SolInput to its own crate #578

Merged
merged 9 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
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
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