Skip to main content

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/// Bit size of a fixed-bytes, integer, or fixed-point number (M) type. Valid values: 0..=256.
200#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
201pub struct TypeSize(u16);
202
203impl Default for TypeSize {
204    #[inline]
205    fn default() -> Self {
206        Self::ZERO
207    }
208}
209
210impl fmt::Debug for TypeSize {
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        write!(f, "TypeSize({})", self.0)
213    }
214}
215
216impl TypeSize {
217    /// The value zero. Note that this is not a valid size for a fixed-bytes type.
218    pub const ZERO: Self = Self(0);
219
220    /// The maximum bit value of a `TypeSize`.
221    pub const MAX: u16 = 256;
222
223    /// Creates a new `TypeSize` from a `u16` number of **bits**.
224    #[inline]
225    const fn new(bits: u16) -> Option<Self> {
226        if bits > Self::MAX { None } else { Some(Self(bits)) }
227    }
228
229    /// Creates a new `TypeSize` for an integer type from **bits**.
230    ///
231    /// Panics if `bits` is not a multiple of 8 or greater than 256.
232    #[inline]
233    #[track_caller]
234    pub fn new_int_bits(bits: u16) -> Self {
235        Self::try_new_int_bits(bits).unwrap_or_else(|| panic!("invalid integer size: {bits}"))
236    }
237
238    /// Creates a new `TypeSize` for an integer type from **bits**.
239    ///
240    /// Returns None if `bits` is not a multiple of 8 or greater than 256.
241    #[inline]
242    pub fn try_new_int_bits(bits: u16) -> Option<Self> {
243        if bits.is_multiple_of(8) { Self::new(bits) } else { None }
244    }
245
246    /// Creates a new `TypeSize` for an integer literal from **bits**.
247    ///
248    /// Panics if `bits` is zero or greater than 256.
249    #[inline]
250    #[track_caller]
251    pub fn new_literal_bits(bits: u16) -> Self {
252        Self::try_new_literal_bits(bits).unwrap_or_else(|| panic!("invalid literal size: {bits}"))
253    }
254
255    /// Creates a new `TypeSize` for an integer literal from **bits**.
256    ///
257    /// Returns None if `bits` is zero or greater than 256.
258    #[inline]
259    pub fn try_new_literal_bits(bits: u16) -> Option<Self> {
260        if bits == 0 { None } else { Self::new(bits) }
261    }
262
263    /// Creates a new `TypeSize` for a fixed-bytes type from **bytes**.
264    ///
265    /// Panics if `bytes` is not in the range 1..=32.
266    #[inline]
267    #[track_caller]
268    pub fn new_fb_bytes(bytes: u8) -> Self {
269        Self::try_new_fb_bytes(bytes).unwrap_or_else(|| panic!("invalid fixed-bytes size: {bytes}"))
270    }
271
272    /// Creates a new `TypeSize` for a fixed-bytes type from **bytes**.
273    ///
274    /// Returns None if `bytes` is not in the range 1..=32.
275    #[inline]
276    pub fn try_new_fb_bytes(bytes: u8) -> Option<Self> {
277        if bytes == 0 {
278            return None;
279        }
280        Self::new(bytes as u16 * 8)
281    }
282
283    /// Returns the number of **bytes**, with `0` defaulting to 32.
284    #[inline]
285    pub const fn bytes(self) -> u8 {
286        if self.0 == 0 { (Self::MAX / 8) as u8 } else { self.0.div_ceil(8) as u8 }
287    }
288
289    /// Returns the number of **bytes**.
290    #[inline]
291    pub const fn bytes_raw(self) -> u8 {
292        self.0.div_ceil(8) as u8
293    }
294
295    /// Returns the number of **bits**, with `0` defaulting to `MAX`.
296    #[inline]
297    pub const fn bits(self) -> u16 {
298        if self.0 == 0 { Self::MAX } else { self.0 }
299    }
300
301    /// Returns the number of **bits**.
302    #[inline]
303    pub const fn bits_raw(self) -> u16 {
304        self.0
305    }
306
307    /// Returns the `int` symbol for the type name.
308    #[inline]
309    pub const fn int_keyword(self) -> Symbol {
310        kw::int(self.bytes_raw())
311    }
312
313    /// Returns the `uint` symbol for the type name.
314    #[inline]
315    pub const fn uint_keyword(self) -> Symbol {
316        kw::uint(self.bytes_raw())
317    }
318
319    /// Returns the `bytesN` symbol for the type name.
320    ///
321    /// # Panics
322    ///
323    /// Panics if `self` is 0.
324    #[inline]
325    #[track_caller]
326    pub const fn bytes_keyword(self) -> Symbol {
327        kw::fixed_bytes(self.bytes_raw())
328    }
329}
330
331/// Size of a fixed-point number (N) type. Valid values: 0..=80.
332#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
333pub struct TypeFixedSize(u8);
334
335impl fmt::Debug for TypeFixedSize {
336    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337        write!(f, "TypeFixedSize({})", self.0)
338    }
339}
340
341impl TypeFixedSize {
342    /// The value zero.
343    pub const ZERO: Self = Self(0);
344
345    /// The maximum value of a `TypeFixedSize`.
346    pub const MAX: u8 = 80;
347
348    /// Creates a new `TypeFixedSize` from a `u8`.
349    #[inline]
350    pub const fn new(value: u8) -> Option<Self> {
351        if value > Self::MAX { None } else { Some(Self(value)) }
352    }
353
354    /// Returns the value.
355    #[inline]
356    pub const fn get(self) -> u8 {
357        self.0
358    }
359}
360
361/// An array type.
362#[derive(Debug)]
363pub struct TypeArray<'ast> {
364    pub element: Type<'ast>,
365    pub size: Option<Box<'ast, Expr<'ast>>>,
366}
367
368/// A function type name.
369///
370/// Reference: <https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.functionTypeName>
371#[derive(Debug)]
372pub struct TypeFunction<'ast> {
373    pub parameters: ParameterList<'ast>,
374    pub visibility: Option<Spanned<Visibility>>,
375    pub state_mutability: Option<Spanned<StateMutability>>,
376    pub returns: Option<ParameterList<'ast>>,
377}
378
379impl<'ast> TypeFunction<'ast> {
380    pub fn visibility(&self) -> Option<Visibility> {
381        self.visibility.map(Spanned::into_inner)
382    }
383
384    pub fn state_mutability(&self) -> StateMutability {
385        self.state_mutability.map(Spanned::into_inner).unwrap_or(StateMutability::NonPayable)
386    }
387
388    pub fn returns(&self) -> &[crate::VariableDefinition<'ast>] {
389        self.returns.as_ref().map(|pl| &pl.vars[..]).unwrap_or(&[])
390    }
391}
392
393/// A mapping type.
394///
395/// Reference: <https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.mappingType>
396#[derive(Debug)]
397pub struct TypeMapping<'ast> {
398    pub key: Type<'ast>,
399    pub key_name: Option<Ident>,
400    pub value: Type<'ast>,
401    pub value_name: Option<Ident>,
402}