Skip to main content

alloy_sol_macro_expander/expand/
to_abi.rs

1use super::ExpCtxt;
2use crate::verbatim::Verbatim;
3use alloy_json_abi::{
4    Constructor, Error, Event, EventParam, Fallback, Function, Param, Receive, StateMutability,
5};
6use ast::{ItemError, ItemEvent, ItemFunction};
7use proc_macro2::TokenStream;
8use quote::quote;
9use std::fmt::Write;
10
11pub(crate) fn generate<T>(t: &T, cx: &ExpCtxt<'_>) -> TokenStream
12where
13    T: ToAbi,
14    T::DynAbi: Verbatim,
15{
16    crate::verbatim::verbatim(&t.to_dyn_abi(cx), &cx.crates)
17}
18
19pub(crate) trait ToAbi {
20    type DynAbi;
21
22    fn to_dyn_abi(&self, cx: &ExpCtxt<'_>) -> Self::DynAbi;
23}
24
25impl ToAbi for ast::ItemFunction {
26    type DynAbi = Function;
27
28    fn to_dyn_abi(&self, cx: &ExpCtxt<'_>) -> Self::DynAbi {
29        Function {
30            name: self.name.as_ref().map(|i| i.as_string()).unwrap_or_default(),
31            inputs: self.parameters.to_dyn_abi(cx),
32            outputs: self.returns.as_ref().map(|r| r.returns.to_dyn_abi(cx)).unwrap_or_default(),
33            state_mutability: self.attributes.to_dyn_abi(cx),
34        }
35    }
36}
37
38impl ToAbi for ast::ItemError {
39    type DynAbi = Error;
40
41    fn to_dyn_abi(&self, cx: &ExpCtxt<'_>) -> Self::DynAbi {
42        Error { name: self.name.as_string(), inputs: self.parameters.to_dyn_abi(cx) }
43    }
44}
45
46impl ToAbi for ast::ItemEvent {
47    type DynAbi = Event;
48
49    fn to_dyn_abi(&self, cx: &ExpCtxt<'_>) -> Self::DynAbi {
50        Event {
51            name: self.name.as_string(),
52            inputs: self.parameters.iter().map(|e| e.to_dyn_abi(cx)).collect(),
53            anonymous: self.is_anonymous(),
54        }
55    }
56}
57
58impl<P> ToAbi for ast::Parameters<P> {
59    type DynAbi = Vec<Param>;
60
61    fn to_dyn_abi(&self, cx: &ExpCtxt<'_>) -> Self::DynAbi {
62        self.iter().map(|p| p.to_dyn_abi(cx)).collect()
63    }
64}
65
66impl ToAbi for ast::VariableDeclaration {
67    type DynAbi = Param;
68
69    fn to_dyn_abi(&self, cx: &ExpCtxt<'_>) -> Self::DynAbi {
70        ty_to_param(self.name.as_ref().map(ast::SolIdent::as_string), &self.ty, cx)
71    }
72}
73
74impl ToAbi for ast::EventParameter {
75    type DynAbi = EventParam;
76
77    fn to_dyn_abi(&self, cx: &ExpCtxt<'_>) -> Self::DynAbi {
78        let name = self.name.as_ref().map(ast::SolIdent::as_string);
79        let Param { ty, name, components, internal_type } = ty_to_param(name, &self.ty, cx);
80        EventParam { ty, name, indexed: self.is_indexed(), internal_type, components }
81    }
82}
83
84impl ToAbi for ast::FunctionAttributes {
85    type DynAbi = StateMutability;
86
87    fn to_dyn_abi(&self, _cx: &ExpCtxt<'_>) -> Self::DynAbi {
88        match self.mutability() {
89            Some(ast::Mutability::Pure(_) | ast::Mutability::Constant(_)) => StateMutability::Pure,
90            Some(ast::Mutability::View(_)) => StateMutability::View,
91            Some(ast::Mutability::Payable(_)) => StateMutability::Payable,
92            None => StateMutability::NonPayable,
93        }
94    }
95}
96
97fn ty_to_param(name: Option<String>, ty: &ast::Type, cx: &ExpCtxt<'_>) -> Param {
98    let mut ty_name = ty_abi_string(ty, cx);
99
100    // HACK: `cx.custom_type` resolves the custom type recursively, so in recursive structs the
101    // peeled `ty` will be `Tuple` rather than `Custom`.
102    if ty_name.starts_with('(') {
103        let paren_i = ty_name.rfind(')').expect("malformed tuple type");
104        let suffix = &ty_name[paren_i + 1..];
105        ty_name = format!("tuple{suffix}");
106    }
107
108    let resolved = match ty.peel_arrays() {
109        ast::Type::Custom(custom_name) => {
110            if let ast::Item::Struct(s) = cx.item(custom_name) {
111                return Param {
112                    ty: ty_name,
113                    name: name.unwrap_or_default(),
114                    internal_type: None,
115                    components: s.fields.to_dyn_abi(cx),
116                };
117            }
118            cx.custom_type(custom_name)
119        }
120        ty => ty,
121    };
122
123    let components = if let ast::Type::Tuple(tuple) = resolved {
124        tuple.types.iter().map(|ty| ty_to_param(None, ty, cx)).collect()
125    } else {
126        vec![]
127    };
128
129    // TODO: internal_type
130    let internal_type = None;
131
132    Param { ty: ty_name, name: name.unwrap_or_default(), internal_type, components }
133}
134
135fn ty_abi_string(ty: &ast::Type, cx: &ExpCtxt<'_>) -> String {
136    let mut suffix = String::new();
137    rec_ty_abi_string_suffix(cx, ty, &mut suffix);
138
139    let mut ty = ty.peel_arrays();
140    if let ast::Type::Custom(name) = ty {
141        match cx.try_custom_type(name) {
142            Some(ast::Type::Tuple(_)) => return format!("tuple{suffix}"),
143            Some(custom) => ty = custom,
144            None => {}
145        }
146    }
147    format!("{}{suffix}", super::ty::TypePrinter::new(cx, ty))
148}
149
150fn rec_ty_abi_string_suffix(cx: &ExpCtxt<'_>, ty: &ast::Type, s: &mut String) {
151    if let ast::Type::Array(array) = ty {
152        rec_ty_abi_string_suffix(cx, &array.ty, s);
153        if let Some(size) = cx.eval_array_size(array) {
154            write!(s, "[{size}]").unwrap();
155        } else {
156            s.push_str("[]");
157        }
158    }
159}
160
161pub(super) fn constructor(function: &ItemFunction, cx: &ExpCtxt<'_>) -> Constructor {
162    assert!(function.kind.is_constructor());
163    Constructor {
164        inputs: function.parameters.to_dyn_abi(cx),
165        state_mutability: function.attributes.to_dyn_abi(cx),
166    }
167}
168
169pub(super) fn fallback(function: &ItemFunction, cx: &ExpCtxt<'_>) -> Fallback {
170    assert!(function.kind.is_fallback());
171    Fallback { state_mutability: function.attributes.to_dyn_abi(cx) }
172}
173
174pub(super) fn receive(function: &ItemFunction, _cx: &ExpCtxt<'_>) -> Receive {
175    assert!(function.kind.is_receive());
176    Receive { state_mutability: StateMutability::Payable }
177}
178
179macro_rules! make_map {
180    ($items:expr, $cx:expr, $get_ident:ident, $ty:ident) => {{
181        let mut items_map = std::collections::BTreeMap::<String, Vec<_>>::new();
182        let alloy_sol_types = &$cx.crates.sol_types;
183        for item in $items {
184            let name = item.to_dyn_abi($cx).name;
185            let ident = $cx.$get_ident(item.into());
186            let item = quote::quote!(<#ident as #alloy_sol_types::JsonAbiExt>::abi);
187            items_map.entry(name).or_default().push(item);
188        }
189        let items = items_map.into_iter().map(|(name, items)| {
190            quote!((#name, &[#(#items),*]))
191        });
192        quote! {
193            static LAZY_ITEMS: &[(&str, &[fn() -> #alloy_sol_types::private::alloy_json_abi::$ty])] = &[#(#items,)*];
194            let items = LAZY_ITEMS
195                .iter()
196                .map(|(name, item_fns)| (
197                    alloy_sol_types::private::str_to_owned(name),
198                    item_fns.iter().map(|item_fn| item_fn()).collect()
199                ))
200                .collect();
201            alloy_sol_types::private::make_btree_map(items)
202        }
203    }};
204}
205
206pub(super) fn functions_map(functions: &[ItemFunction], cx: &ExpCtxt<'_>) -> TokenStream {
207    make_map!(functions, cx, call_name, Function)
208}
209
210pub(super) fn events_map(events: &[&ItemEvent], cx: &ExpCtxt<'_>) -> TokenStream {
211    make_map!(events.iter().copied(), cx, overloaded_name, Event)
212}
213
214pub(super) fn errors_map(errors: &[&ItemError], cx: &ExpCtxt<'_>) -> TokenStream {
215    make_map!(errors.iter().copied(), cx, overloaded_name, Error)
216}