Skip to main content

alloy_sol_macro_expander/expand/
ty.rs

1//! [`Type`] expansion.
2
3use super::ExpCtxt;
4use ast::{Item, Parameters, SolIdent, Spanned, Type, TypeArray};
5use proc_macro_error3::{abort, emit_error};
6use proc_macro2::{Ident, Literal, Span, TokenStream};
7use quote::{ToTokens, quote_spanned};
8use std::{fmt, num::NonZeroU16};
9
10const MAX_SUPPORTED_ARRAY_LEN: usize = 32;
11const MAX_SUPPORTED_TUPLE_LEN: usize = 12;
12
13impl ExpCtxt<'_> {
14    /// Expands a single [`Type`] recursively to its `alloy_sol_types::sol_data`
15    /// equivalent.
16    pub fn expand_type(&self, ty: &Type) -> TokenStream {
17        let mut tokens = TokenStream::new();
18        self.expand_type_to(ty, &mut tokens);
19        tokens
20    }
21
22    /// Expands a single [`Type`] recursively to its Rust type equivalent.
23    ///
24    /// This is the same as `<#expand_type(ty) as SolType>::RustType`, but generates
25    /// nicer code for documentation and IDE/LSP support when the type is not
26    /// ambiguous.
27    pub fn expand_rust_type(&self, ty: &Type) -> TokenStream {
28        let mut tokens = TokenStream::new();
29        self.expand_rust_type_to(ty, &mut tokens);
30        tokens
31    }
32
33    /// Expands a single [`Type`] recursively to its `alloy_sol_types::sol_data` equivalent into the
34    /// given buffer.
35    ///
36    /// See [`expand_type`](Self::expand_type) for more information.
37    pub fn expand_type_to(&self, ty: &Type, tokens: &mut TokenStream) {
38        let alloy_sol_types = &self.crates.sol_types;
39        let tts = match *ty {
40            Type::Address(span, _) => quote_spanned! {span=> #alloy_sol_types::sol_data::Address },
41            Type::Bool(span) => quote_spanned! {span=> #alloy_sol_types::sol_data::Bool },
42            Type::String(span) => quote_spanned! {span=> #alloy_sol_types::sol_data::String },
43            Type::Bytes(span) => quote_spanned! {span=> #alloy_sol_types::sol_data::Bytes },
44
45            Type::FixedBytes(span, size) => {
46                assert!(size.get() <= 32);
47                let size = Literal::u16_unsuffixed(size.get());
48                quote_spanned! {span=> #alloy_sol_types::sol_data::FixedBytes<#size> }
49            }
50            Type::Int(span, size) | Type::Uint(span, size) => {
51                let name = match ty {
52                    Type::Int(..) => "Int",
53                    Type::Uint(..) => "Uint",
54                    _ => unreachable!(),
55                };
56                let name = Ident::new(name, span);
57
58                let size = size.map_or(256, NonZeroU16::get);
59                assert!(size <= 256 && size % 8 == 0);
60                let size = Literal::u16_unsuffixed(size);
61
62                quote_spanned! {span=> #alloy_sol_types::sol_data::#name<#size> }
63            }
64
65            Type::Tuple(ref tuple) => {
66                return tuple.paren_token.surround(tokens, |tokens| {
67                    for pair in tuple.types.pairs() {
68                        let (ty, comma) = pair.into_tuple();
69                        self.expand_type_to(ty, tokens);
70                        comma.to_tokens(tokens);
71                    }
72                });
73            }
74            Type::Array(ref array) => {
75                let ty = self.expand_type(&array.ty);
76                let span = array.span();
77                if let Some(size) = self.eval_array_size(array) {
78                    quote_spanned! {span=> #alloy_sol_types::sol_data::FixedArray<#ty, #size> }
79                } else {
80                    quote_spanned! {span=> #alloy_sol_types::sol_data::Array<#ty> }
81                }
82            }
83            Type::Function(ref function) => quote_spanned! {function.span()=>
84                #alloy_sol_types::sol_data::Function
85            },
86            Type::Mapping(ref mapping) => quote_spanned! {mapping.span()=>
87                ::core::compile_error!("Mapping types are not supported here")
88            },
89
90            Type::Custom(ref custom) => {
91                if let Some(Item::Contract(c)) = self.try_item(custom) {
92                    quote_spanned! {c.span()=> #alloy_sol_types::sol_data::Address }
93                } else {
94                    let segments = custom.iter();
95                    quote_spanned! {custom.span()=> #(#segments)::* }
96                }
97            }
98        };
99        tokens.extend(tts);
100    }
101
102    // IMPORTANT: Keep in sync with `sol-types/src/types/data_type.rs`
103    /// Expands a single [`Type`] recursively to its Rust type equivalent into the given buffer.
104    ///
105    /// See [`expand_rust_type`](Self::expand_rust_type) for more information.
106    pub(crate) fn expand_rust_type_to(&self, ty: &Type, tokens: &mut TokenStream) {
107        let alloy_sol_types = &self.crates.sol_types;
108        let tts = match *ty {
109            Type::Address(span, _) => quote_spanned! {span=> #alloy_sol_types::private::Address },
110            Type::Bool(span) => return Ident::new("bool", span).to_tokens(tokens),
111            Type::String(span) => quote_spanned! {span=> #alloy_sol_types::private::String },
112            Type::Bytes(span) => quote_spanned! {span=> #alloy_sol_types::private::Bytes },
113
114            Type::FixedBytes(span, size) => {
115                assert!(size.get() <= 32);
116                let size = Literal::u16_unsuffixed(size.get());
117                quote_spanned! {span=> #alloy_sol_types::private::FixedBytes<#size> }
118            }
119            Type::Int(span, size) | Type::Uint(span, size) => {
120                let size = size.map_or(256, NonZeroU16::get);
121                let primitive = matches!(size, 8 | 16 | 32 | 64 | 128);
122                if primitive {
123                    let prefix = match ty {
124                        Type::Int(..) => "i",
125                        Type::Uint(..) => "u",
126                        _ => unreachable!(),
127                    };
128                    return Ident::new(&format!("{prefix}{size}"), span).to_tokens(tokens);
129                }
130                let prefix = match ty {
131                    Type::Int(..) => "I",
132                    Type::Uint(..) => "U",
133                    _ => unreachable!(),
134                };
135                let name = Ident::new(&format!("{prefix}{size}"), span);
136                quote_spanned! {span=> #alloy_sol_types::private::primitives::aliases::#name }
137            }
138
139            Type::Tuple(ref tuple) => {
140                return tuple.paren_token.surround(tokens, |tokens| {
141                    for pair in tuple.types.pairs() {
142                        let (ty, comma) = pair.into_tuple();
143                        self.expand_rust_type_to(ty, tokens);
144                        comma.to_tokens(tokens);
145                    }
146                });
147            }
148            Type::Array(ref array) => {
149                let ty = self.expand_rust_type(&array.ty);
150                let span = array.span();
151                if let Some(size) = self.eval_array_size(array) {
152                    quote_spanned! {span=> [#ty; #size] }
153                } else {
154                    quote_spanned! {span=> #alloy_sol_types::private::Vec<#ty> }
155                }
156            }
157            Type::Function(ref function) => quote_spanned! {function.span()=>
158                #alloy_sol_types::private::Function
159            },
160            Type::Mapping(ref mapping) => quote_spanned! {mapping.span()=>
161                ::core::compile_error!("Mapping types are not supported here")
162            },
163
164            // Exhaustive fallback to `SolType::RustType`
165            Type::Custom(_) => {
166                let span = ty.span();
167                let ty = self.expand_type(ty);
168                quote_spanned! {span=> <#ty as #alloy_sol_types::SolType>::RustType }
169            }
170        };
171        tokens.extend(tts);
172    }
173
174    /// Calculates the base ABI-encoded size of the given parameters in bytes.
175    ///
176    /// See [`type_base_data_size`] for more information.
177    pub(crate) fn params_base_data_size<P>(&self, params: &Parameters<P>) -> usize {
178        params.iter().map(|param| self.type_base_data_size(&param.ty)).sum()
179    }
180
181    /// Recursively calculates the base ABI-encoded size of the given parameter
182    /// in bytes.
183    ///
184    /// That is, the minimum number of bytes required to encode `self` without
185    /// any dynamic data.
186    pub(crate) fn type_base_data_size(&self, ty: &Type) -> usize {
187        match ty {
188            // static types: 1 word
189            Type::Address(..)
190            | Type::Bool(_)
191            | Type::Int(..)
192            | Type::Uint(..)
193            | Type::FixedBytes(..)
194            | Type::Function(_) => 32,
195
196            // dynamic types: 1 offset word, 1 length word
197            Type::String(_) | Type::Bytes(_) | Type::Array(TypeArray { size: None, .. }) => 64,
198
199            // fixed array: size * encoded size
200            Type::Array(a @ TypeArray { ty: inner, size: Some(_), .. }) => {
201                let Some(size) = self.eval_array_size(a) else { return 0 };
202                self.type_base_data_size(inner).checked_mul(size).unwrap_or(0)
203            }
204
205            // tuple: sum of encoded sizes
206            Type::Tuple(tuple) => tuple.types.iter().map(|ty| self.type_base_data_size(ty)).sum(),
207
208            Type::Custom(name) => match self.try_item(name) {
209                Some(Item::Contract(_)) | Some(Item::Enum(_)) => 32,
210                Some(Item::Error(error)) => {
211                    error.parameters.types().map(|ty| self.type_base_data_size(ty)).sum()
212                }
213                Some(Item::Event(event)) => {
214                    event.parameters.iter().map(|p| self.type_base_data_size(&p.ty)).sum()
215                }
216                Some(Item::Struct(strukt)) => {
217                    strukt.fields.types().map(|ty| self.type_base_data_size(ty)).sum()
218                }
219                Some(Item::Udt(udt)) => self.type_base_data_size(&udt.ty),
220                Some(item) => abort!(item.span(), "Invalid type in struct field: {:?}", item),
221                None => 0,
222            },
223
224            // not applicable
225            Type::Mapping(_) => 0,
226        }
227    }
228
229    /// Returns whether the given type can derive the [`Default`] trait.
230    pub(crate) fn can_derive_default(&self, ty: &Type) -> bool {
231        self.can_derive_default_in_namespace(ty, &self.current_namespace)
232    }
233
234    fn can_derive_default_in_namespace(
235        &self,
236        ty: &Type,
237        current_namespace: &Option<SolIdent>,
238    ) -> bool {
239        match ty {
240            Type::Array(a) => match self.eval_array_size(a) {
241                // Dynamic arrays are `Vec<T>`, whose `Default` impl holds for any `T`.
242                None => true,
243                // Fixed arrays are `[T; N]`: `Default` needs `T: Default` and `N <= 32`.
244                Some(sz) => {
245                    sz <= MAX_SUPPORTED_ARRAY_LEN
246                        && self.can_derive_default_in_namespace(&a.ty, current_namespace)
247                }
248            },
249            Type::Tuple(tuple) => {
250                if tuple.types.len() > MAX_SUPPORTED_TUPLE_LEN {
251                    false
252                } else {
253                    tuple
254                        .types
255                        .iter()
256                        .all(|ty| self.can_derive_default_in_namespace(ty, current_namespace))
257                }
258            }
259
260            Type::Custom(name) => match self.try_item_in_namespace(name, current_namespace) {
261                Some((_, Item::Contract(_))) => true,
262                Some((_, Item::Enum(_))) => false,
263                Some((namespace, Item::Error(error))) => error
264                    .parameters
265                    .types()
266                    .all(|ty| self.can_derive_default_in_namespace(ty, namespace)),
267                Some((namespace, Item::Event(event))) => event
268                    .parameters
269                    .iter()
270                    .all(|p| self.can_derive_default_in_namespace(&p.ty, namespace)),
271                Some((namespace, Item::Struct(strukt))) => strukt
272                    .fields
273                    .types()
274                    .all(|ty| self.can_derive_default_in_namespace(ty, namespace)),
275                Some((namespace, Item::Udt(udt))) => {
276                    self.can_derive_default_in_namespace(&udt.ty, namespace)
277                }
278                Some((_, item)) => abort!(item.span(), "Invalid type in struct field: {:?}", item),
279                _ => false,
280            },
281
282            _ => true,
283        }
284    }
285
286    /// Returns whether the given type can derive the builtin traits listed in
287    /// `ExprCtxt::derives`, minus `Default`.
288    pub(crate) fn can_derive_builtin_traits(&self, ty: &Type) -> bool {
289        self.can_derive_builtin_traits_in_namespace(ty, &self.current_namespace)
290    }
291
292    fn can_derive_builtin_traits_in_namespace(
293        &self,
294        ty: &Type,
295        current_namespace: &Option<SolIdent>,
296    ) -> bool {
297        match ty {
298            Type::Array(a) => self.can_derive_builtin_traits_in_namespace(&a.ty, current_namespace),
299            Type::Tuple(tuple) => {
300                if tuple.types.len() > MAX_SUPPORTED_TUPLE_LEN {
301                    false
302                } else {
303                    tuple.types.iter().all(|ty| {
304                        self.can_derive_builtin_traits_in_namespace(ty, current_namespace)
305                    })
306                }
307            }
308
309            Type::Custom(name) => match self.try_item_in_namespace(name, current_namespace) {
310                Some((_, Item::Contract(_))) | Some((_, Item::Enum(_))) => true,
311                Some((namespace, Item::Error(error))) => error
312                    .parameters
313                    .types()
314                    .all(|ty| self.can_derive_builtin_traits_in_namespace(ty, namespace)),
315                Some((namespace, Item::Event(event))) => event
316                    .parameters
317                    .iter()
318                    .all(|p| self.can_derive_builtin_traits_in_namespace(&p.ty, namespace)),
319                Some((namespace, Item::Struct(strukt))) => strukt
320                    .fields
321                    .types()
322                    .all(|ty| self.can_derive_builtin_traits_in_namespace(ty, namespace)),
323                Some((namespace, Item::Udt(udt))) => {
324                    self.can_derive_builtin_traits_in_namespace(&udt.ty, namespace)
325                }
326                Some((_, item)) => abort!(item.span(), "Invalid type in struct field: {:?}", item),
327                _ => false,
328            },
329
330            _ => true,
331        }
332    }
333
334    /// Evaluates the size of the given array type.
335    pub fn eval_array_size(&self, array: &TypeArray) -> Option<ArraySize> {
336        let size = array.size.as_deref()?;
337        ArraySizeEvaluator::new(self).eval(size)
338    }
339}
340
341type ArraySize = usize;
342
343struct ArraySizeEvaluator<'a> {
344    cx: &'a ExpCtxt<'a>,
345    depth: usize,
346}
347
348impl<'a> ArraySizeEvaluator<'a> {
349    fn new(cx: &'a ExpCtxt<'a>) -> Self {
350        Self { cx, depth: 0 }
351    }
352
353    fn eval(&mut self, expr: &ast::Expr) -> Option<ArraySize> {
354        match self.try_eval(expr) {
355            Ok(value) => Some(value),
356            Err(err) => {
357                emit_error!(
358                    expr.span(), "evaluation of constant value failed";
359                    note = err.span() => err.kind.msg()
360                );
361                None
362            }
363        }
364    }
365
366    fn try_eval(&mut self, expr: &ast::Expr) -> Result<ArraySize, EvalError> {
367        self.depth += 1;
368        if self.depth > 32 {
369            return Err(EvalErrorKind::RecursionLimitReached.spanned(expr.span()));
370        }
371        let mut r = self.try_eval_expr(expr);
372        if let Err(e) = &mut r {
373            if e.span.is_none() {
374                e.span = Some(expr.span());
375            }
376        }
377        self.depth -= 1;
378        r
379    }
380
381    fn try_eval_expr(&mut self, expr: &ast::Expr) -> Result<ArraySize, EvalError> {
382        let expr = expr.peel_parens();
383        match expr {
384            ast::Expr::Lit(ast::Lit::Number(ast::LitNumber::Int(n))) => {
385                n.base10_digits().parse::<ArraySize>().map_err(|_| EE::ParseInt.into())
386            }
387            ast::Expr::Binary(bin) => {
388                let lhs = self.try_eval(&bin.left)?;
389                let rhs = self.try_eval(&bin.right)?;
390                self.eval_binop(bin.op, lhs, rhs)
391            }
392            ast::Expr::Ident(ident) => {
393                let name = ast::sol_path![ident.clone()];
394                let Some(item) = self.cx.try_item(&name) else {
395                    eprintln!("{}", std::backtrace::Backtrace::force_capture());
396                    eprintln!("{:#?}", self.cx.all_items);
397                    return Err(EE::CouldNotResolve.into());
398                };
399                let ast::Item::Variable(var) = item else {
400                    return Err(EE::NonConstantVar.into());
401                };
402                if !var.attributes.has_constant() {
403                    return Err(EE::NonConstantVar.into());
404                }
405                let Some((_, expr)) = var.initializer.as_ref() else {
406                    return Err(EE::NonConstantVar.into());
407                };
408                self.try_eval(expr)
409            }
410            ast::Expr::LitDenominated(ast::LitDenominated {
411                number: ast::LitNumber::Int(n),
412                denom,
413            }) => {
414                let n = n.base10_digits().parse::<ArraySize>().map_err(|_| EE::ParseInt)?;
415                let Ok(denom) = denom.value().try_into() else {
416                    return Err(EE::IntTooBig.into());
417                };
418                n.checked_mul(denom).ok_or_else(|| EE::ArithmeticOverflow.into())
419            }
420            ast::Expr::Unary(unary) => {
421                let value = self.try_eval(&unary.expr)?;
422                self.eval_unop(unary.op, value)
423            }
424            _ => Err(EE::UnsupportedExpr.into()),
425        }
426    }
427
428    fn eval_binop(
429        &mut self,
430        bin: ast::BinOp,
431        lhs: ArraySize,
432        rhs: ArraySize,
433    ) -> Result<ArraySize, EvalError> {
434        match bin {
435            ast::BinOp::Shr(..) => rhs
436                .try_into()
437                .ok()
438                .and_then(|rhs| lhs.checked_shr(rhs))
439                .ok_or_else(|| EE::ArithmeticOverflow.into()),
440            ast::BinOp::Shl(..) => rhs
441                .try_into()
442                .ok()
443                .and_then(|rhs| lhs.checked_shl(rhs))
444                .ok_or_else(|| EE::ArithmeticOverflow.into()),
445            ast::BinOp::BitAnd(..) => Ok(lhs & rhs),
446            ast::BinOp::BitOr(..) => Ok(lhs | rhs),
447            ast::BinOp::BitXor(..) => Ok(lhs ^ rhs),
448            ast::BinOp::Add(..) => {
449                lhs.checked_add(rhs).ok_or_else(|| EE::ArithmeticOverflow.into())
450            }
451            ast::BinOp::Sub(..) => {
452                lhs.checked_sub(rhs).ok_or_else(|| EE::ArithmeticOverflow.into())
453            }
454            ast::BinOp::Pow(..) => rhs
455                .try_into()
456                .ok()
457                .and_then(|rhs| lhs.checked_pow(rhs))
458                .ok_or_else(|| EE::ArithmeticOverflow.into()),
459            ast::BinOp::Mul(..) => {
460                lhs.checked_mul(rhs).ok_or_else(|| EE::ArithmeticOverflow.into())
461            }
462            ast::BinOp::Div(..) => lhs.checked_div(rhs).ok_or_else(|| EE::DivisionByZero.into()),
463            ast::BinOp::Rem(..) => lhs.checked_rem(rhs).ok_or_else(|| EE::DivisionByZero.into()),
464            _ => Err(EE::UnsupportedExpr.into()),
465        }
466    }
467
468    fn eval_unop(&mut self, unop: ast::UnOp, value: ArraySize) -> Result<ArraySize, EvalError> {
469        match unop {
470            ast::UnOp::Neg(..) => value.checked_neg().ok_or_else(|| EE::ArithmeticOverflow.into()),
471            ast::UnOp::BitNot(..) | ast::UnOp::Not(..) => Ok(!value),
472            _ => Err(EE::UnsupportedUnaryOp.into()),
473        }
474    }
475}
476
477struct EvalError {
478    kind: EvalErrorKind,
479    span: Option<Span>,
480}
481
482impl From<EvalErrorKind> for EvalError {
483    fn from(kind: EvalErrorKind) -> Self {
484        Self { kind, span: None }
485    }
486}
487
488impl EvalError {
489    fn span(&self) -> Span {
490        self.span.unwrap_or_else(Span::call_site)
491    }
492}
493
494enum EvalErrorKind {
495    RecursionLimitReached,
496    ArithmeticOverflow,
497    ParseInt,
498    IntTooBig,
499    DivisionByZero,
500    UnsupportedUnaryOp,
501    UnsupportedExpr,
502    CouldNotResolve,
503    NonConstantVar,
504}
505use EvalErrorKind as EE;
506
507impl EvalErrorKind {
508    fn spanned(self, span: Span) -> EvalError {
509        EvalError { kind: self, span: Some(span) }
510    }
511
512    fn msg(&self) -> &'static str {
513        match self {
514            Self::RecursionLimitReached => "recursion limit reached",
515            Self::ArithmeticOverflow => "arithmetic overflow",
516            Self::ParseInt => "failed to parse integer",
517            Self::IntTooBig => "integer value is too big",
518            Self::DivisionByZero => "division by zero",
519            Self::UnsupportedUnaryOp => "unsupported unary operation",
520            Self::UnsupportedExpr => "unsupported expression",
521            Self::CouldNotResolve => "could not resolve identifier",
522            Self::NonConstantVar => "only constant variables are allowed",
523        }
524    }
525}
526
527/// Implements [`fmt::Display`] which formats a [`Type`] to its canonical
528/// representation. This is then used in function, error, and event selector
529/// generation.
530pub(crate) struct TypePrinter<'ast> {
531    cx: &'ast ExpCtxt<'ast>,
532    ty: &'ast Type,
533}
534
535impl<'ast> TypePrinter<'ast> {
536    pub(crate) fn new(cx: &'ast ExpCtxt<'ast>, ty: &'ast Type) -> Self {
537        Self { cx, ty }
538    }
539}
540
541impl fmt::Display for TypePrinter<'_> {
542    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
543        match self.ty {
544            Type::Int(_, None) => f.write_str("int256"),
545            Type::Uint(_, None) => f.write_str("uint256"),
546
547            Type::Array(array) => {
548                Self::new(self.cx, &array.ty).fmt(f)?;
549                f.write_str("[")?;
550                if let Some(size) = self.cx.eval_array_size(array) {
551                    size.fmt(f)?;
552                }
553                f.write_str("]")
554            }
555            Type::Tuple(tuple) => {
556                f.write_str("(")?;
557                for (i, ty) in tuple.types.iter().enumerate() {
558                    if i > 0 {
559                        f.write_str(",")?;
560                    }
561                    Self::new(self.cx, ty).fmt(f)?;
562                }
563                f.write_str(")")
564            }
565
566            Type::Custom(name) => Self::new(self.cx, self.cx.custom_type(name)).fmt(f),
567
568            ty => ty.fmt(f),
569        }
570    }
571}