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

feat(sol!)!: generate unit/tuple structs for errors with 0/1 param #883

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
35 changes: 29 additions & 6 deletions crates/sol-macro-expander/src/expand/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, error: &ItemError) -> Result<TokenStream>
let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true);
let abi = sol_attrs.abi.or(cx.attrs.abi).unwrap_or(false);

let tokenize_impl = expand_tokenize(params, cx);
let tokenize_impl = expand_tokenize(params, cx, super::FieldKind::Deconstruct);

let name = cx.overloaded_name(error.into());
let signature = cx.error_signature(error);
let selector = crate::utils::selector(&signature);

let alloy_sol_types = &cx.crates.sol_types;

let converts = expand_from_into_tuples(&name.0, params, cx);
let fields = expand_fields(params, cx);
let converts = expand_from_into_tuples(&name.0, params, cx, super::FieldKind::Deconstruct);

let doc = docs.then(|| {
let selector = hex::encode_prefixed(selector.array.as_slice());
mk_doc(format!(
Expand All @@ -60,14 +60,37 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, error: &ItemError) -> Result<TokenStream>
}
}
});

let err_struct = match params.len() {
0 => {
// Expanded as a unit struct.
quote! {
pub struct #name;
}
}
1 if params[0].name.is_none() => {
let ty = cx.expand_rust_type(&params[0].ty);
// Expanded as tuple struct if only one _unnamed_ parameter.
quote! {
pub struct #name(pub #ty);
}
}
_ => {
let fields = expand_fields(params, cx);
quote! {
pub struct #name {
#(#fields),*
}
}
}
};

let tokens = quote! {
#(#attrs)*
#doc
#[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)]
#[derive(Clone)]
pub struct #name {
#(#fields),*
}
#err_struct

#[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields, clippy::style)]
const _: () = {
Expand Down
7 changes: 6 additions & 1 deletion crates/sol-macro-expander/src/expand/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,12 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, event: &ItemEvent) -> Result<TokenStream>
}
});

let tokenize_body_impl = expand_event_tokenize(&event.parameters, cx);
let tokenize_body_impl = expand_event_tokenize(
&event.parameters,
cx,
event.parameters.len(),
super::FieldKind::Original,
);

let encode_topics_impl = encode_first_topic
.into_iter()
Expand Down
14 changes: 8 additions & 6 deletions crates/sol-macro-expander/src/expand/function.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! [`ItemFunction`] expansion.

use super::{expand_fields, expand_from_into_tuples, expand_tokenize, expand_tuple_types, ExpCtxt};
use super::{
expand_fields, expand_from_into_tuples, expand_tokenize, expand_tuple_types, ExpCtxt, FieldKind,
};
use alloy_sol_macro_input::{mk_doc, ContainsSolAttrs};
use ast::{FunctionKind, ItemFunction, Spanned};
use proc_macro2::TokenStream;
Expand Down Expand Up @@ -60,12 +62,12 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, function: &ItemFunction) -> Result<TokenS
let call_tuple = expand_tuple_types(parameters.types(), cx).0;
let return_tuple = expand_tuple_types(returns.types(), cx).0;

let converts = expand_from_into_tuples(&call_name, parameters, cx);
let return_converts = expand_from_into_tuples(&return_name, returns, cx);
let converts = expand_from_into_tuples(&call_name, parameters, cx, FieldKind::Original);
let return_converts = expand_from_into_tuples(&return_name, returns, cx, FieldKind::Original);

let signature = cx.function_signature(function);
let selector = crate::utils::selector(&signature);
let tokenize_impl = expand_tokenize(parameters, cx);
let tokenize_impl = expand_tokenize(parameters, cx, FieldKind::Original);

let call_doc = docs.then(|| {
let selector = hex::encode_prefixed(selector.array.as_slice());
Expand Down Expand Up @@ -169,8 +171,8 @@ fn expand_constructor(cx: &ExpCtxt<'_>, constructor: &ItemFunction) -> Result<To
let call_name = format_ident!("constructorCall").with_span(constructor.kind.span());
let call_fields = expand_fields(parameters, cx);
let call_tuple = expand_tuple_types(parameters.types(), cx).0;
let converts = expand_from_into_tuples(&call_name, parameters, cx);
let tokenize_impl = expand_tokenize(parameters, cx);
let converts = expand_from_into_tuples(&call_name, parameters, cx, FieldKind::Original);
let tokenize_impl = expand_tokenize(parameters, cx, FieldKind::Original);

let call_doc = docs.then(|| {
mk_doc(format!(
Expand Down
92 changes: 83 additions & 9 deletions crates/sol-macro-expander/src/expand/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -800,11 +800,57 @@ pub fn anon_name<T: Into<Ident> + Clone>((i, name): (usize, Option<&T>)) -> Iden
}
}

/// Utility type to determining how a solidity type should be expanded.
enum FieldKind {
/// The type should be expanded as is. i.e a struct with named/unnamed fields.
///
/// e.g
/// ```ignore
/// sol! {
/// error MyError(uint256, string);
/// }
/// ```
/// Expands to:
///
/// ```ignore
/// struct MyError {
/// pub _0: U256,
/// pub _1: String,
/// }
Original,
/// The types should be deconstructed and expanded into a unit/tuple struct depending on the
/// number of params.
///
/// e.g
/// ```ignore
/// sol! {
/// error MyError(uint256 value);
/// error UnitError();
/// }
/// ```
///
/// Expands to:
///
/// ```ignore
/// struct MyError(pub U256);
/// struct UnitError();
/// ```
Deconstruct,
}

impl FieldKind {
/// Returns whether the field kind is `Deconstruct`.
fn is_deconstruct(&self) -> bool {
matches!(self, Self::Deconstruct)
}
}

/// Expands `From` impls for a list of types and the corresponding tuple.
fn expand_from_into_tuples<P>(
name: &Ident,
fields: &Parameters<P>,
cx: &ExpCtxt<'_>,
field_kind: FieldKind,
) -> TokenStream {
let names = fields.names().enumerate().map(anon_name);

Expand All @@ -813,6 +859,15 @@ fn expand_from_into_tuples<P>(

let (sol_tuple, rust_tuple) = expand_tuple_types(fields.types(), cx);

let (from_sol_type, from_rust_tuple) = if fields.is_empty() && field_kind.is_deconstruct() {
(quote!(()), quote!(Self))
} else if fields.len() == 1 && fields[0].name.is_none() && field_kind.is_deconstruct() {
let idxs2 = (0..fields.len()).map(syn::Index::from);
(quote!((#(value.#idxs),*,)), quote!(Self(#(tuple.#idxs2),*)))
} else {
(quote!((#(value.#names,)*)), quote!(Self { #(#names2: tuple.#idxs),* }))
};

quote! {
#[doc(hidden)]
type UnderlyingSolTuple<'a> = #sol_tuple;
Expand All @@ -831,17 +886,15 @@ fn expand_from_into_tuples<P>(
#[doc(hidden)]
impl ::core::convert::From<#name> for UnderlyingRustTuple<'_> {
fn from(value: #name) -> Self {
(#(value.#names,)*)
#from_sol_type
}
}

#[automatically_derived]
#[doc(hidden)]
impl ::core::convert::From<UnderlyingRustTuple<'_>> for #name {
fn from(tuple: UnderlyingRustTuple<'_>) -> Self {
Self {
#(#names2: tuple.#idxs),*
}
#from_rust_tuple
}
}
}
Expand All @@ -868,14 +921,25 @@ fn expand_tuple_types<'a, I: IntoIterator<Item = &'a Type>>(
}

/// Expand the body of a `tokenize` function.
fn expand_tokenize<P>(params: &Parameters<P>, cx: &ExpCtxt<'_>) -> TokenStream {
tokenize_(params.iter().enumerate().map(|(i, p)| (i, &p.ty, p.name.as_ref())), cx)
fn expand_tokenize<P>(
params: &Parameters<P>,
cx: &ExpCtxt<'_>,
field_kind: FieldKind,
) -> TokenStream {
tokenize_(
params.iter().enumerate().map(|(i, p)| (i, &p.ty, p.name.as_ref())),
cx,
params.len(),
field_kind,
)
}

/// Expand the body of a `tokenize` function.
fn expand_event_tokenize<'a>(
params: impl IntoIterator<Item = &'a EventParameter>,
cx: &ExpCtxt<'_>,
params_len: usize,
field_kind: FieldKind,
) -> TokenStream {
tokenize_(
params
Expand All @@ -884,18 +948,28 @@ fn expand_event_tokenize<'a>(
.filter(|(_, p)| !p.is_indexed())
.map(|(i, p)| (i, &p.ty, p.name.as_ref())),
cx,
params_len,
field_kind,
)
}

fn tokenize_<'a>(
iter: impl Iterator<Item = (usize, &'a Type, Option<&'a SolIdent>)>,
cx: &'a ExpCtxt<'_>,
params_len: usize,
field_kind: FieldKind,
) -> TokenStream {
let statements = iter.into_iter().map(|(i, ty, name)| {
let ty = cx.expand_type(ty);
let name = name.cloned().unwrap_or_else(|| generate_name(i).into());
quote! {
<#ty as alloy_sol_types::SolType>::tokenize(&self.#name)
if params_len == 1 && name.is_none() && field_kind.is_deconstruct() {
quote! {
<#ty as alloy_sol_types::SolType>::tokenize(&self.0)
}
} else {
let name = name.cloned().unwrap_or_else(|| generate_name(i).into());
quote! {
<#ty as alloy_sol_types::SolType>::tokenize(&self.#name)
}
}
});
quote! {
Expand Down
4 changes: 2 additions & 2 deletions crates/sol-macro-expander/src/expand/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result<TokenStream> {

let eip712_encode_type_fns = expand_encode_type_fns(cx, fields, name);

let tokenize_impl = expand_tokenize(fields, cx);
let tokenize_impl = expand_tokenize(fields, cx, super::FieldKind::Original);

let encode_data_impl = match fields.len() {
0 => unreachable!("struct with zero fields"),
Expand All @@ -56,7 +56,7 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result<TokenStream> {
let alloy_sol_types = &cx.crates.sol_types;

let attrs = attrs.iter();
let convert = expand_from_into_tuples(&name.0, fields, cx);
let convert = expand_from_into_tuples(&name.0, fields, cx, super::FieldKind::Original);
let name_s = name.as_string();
let fields = expand_fields(fields, cx);

Expand Down
2 changes: 1 addition & 1 deletion crates/sol-types/src/types/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ mod tests {
let C::CErrors::SenderAddressError(decoded) = C::CErrors::abi_decode(&data, true).unwrap();
assert_eq!(
decoded,
C::SenderAddressError { _0: address!("0xa48388222c7ee7daefde5d0b9c99319995c4a990") }
C::SenderAddressError(address!("0xa48388222c7ee7daefde5d0b9c99319995c4a990"))
);
}
}
4 changes: 2 additions & 2 deletions crates/sol-types/src/types/interface/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ mod tests {
assert_eq!(C::CErrors::abi_decode(&data, true), Ok(errors_err1()));
assert_eq!(ContractError::<C::CErrors>::abi_decode(&data, true), Ok(contract_error_err1()));

let err2 = || C::Err2 { _0: U256::from(42) };
let err2 = || C::Err2(U256::from(42));
let errors_err2 = || C::CErrors::Err2(err2());
let contract_error_err2 = || ContractError::<C::CErrors>::CustomError(errors_err2());
let data = err2().abi_encode();
Expand All @@ -649,7 +649,7 @@ mod tests {
assert_eq!(C::CErrors::abi_decode(&data, true), Ok(errors_err2()));
assert_eq!(ContractError::<C::CErrors>::abi_decode(&data, true), Ok(contract_error_err2()));

let err3 = || C::Err3 { _0: "hello".into() };
let err3 = || C::Err3("hello".into());
let errors_err3 = || C::CErrors::Err3(err3());
let contract_error_err3 = || ContractError::<C::CErrors>::CustomError(errors_err3());
let data = err3().abi_encode();
Expand Down
1 change: 1 addition & 0 deletions crates/sol-types/tests/macros/sol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,7 @@ fn contract_derive_default() {
let MyContract::f2Call {} = MyContract::f2Call::default();
let MyContract::e1 {} = MyContract::e1::default();
let MyContract::e2 {} = MyContract::e2::default();
#[allow(clippy::default_constructed_unit_structs)]
let MyContract::c {} = MyContract::c::default();
}

Expand Down