solar_ast/ast/
ty.rs

1use super::{AstPath, Box, Expr, ParameterList, StateMutability, Visibility};
2use solar_interface::{Ident, Span, Spanned, Symbol, kw};
3use std::{borrow::Cow, fmt};
4
5/// A type name.
6///
7/// Reference: <https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.typeName>
8#[derive(Debug)]
9pub struct Type<'ast> {
10    pub span: Span,
11    pub kind: TypeKind<'ast>,
12}
13
14impl Type<'_> {
15    /// Returns `true` if the type is an elementary type.
16    #[inline]
17    pub fn is_elementary(&self) -> bool {
18        self.kind.is_elementary()
19    }
20
21    /// Returns `true` if the type is a custom type.
22    #[inline]
23    pub fn is_custom(&self) -> bool {
24        self.kind.is_custom()
25    }
26
27    /// Returns `true` if the type is a function.
28    #[inline]
29    pub fn is_function(&self) -> bool {
30        matches!(self.kind, TypeKind::Function(_))
31    }
32}
33
34/// The kind of a type.
35pub enum TypeKind<'ast> {
36    /// An elementary/primitive type.
37    Elementary(ElementaryType),
38
39    /// `$element[$($size)?]`
40    Array(Box<'ast, TypeArray<'ast>>),
41    /// `function($($parameters),*) $($attributes)* $(returns ($($returns),+))?`
42    Function(Box<'ast, TypeFunction<'ast>>),
43    /// `mapping($key $($key_name)? => $value $($value_name)?)`
44    Mapping(Box<'ast, TypeMapping<'ast>>),
45
46    /// A custom type.
47    Custom(AstPath<'ast>),
48}
49
50impl fmt::Debug for TypeKind<'_> {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {
53            Self::Elementary(ty) => ty.fmt(f),
54            Self::Array(ty) => ty.fmt(f),
55            Self::Function(ty) => ty.fmt(f),
56            Self::Mapping(ty) => ty.fmt(f),
57            Self::Custom(path) => write!(f, "Custom({path:?})"),
58        }
59    }
60}
61
62impl TypeKind<'_> {
63    /// Returns `true` if the type is an elementary type.
64    ///
65    /// Note that this does not include `Custom` types.
66    pub fn is_elementary(&self) -> bool {
67        matches!(self, Self::Elementary(_))
68    }
69
70    /// Returns `true` if the type is a custom type.
71    pub fn is_custom(&self) -> bool {
72        matches!(self, Self::Custom(_))
73    }
74}
75
76/// Elementary/primitive type.
77///
78/// Reference: <https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.elementaryTypeName>
79#[derive(Clone, Copy, PartialEq, Eq, Hash)]
80pub enum ElementaryType {
81    /// Ethereum address, 20-byte fixed-size byte array.
82    /// `address $(payable)?`
83    Address(/* payable: */ bool),
84    /// Boolean.
85    /// `bool`
86    Bool,
87    /// UTF-8 string.
88    /// `string`
89    String,
90    /// Dynamic byte array.
91    /// `bytes`
92    Bytes,
93
94    /// Signed fixed-point number.
95    /// `fixedMxN where M @ 0..=32, N @ 0..=80`. M is the number of bytes, **not bits**.
96    Fixed(TypeSize, TypeFixedSize),
97    /// Unsigned fixed-point number.
98    /// `ufixedMxN where M @ 0..=32, N @ 0..=80`. M is the number of bytes, **not bits**.
99    UFixed(TypeSize, TypeFixedSize),
100
101    /// Signed integer. The number is the number of bytes, **not bits**.
102    /// `0 => int`
103    /// `size @ 1..=32 => int{size*8}`
104    /// `33.. => unreachable!()`
105    Int(TypeSize),
106    /// Unsigned integer. The number is the number of bytes, **not bits**.
107    /// `0 => uint`
108    /// `size @ 1..=32 => uint{size*8}`
109    /// `33.. => unreachable!()`
110    UInt(TypeSize),
111    /// Fixed-size byte array.
112    /// `size @ 1..=32 => bytes{size}`
113    /// `0 | 33.. => unreachable!()`
114    FixedBytes(TypeSize),
115}
116
117impl fmt::Debug for ElementaryType {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        match self {
120            Self::Address(false) => f.write_str("Address"),
121            Self::Address(true) => f.write_str("AddressPayable"),
122            Self::Bool => f.write_str("Bool"),
123            Self::String => f.write_str("String"),
124            Self::Bytes => f.write_str("Bytes"),
125            Self::Fixed(size, fixed) => write!(f, "Fixed({}, {})", size.bytes_raw(), fixed.get()),
126            Self::UFixed(size, fixed) => write!(f, "UFixed({}, {})", size.bytes_raw(), fixed.get()),
127            Self::Int(size) => write!(f, "Int({})", size.bits_raw()),
128            Self::UInt(size) => write!(f, "UInt({})", size.bits_raw()),
129            Self::FixedBytes(size) => write!(f, "FixedBytes({})", size.bytes_raw()),
130        }
131    }
132}
133
134impl fmt::Display for ElementaryType {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        self.write_abi_str(f)?;
137        if let Self::Address(true) = self {
138            f.write_str(" payable")?;
139        }
140        Ok(())
141    }
142}
143
144impl ElementaryType {
145    /// Returns the Solidity ABI representation of the type as a string.
146    pub fn to_abi_str(self) -> Cow<'static, str> {
147        match self {
148            Self::Address(_) => "address".into(),
149            Self::Bool => "bool".into(),
150            Self::String => "string".into(),
151            Self::Bytes => "bytes".into(),
152            Self::Fixed(_size, _fixed) => "fixed".into(),
153            Self::UFixed(_size, _fixed) => "ufixed".into(),
154            Self::Int(size) => format!("int{}", size.bits()).into(),
155            Self::UInt(size) => format!("uint{}", size.bits()).into(),
156            Self::FixedBytes(size) => format!("bytes{}", size.bytes()).into(),
157        }
158    }
159
160    /// Writes the Solidity ABI representation of the type to a formatter.
161    pub fn write_abi_str<W: fmt::Write + ?Sized>(self, f: &mut W) -> fmt::Result {
162        f.write_str(match self {
163            Self::Address(_) => "address",
164            Self::Bool => "bool",
165            Self::String => "string",
166            Self::Bytes => "bytes",
167            Self::Fixed(m, n) => return write!(f, "fixed{}x{}", m.bits(), n.get()),
168            Self::UFixed(m, n) => return write!(f, "ufixed{}x{}", m.bits(), n.get()),
169            Self::Int(size) => return write!(f, "int{}", size.bits()),
170            Self::UInt(size) => return write!(f, "uint{}", size.bits()),
171            Self::FixedBytes(size) => return write!(f, "bytes{}", size.bytes()),
172        })
173    }
174
175    /// Returns `true` if the type is a value type.
176    ///
177    /// Reference: <https://docs.soliditylang.org/en/latest/types.html#value-types>
178    #[inline]
179    pub const fn is_value_type(self) -> bool {
180        matches!(
181            self,
182            Self::Address(_)
183                | Self::Bool
184                | Self::Fixed(..)
185                | Self::UFixed(..)
186                | Self::Int(..)
187                | Self::UInt(..)
188                | Self::FixedBytes(..)
189        )
190    }
191
192    /// Returns `true` if the type is a reference type.
193    #[inline]
194    pub const fn is_reference_type(self) -> bool {
195        matches!(self, Self::String | Self::Bytes)
196    }
197}
198
199/// Byte size of a fixed-bytes, integer, or fixed-point number (M) type. Valid values: 0..=32.
200#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
201pub struct TypeSize(u8);
202
203impl fmt::Debug for TypeSize {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        write!(f, "TypeSize({})", self.0)
206    }
207}
208
209impl TypeSize {
210    /// The value zero. Note that this is not a valid size for a fixed-bytes type.
211    pub const ZERO: Self = Self(0);
212
213    /// The maximum byte value of a `TypeSize`.
214    pub const MAX: u8 = 32;
215
216    /// Creates a new `TypeSize` from a `u8` number of **bytes**.
217    #[inline]
218    pub const fn new(bytes: u8) -> Option<Self> {
219        if bytes > Self::MAX { None } else { Some(Self(bytes)) }
220    }
221
222    /// Creates a new `TypeSize` from a `u8` number of **bits**.
223    ///
224    /// Panics if `bits` is not a multiple of 8 or greater than 256.
225    #[inline]
226    #[track_caller]
227    pub fn new_int_bits(bits: u16) -> Self {
228        Self::try_new_int_bits(bits).unwrap_or_else(|| panic!("invalid integer size: {bits}"))
229    }
230
231    /// Creates a new `TypeSize` for an integer type.
232    ///
233    /// Returns None if `bits` is not a multiple of 8 or greater than 256.
234    #[inline]
235    pub fn try_new_int_bits(bits: u16) -> Option<Self> {
236        if bits.is_multiple_of(8) { Self::new((bits / 8).try_into().ok()?) } else { None }
237    }
238
239    /// Creates a new `TypeSize` for a fixed-bytes type.
240    ///
241    /// Panics if `bytes` is not in the range 1..=32.
242    #[inline]
243    #[track_caller]
244    pub fn new_fb_bytes(bytes: u8) -> Self {
245        Self::try_new_fb_bytes(bytes).unwrap_or_else(|| panic!("invalid fixed-bytes size: {bytes}"))
246    }
247
248    /// Creates a new `TypeSize` for a fixed-bytes type.
249    ///
250    /// Returns None if `bytes` is not in the range 1..=32.
251    #[inline]
252    pub fn try_new_fb_bytes(bytes: u8) -> Option<Self> {
253        if bytes == 0 {
254            return None;
255        }
256        Self::new(bytes)
257    }
258
259    /// Returns the number of **bytes**, with `0` defaulting to `MAX`.
260    #[inline]
261    pub const fn bytes(self) -> u8 {
262        if self.0 == 0 { Self::MAX } else { self.0 }
263    }
264
265    /// Returns the number of **bytes**.
266    #[inline]
267    pub const fn bytes_raw(self) -> u8 {
268        self.0
269    }
270
271    /// Returns the number of **bits**, with `0` defaulting to `MAX*8`.
272    #[inline]
273    pub const fn bits(self) -> u16 {
274        self.bytes() as u16 * 8
275    }
276
277    /// Returns the number of **bits**.
278    #[inline]
279    pub const fn bits_raw(self) -> u16 {
280        self.0 as u16 * 8
281    }
282
283    /// Returns the `int` symbol for the type name.
284    #[inline]
285    pub const fn int_keyword(self) -> Symbol {
286        kw::int(self.0)
287    }
288
289    /// Returns the `uint` symbol for the type name.
290    #[inline]
291    pub const fn uint_keyword(self) -> Symbol {
292        kw::uint(self.0)
293    }
294
295    /// Returns the `bytesN` symbol for the type name.
296    ///
297    /// # Panics
298    ///
299    /// Panics if `self` is 0.
300    #[inline]
301    #[track_caller]
302    pub const fn bytes_keyword(self) -> Symbol {
303        kw::fixed_bytes(self.0)
304    }
305}
306
307/// Size of a fixed-point number (N) type. Valid values: 0..=80.
308#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
309pub struct TypeFixedSize(u8);
310
311impl fmt::Debug for TypeFixedSize {
312    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313        write!(f, "TypeFixedSize({})", self.0)
314    }
315}
316
317impl TypeFixedSize {
318    /// The value zero.
319    pub const ZERO: Self = Self(0);
320
321    /// The maximum value of a `TypeFixedSize`.
322    pub const MAX: u8 = 80;
323
324    /// Creates a new `TypeFixedSize` from a `u8`.
325    #[inline]
326    pub const fn new(value: u8) -> Option<Self> {
327        if value > Self::MAX { None } else { Some(Self(value)) }
328    }
329
330    /// Returns the value.
331    #[inline]
332    pub const fn get(self) -> u8 {
333        self.0
334    }
335}
336
337/// An array type.
338#[derive(Debug)]
339pub struct TypeArray<'ast> {
340    pub element: Type<'ast>,
341    pub size: Option<Box<'ast, Expr<'ast>>>,
342}
343
344/// A function type name.
345///
346/// Reference: <https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.functionTypeName>
347#[derive(Debug)]
348pub struct TypeFunction<'ast> {
349    pub parameters: ParameterList<'ast>,
350    pub visibility: Option<Spanned<Visibility>>,
351    pub state_mutability: Option<Spanned<StateMutability>>,
352    pub returns: Option<ParameterList<'ast>>,
353}
354
355impl<'ast> TypeFunction<'ast> {
356    pub fn visibility(&self) -> Option<Visibility> {
357        self.visibility.map(Spanned::into_inner)
358    }
359
360    pub fn state_mutability(&self) -> StateMutability {
361        self.state_mutability.map(Spanned::into_inner).unwrap_or(StateMutability::NonPayable)
362    }
363
364    pub fn returns(&self) -> &[crate::VariableDefinition<'ast>] {
365        self.returns.as_ref().map(|pl| &pl.vars[..]).unwrap_or(&[])
366    }
367}
368
369/// A mapping type.
370///
371/// Reference: <https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.mappingType>
372#[derive(Debug)]
373pub struct TypeMapping<'ast> {
374    pub key: Type<'ast>,
375    pub key_name: Option<Ident>,
376    pub value: Type<'ast>,
377    pub value_name: Option<Ident>,
378}