syn_solidity/type/
mod.rs

1use crate::{kw, sol_path, SolPath, Spanned};
2use proc_macro2::Span;
3use std::{
4    fmt,
5    fmt::Write,
6    hash::{Hash, Hasher},
7    num::{IntErrorKind, NonZeroU16},
8};
9use syn::{
10    ext::IdentExt,
11    parse::{Lookahead1, Parse, ParseStream},
12    token::{Bracket, Paren},
13    Error, Ident, Result, Token,
14};
15
16mod array;
17pub use array::TypeArray;
18
19mod function;
20pub use function::TypeFunction;
21
22mod mapping;
23pub use mapping::TypeMapping;
24
25mod tuple;
26pub use tuple::TypeTuple;
27
28/// A type name.
29///
30/// Solidity reference:
31/// <https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.typeName>
32#[derive(Clone)]
33pub enum Type {
34    // TODO: `fixed` and `ufixed`
35    /// `address $(payable)?`
36    Address(Span, Option<kw::payable>),
37    /// `bool`
38    Bool(Span),
39    /// `string`
40    String(Span),
41
42    /// `bytes`
43    Bytes(Span),
44    /// `bytes<size>`
45    FixedBytes(Span, NonZeroU16),
46
47    /// `int[size]`
48    Int(Span, Option<NonZeroU16>),
49    /// `uint[size]`
50    Uint(Span, Option<NonZeroU16>),
51
52    /// `$ty[$($size)?]`
53    Array(TypeArray),
54    /// `$(tuple)? ( $($types,)* )`
55    Tuple(TypeTuple),
56    /// `function($($arguments),*) $($attributes)* $(returns ($($returns),+))?`
57    Function(TypeFunction),
58    /// `mapping($key $($key_name)? => $value $($value_name)?)`
59    Mapping(TypeMapping),
60
61    /// A custom type.
62    Custom(SolPath),
63}
64
65impl PartialEq for Type {
66    fn eq(&self, other: &Self) -> bool {
67        match (self, other) {
68            (Self::Address(..), Self::Address(..)) => true,
69            (Self::Bool(_), Self::Bool(_)) => true,
70            (Self::String(_), Self::String(_)) => true,
71            (Self::Bytes { .. }, Self::Bytes { .. }) => true,
72
73            (Self::FixedBytes(_, a), Self::FixedBytes(_, b)) => a == b,
74            (Self::Int(_, a), Self::Int(_, b)) => a == b,
75            (Self::Uint(_, a), Self::Uint(_, b)) => a == b,
76
77            (Self::Tuple(a), Self::Tuple(b)) => a == b,
78            (Self::Array(a), Self::Array(b)) => a == b,
79            (Self::Function(a), Self::Function(b)) => a == b,
80            (Self::Mapping(a), Self::Mapping(b)) => a == b,
81            (Self::Custom(a), Self::Custom(b)) => a == b,
82
83            _ => false,
84        }
85    }
86}
87
88impl Eq for Type {}
89
90impl Hash for Type {
91    fn hash<H: Hasher>(&self, state: &mut H) {
92        std::mem::discriminant(self).hash(state);
93        match self {
94            Self::Address(..) | Self::Bool(_) | Self::String(_) | Self::Bytes(_) => {}
95
96            Self::FixedBytes(_, size) => size.hash(state),
97            Self::Int(_, size) => size.hash(state),
98            Self::Uint(_, size) => size.hash(state),
99
100            Self::Tuple(tuple) => tuple.hash(state),
101            Self::Array(array) => array.hash(state),
102            Self::Function(function) => function.hash(state),
103            Self::Mapping(mapping) => mapping.hash(state),
104            Self::Custom(custom) => custom.hash(state),
105        }
106    }
107}
108
109impl fmt::Debug for Type {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        f.write_str("Type::")?;
112        match self {
113            Self::Address(_, None) => f.write_str("Address"),
114            Self::Address(_, Some(_)) => f.write_str("AddressPayable"),
115            Self::Bool(_) => f.write_str("Bool"),
116            Self::String(_) => f.write_str("String"),
117            Self::Bytes(_) => f.write_str("Bytes"),
118
119            Self::FixedBytes(_, size) => f.debug_tuple("FixedBytes").field(size).finish(),
120            Self::Int(_, size) => f.debug_tuple("Int").field(size).finish(),
121            Self::Uint(_, size) => f.debug_tuple("Uint").field(size).finish(),
122
123            Self::Tuple(tuple) => tuple.fmt(f),
124            Self::Array(array) => array.fmt(f),
125            Self::Function(function) => function.fmt(f),
126            Self::Mapping(mapping) => mapping.fmt(f),
127            Self::Custom(custom) => f.debug_tuple("Custom").field(custom).finish(),
128        }
129    }
130}
131
132/// Canonical type name formatting, used in selector preimages.
133impl fmt::Display for Type {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        match self {
136            Self::Address(_, _) => f.write_str("address"),
137            Self::Bool(_) => f.write_str("bool"),
138            Self::String(_) => f.write_str("string"),
139            Self::Bytes(_) => f.write_str("bytes"),
140
141            Self::FixedBytes(_, size) => write!(f, "bytes{size}"),
142            Self::Int(_, size) => write_opt(f, "int", *size),
143            Self::Uint(_, size) => write_opt(f, "uint", *size),
144
145            Self::Tuple(tuple) => tuple.fmt(f),
146            Self::Array(array) => array.fmt(f),
147            Self::Function(_) => f.write_str("function"),
148            Self::Mapping(mapping) => mapping.fmt(f),
149            Self::Custom(custom) => custom.fmt(f),
150        }
151    }
152}
153
154impl Parse for Type {
155    fn parse(input: ParseStream<'_>) -> Result<Self> {
156        let mut candidate = Self::parse_simple(input)?;
157
158        // while the next token is a bracket, parse an array size and nest the
159        // candidate into an array
160        while input.peek(Bracket) {
161            candidate = Self::Array(TypeArray::parse_nested(Box::new(candidate), input)?);
162        }
163
164        Ok(candidate)
165    }
166}
167
168impl Spanned for Type {
169    fn span(&self) -> Span {
170        match self {
171            &Self::Address(span, payable) => {
172                payable.and_then(|kw| span.join(kw.span)).unwrap_or(span)
173            }
174            Self::Bool(span)
175            | Self::String(span)
176            | Self::Bytes(span)
177            | Self::FixedBytes(span, _)
178            | Self::Int(span, _)
179            | Self::Uint(span, _) => *span,
180            Self::Tuple(tuple) => tuple.span(),
181            Self::Array(array) => array.span(),
182            Self::Function(function) => function.span(),
183            Self::Mapping(mapping) => mapping.span(),
184            Self::Custom(custom) => custom.span(),
185        }
186    }
187
188    fn set_span(&mut self, new_span: Span) {
189        match self {
190            Self::Address(span, payable) => {
191                *span = new_span;
192                if let Some(kw) = payable {
193                    kw.span = new_span;
194                }
195            }
196            Self::Bool(span)
197            | Self::String(span)
198            | Self::Bytes(span)
199            | Self::FixedBytes(span, _)
200            | Self::Int(span, _)
201            | Self::Uint(span, _) => *span = new_span,
202
203            Self::Tuple(tuple) => tuple.set_span(new_span),
204            Self::Array(array) => array.set_span(new_span),
205            Self::Function(function) => function.set_span(new_span),
206            Self::Mapping(mapping) => mapping.set_span(new_span),
207            Self::Custom(custom) => custom.set_span(new_span),
208        }
209    }
210}
211
212impl Type {
213    pub fn custom(ident: Ident) -> Self {
214        Self::Custom(sol_path![ident])
215    }
216
217    pub fn peek(lookahead: &Lookahead1<'_>) -> bool {
218        lookahead.peek(syn::token::Paren)
219            || lookahead.peek(kw::tuple)
220            || lookahead.peek(kw::function)
221            || lookahead.peek(kw::mapping)
222            || lookahead.peek(Ident::peek_any)
223    }
224
225    /// Parses an identifier as an [elementary type name][ref].
226    ///
227    /// Note that you will have to check for the existence of a `payable`
228    /// keyword separately.
229    ///
230    /// [ref]: https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.elementaryTypeName
231    pub fn parse_ident(ident: Ident) -> Self {
232        Self::try_parse_ident(ident.clone()).unwrap_or_else(|_| Self::custom(ident))
233    }
234
235    pub fn try_parse_ident(ident: Ident) -> Result<Self> {
236        let span = ident.span();
237        let s = ident.to_string();
238        let ret = match s.as_str() {
239            "address" => Self::Address(span, None),
240            "bool" => Self::Bool(span),
241            "string" => Self::String(span),
242            s => {
243                if let Some(s) = s.strip_prefix("bytes") {
244                    match parse_size(s, span)? {
245                        None => Self::custom(ident),
246                        Some(Some(size)) if size.get() > 32 => {
247                            return Err(Error::new(span, "fixed bytes range is 1-32"))
248                        }
249                        Some(Some(size)) => Self::FixedBytes(span, size),
250                        Some(None) => Self::Bytes(span),
251                    }
252                } else if let Some(s) = s.strip_prefix("int") {
253                    match parse_size(s, span)? {
254                        None => Self::custom(ident),
255                        Some(Some(size)) if size.get() > 256 || size.get() % 8 != 0 => {
256                            return Err(Error::new(span, "intX must be a multiple of 8 up to 256"))
257                        }
258                        Some(size) => Self::Int(span, size),
259                    }
260                } else if let Some(s) = s.strip_prefix("uint") {
261                    match parse_size(s, span)? {
262                        None => Self::custom(ident),
263                        Some(Some(size)) if size.get() > 256 || size.get() % 8 != 0 => {
264                            return Err(Error::new(span, "uintX must be a multiple of 8 up to 256"))
265                        }
266                        Some(size) => Self::Uint(span, size),
267                    }
268                } else {
269                    Self::custom(ident)
270                }
271            }
272        };
273        Ok(ret)
274    }
275
276    /// Parses the `payable` keyword from the input stream if this type is an
277    /// address.
278    pub fn parse_payable(mut self, input: ParseStream<'_>) -> Self {
279        if let Self::Address(_, opt @ None) = &mut self {
280            *opt = input.parse().unwrap();
281        }
282        self
283    }
284
285    /// Returns whether this type is ABI-encoded as a single EVM word (32 bytes).
286    ///
287    /// This is the same as [`is_value_type`](Self::is_value_type).
288    #[deprecated = "use `is_value_type` instead"]
289    pub fn is_one_word(&self, custom_is_value_type: impl Fn(&SolPath) -> bool) -> bool {
290        self.is_value_type(custom_is_value_type)
291    }
292
293    /// Returns whether this type is dynamic according to ABI rules.
294    ///
295    /// Note that this does not account for custom types, such as UDVTs.
296    pub fn is_abi_dynamic(&self) -> bool {
297        match self {
298            Self::Bool(_)
299            | Self::Int(..)
300            | Self::Uint(..)
301            | Self::FixedBytes(..)
302            | Self::Address(..)
303            | Self::Function(_) => false,
304
305            Self::String(_) | Self::Bytes(_) | Self::Custom(_) => true,
306
307            Self::Array(array) => array.is_abi_dynamic(),
308            Self::Tuple(tuple) => tuple.is_abi_dynamic(),
309
310            // not applicable
311            Self::Mapping(_) => true,
312        }
313    }
314
315    /// Returns whether this type is a value type.
316    ///
317    /// These types' variables are always passed by value.
318    ///
319    /// `custom_is_value_type` accounts for custom value types.
320    ///
321    /// See the [Solidity docs](https://docs.soliditylang.org/en/latest/types.html#value-types) for more information.
322    pub fn is_value_type(&self, custom_is_value_type: impl Fn(&SolPath) -> bool) -> bool {
323        match self {
324            Self::Custom(custom) => custom_is_value_type(custom),
325            _ => self.is_value_type_simple(),
326        }
327    }
328
329    /// Returns whether this type is a simple value type.
330    ///
331    /// See [`is_value_type`](Self::is_value_type) for more information.
332    pub fn is_value_type_simple(&self) -> bool {
333        matches!(
334            self,
335            Self::Bool(_)
336                | Self::Int(..)
337                | Self::Uint(..)
338                | Self::FixedBytes(..)
339                | Self::Address(..)
340                | Self::Function(_)
341        )
342    }
343
344    pub const fn is_array(&self) -> bool {
345        matches!(self, Self::Array(_))
346    }
347
348    pub const fn is_tuple(&self) -> bool {
349        matches!(self, Self::Tuple(_))
350    }
351
352    pub const fn is_custom(&self) -> bool {
353        matches!(self, Self::Custom(_))
354    }
355
356    /// Recurses into this type and returns whether it contains a custom type.
357    pub fn has_custom(&self) -> bool {
358        match self {
359            Self::Custom(_) => true,
360            Self::Array(a) => a.ty.has_custom(),
361            Self::Tuple(t) => t.types.iter().any(Self::has_custom),
362            Self::Function(f) => {
363                f.arguments.iter().any(|arg| arg.ty.has_custom())
364                    || f.returns
365                        .as_ref()
366                        .is_some_and(|ret| ret.returns.iter().any(|arg| arg.ty.has_custom()))
367            }
368            Self::Mapping(m) => m.key.has_custom() || m.value.has_custom(),
369            Self::Bool(_)
370            | Self::Int(..)
371            | Self::Uint(..)
372            | Self::FixedBytes(..)
373            | Self::Address(..)
374            | Self::String(_)
375            | Self::Bytes(_) => false,
376        }
377    }
378
379    /// Same as [`has_custom`](Self::has_custom), but `Function` returns `false`
380    /// rather than recursing into its arguments and return types.
381    pub fn has_custom_simple(&self) -> bool {
382        match self {
383            Self::Custom(_) => true,
384            Self::Array(a) => a.ty.has_custom_simple(),
385            Self::Tuple(t) => t.types.iter().any(Self::has_custom_simple),
386            Self::Mapping(m) => m.key.has_custom_simple() || m.value.has_custom_simple(),
387            Self::Bool(_)
388            | Self::Int(..)
389            | Self::Uint(..)
390            | Self::FixedBytes(..)
391            | Self::Address(..)
392            | Self::Function(_)
393            | Self::String(_)
394            | Self::Bytes(_) => false,
395        }
396    }
397
398    /// Returns the inner type.
399    pub fn peel_arrays(&self) -> &Self {
400        let mut this = self;
401        while let Self::Array(array) = this {
402            this = &array.ty;
403        }
404        this
405    }
406
407    /// Returns the Solidity ABI name for this type. This is `tuple` for custom types, otherwise the
408    /// same as [`Display`](fmt::Display).
409    pub fn abi_name(&self) -> String {
410        let mut s = String::new();
411        self.abi_name_raw(&mut s);
412        s
413    }
414
415    /// Returns the Solidity ABI name for this type. This is `tuple` for custom types, otherwise the
416    /// same as [`Display`](fmt::Display).
417    pub fn abi_name_raw(&self, s: &mut String) {
418        match self {
419            Self::Custom(_) => s.push_str("tuple"),
420            Self::Array(array) => {
421                array.ty.abi_name_raw(s);
422                if let Some(size) = array.size() {
423                    write!(s, "[{size}]").unwrap();
424                } else {
425                    s.push_str("[]");
426                }
427            }
428            _ => write!(s, "{self}").unwrap(),
429        }
430    }
431
432    /// Traverses this type while calling `f`.
433    #[cfg(feature = "visit")]
434    pub fn visit(&self, f: impl FnMut(&Self)) {
435        use crate::Visit;
436        struct VisitType<F>(F);
437        impl<F: FnMut(&Type)> Visit<'_> for VisitType<F> {
438            fn visit_type(&mut self, ty: &Type) {
439                (self.0)(ty);
440                crate::visit::visit_type(self, ty);
441            }
442            // Reduce generated code size by explicitly implementing these methods as noops.
443            fn visit_block(&mut self, _block: &crate::Block) {}
444            fn visit_expr(&mut self, _expr: &crate::Expr) {}
445            fn visit_stmt(&mut self, _stmt: &crate::Stmt) {}
446            fn visit_file(&mut self, _file: &crate::File) {}
447            fn visit_item(&mut self, _item: &crate::Item) {}
448        }
449        VisitType(f).visit_type(self);
450    }
451
452    /// Traverses this type while calling `f`.
453    #[cfg(feature = "visit-mut")]
454    pub fn visit_mut(&mut self, f: impl FnMut(&mut Self)) {
455        use crate::VisitMut;
456        struct VisitTypeMut<F>(F);
457        impl<F: FnMut(&mut Type)> VisitMut<'_> for VisitTypeMut<F> {
458            fn visit_type(&mut self, ty: &mut Type) {
459                (self.0)(ty);
460                crate::visit_mut::visit_type(self, ty);
461            }
462            // Reduce generated code size by explicitly implementing these methods as noops.
463            fn visit_block(&mut self, _block: &mut crate::Block) {}
464            fn visit_expr(&mut self, _expr: &mut crate::Expr) {}
465            fn visit_stmt(&mut self, _stmt: &mut crate::Stmt) {}
466            fn visit_file(&mut self, _file: &mut crate::File) {}
467            fn visit_item(&mut self, _item: &mut crate::Item) {}
468        }
469        VisitTypeMut(f).visit_type(self);
470    }
471
472    /// Parses a type from the input, without recursing into arrays.
473    #[inline]
474    fn parse_simple(input: ParseStream<'_>) -> Result<Self> {
475        if input.peek(Paren) || input.peek(kw::tuple) {
476            input.parse().map(Self::Tuple)
477        } else if input.peek(kw::function) {
478            input.parse().map(Self::Function)
479        } else if input.peek(kw::mapping) {
480            input.parse().map(Self::Mapping)
481        } else if input.peek2(Token![.]) {
482            input.parse().map(Self::Custom)
483        } else if input.peek(Ident::peek_any) {
484            let ident = input.call(Ident::parse_any)?;
485            Ok(Self::parse_ident(ident).parse_payable(input))
486        } else {
487            Err(input.error(
488                "expected a Solidity type: \
489                 `address`, `bool`, `string`, `bytesN`, `intN`, `uintN`, \
490                 `tuple`, `function`, `mapping`, or a custom type name",
491            ))
492        }
493    }
494}
495
496fn write_opt(f: &mut fmt::Formatter<'_>, name: &str, size: Option<NonZeroU16>) -> fmt::Result {
497    f.write_str(name)?;
498    if let Some(size) = size {
499        write!(f, "{size}")?;
500    }
501    Ok(())
502}
503
504// None => Custom
505// Some(size) => size
506fn parse_size(s: &str, span: Span) -> Result<Option<Option<NonZeroU16>>> {
507    let opt = match s.parse::<NonZeroU16>() {
508        Ok(size) => Some(Some(size)),
509        Err(e) => match e.kind() {
510            // bytes
511            IntErrorKind::Empty => Some(None),
512            // bytes_
513            IntErrorKind::InvalidDigit => None,
514            // bytesN where N == 0 || N > MAX
515            _ => return Err(Error::new(span, format_args!("invalid size: {e}"))),
516        },
517    };
518    Ok(opt)
519}