solar_ast/ast/
lit.rs

1use alloy_primitives::{Address, U256};
2use solar_interface::{ByteSymbol, Span, Symbol, diagnostics::ErrorGuaranteed, kw};
3use std::fmt;
4
5/// A literal: `hex"1234"`, `5.6 ether`.
6///
7/// Note that multiple string literals of the same kind are concatenated together to form a single
8/// `Lit` (see [`LitKind::Str`]), thus the `span` will be the span of the entire literal, and
9/// the `symbol` will be the concatenated string.
10///
11/// Reference: <https://docs.soliditylang.org/en/latest/grammar.html#a4.SolidityParser.literal>
12#[derive(Clone, Copy, Debug)]
13pub struct Lit<'ast> {
14    /// The concatenated span of the literal.
15    pub span: Span,
16    /// The original contents of the literal as written in the source code, excluding any quotes.
17    ///
18    /// If this is a concatenated string literal, this will contain only the **first string
19    /// literal's contents**. For all the other string literals, see the `extra` field in
20    /// [`LitKind::Str`].
21    pub symbol: Symbol,
22    /// The "semantic" representation of the literal lowered from the original tokens.
23    /// Strings are unescaped and concatenated, hexadecimal forms are eliminated, etc.
24    pub kind: LitKind<'ast>,
25}
26
27impl fmt::Display for Lit<'_> {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        let Self { ref kind, symbol, span: _ } = *self;
30        match kind {
31            LitKind::Str(s, ..) => write!(f, "{}\"{symbol}\"", s.prefix()),
32            LitKind::Number(_)
33            | LitKind::Rational(_)
34            | LitKind::Err(_)
35            | LitKind::Address(_)
36            | LitKind::Bool(_) => write!(f, "{symbol}"),
37        }
38    }
39}
40
41impl Lit<'_> {
42    /// Returns the span of the first string literal in this literal.
43    pub fn first_span(&self) -> Span {
44        if let LitKind::Str(kind, _, extra) = &self.kind
45            && !extra.is_empty()
46        {
47            let str_len = kind.prefix().len() + 1 + self.symbol.as_str().len() + 1;
48            return self.span.with_hi(self.span.lo() + str_len as u32);
49        }
50        self.span
51    }
52
53    /// Returns an iterator over all the literals that were concatenated to form this literal.
54    pub fn literals(&self) -> impl Iterator<Item = (Span, Symbol)> + '_ {
55        let extra = if let LitKind::Str(_, _, extra) = self.kind { extra } else { &[] };
56        std::iter::once((self.first_span(), self.symbol)).chain(extra.iter().copied())
57    }
58
59    /// Returns a copy of this literal with any allocated data discarded.
60    pub fn copy_without_data<'a>(&self) -> Lit<'a> {
61        if let LitKind::Str(str_kind, byte_symbol, items) = self.kind
62            && !items.is_empty()
63        {
64            return Lit {
65                span: self.span,
66                symbol: self.symbol,
67                kind: LitKind::Str(str_kind, byte_symbol, &[]),
68            };
69        }
70
71        // SAFETY: We just handled the case with data, this is a POD copy.
72        unsafe { std::mem::transmute::<Lit<'_>, Lit<'a>>(*self) }
73    }
74}
75
76/// A kind of literal.
77#[derive(Clone, Copy)]
78pub enum LitKind<'ast> {
79    /// A string, unicode string, or hex string literal. Contains the kind and the unescaped
80    /// contents of the string.
81    ///
82    /// Note that even if this is a string or unicode string literal, invalid UTF-8 sequences
83    /// are allowed, and as such this cannot be a `str` or `Symbol`.
84    ///
85    /// The `[(Span, Symbol)]` contains the extra string literals of the same kind that were
86    /// concatenated together to form this literal.
87    /// For example, `"foo" "bar"` would be parsed as:
88    /// ```ignore (illustrative-debug-format)
89    /// # #![rustfmt::skip]
90    /// Lit {
91    ///     span: 0..11,
92    ///     symbol: "foo",
93    ///     kind: Str("foobar", [(6..11, "bar")]),
94    /// }
95    /// ```
96    ///
97    /// This list is only present in the AST, and is discarded after.
98    Str(StrKind, ByteSymbol, &'ast [(Span, Symbol)]),
99    /// A decimal or hexadecimal number literal.
100    Number(U256),
101    /// A rational number literal.
102    ///
103    /// Note that rational literals that evaluate to integers are represented as
104    /// [`Number`](Self::Number) (e.g. `1.2e3` is represented as `Number(1200)`).
105    Rational(num_rational::Ratio<U256>),
106    /// An address literal. This is a special case of a 40 digit hexadecimal number literal.
107    Address(Address),
108    /// A boolean literal.
109    Bool(bool),
110    /// An error occurred while parsing the literal, which has been emitted.
111    Err(ErrorGuaranteed),
112}
113
114impl fmt::Debug for LitKind<'_> {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        match self {
117            Self::Str(kind, value, extra) => {
118                write!(f, "{kind:?}(")?;
119                let value = value.as_byte_str();
120                if let Ok(utf8) = std::str::from_utf8(value) {
121                    write!(f, "{utf8:?}")?;
122                } else {
123                    f.write_str(&alloy_primitives::hex::encode_prefixed(value))?;
124                }
125                if !extra.is_empty() {
126                    write!(f, ", {extra:?}")?;
127                }
128                f.write_str(")")
129            }
130            Self::Number(value) => write!(f, "Number({value:?})"),
131            Self::Rational(value) => write!(f, "Rational({value:?})"),
132            Self::Address(value) => write!(f, "Address({value:?})"),
133            Self::Bool(value) => write!(f, "Bool({value:?})"),
134            Self::Err(_) => write!(f, "Err"),
135        }
136    }
137}
138
139impl LitKind<'_> {
140    /// Returns the description of this literal kind.
141    pub fn description(&self) -> &'static str {
142        match self {
143            Self::Str(kind, ..) => kind.description(),
144            Self::Number(_) => "number",
145            Self::Rational(_) => "rational",
146            Self::Address(_) => "address",
147            Self::Bool(_) => "boolean",
148            Self::Err(_) => "<error>",
149        }
150    }
151}
152
153/// A single UTF-8 string literal. Only used in import paths and statements, not expressions.
154#[derive(Clone, Debug)]
155pub struct StrLit {
156    /// The span of the literal.
157    pub span: Span,
158    /// The contents of the string. Not unescaped.
159    pub value: Symbol,
160}
161
162/// A string literal kind.
163#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
164pub enum StrKind {
165    /// A regular string literal.
166    Str,
167    /// A unicode string literal.
168    Unicode,
169    /// A hex string literal.
170    Hex,
171}
172
173impl StrKind {
174    /// Returns the description of this string kind.
175    pub fn description(self) -> &'static str {
176        match self {
177            Self::Str => "string",
178            Self::Unicode => "unicode string",
179            Self::Hex => "hex string",
180        }
181    }
182
183    /// Returns the prefix as a string. Empty if `Str`.
184    #[doc(alias = "to_str")]
185    pub fn prefix(self) -> &'static str {
186        match self {
187            Self::Str => "",
188            Self::Unicode => "unicode",
189            Self::Hex => "hex",
190        }
191    }
192
193    /// Returns the prefix as a symbol. Empty if `Str`.
194    #[doc(alias = "to_symbol")]
195    pub fn prefix_symbol(self) -> Symbol {
196        match self {
197            Self::Str => kw::Empty,
198            Self::Unicode => kw::Unicode,
199            Self::Hex => kw::Hex,
200        }
201    }
202}
203
204/// A number sub-denomination.
205#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
206pub enum SubDenomination {
207    /// An ether sub-denomination.
208    Ether(EtherSubDenomination),
209    /// A time sub-denomination.
210    Time(TimeSubDenomination),
211}
212
213impl fmt::Display for SubDenomination {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        match self {
216            Self::Ether(sub_denomination) => sub_denomination.fmt(f),
217            Self::Time(sub_denomination) => sub_denomination.fmt(f),
218        }
219    }
220}
221
222impl SubDenomination {
223    /// Returns the name of this sub-denomination as a string.
224    pub const fn to_str(self) -> &'static str {
225        match self {
226            Self::Ether(sub_denomination) => sub_denomination.to_str(),
227            Self::Time(sub_denomination) => sub_denomination.to_str(),
228        }
229    }
230
231    /// Returns the symbol of this sub-denomination.
232    pub const fn to_symbol(self) -> Symbol {
233        match self {
234            Self::Ether(sub_denomination) => sub_denomination.to_symbol(),
235            Self::Time(sub_denomination) => sub_denomination.to_symbol(),
236        }
237    }
238
239    /// Returns the value of this sub-denomination.
240    pub const fn value(self) -> u64 {
241        match self {
242            Self::Ether(sub_denomination) => sub_denomination.wei(),
243            Self::Time(sub_denomination) => sub_denomination.seconds(),
244        }
245    }
246}
247
248/// An ether [`SubDenomination`].
249#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
250pub enum EtherSubDenomination {
251    /// `wei`
252    Wei,
253    /// `gwei`
254    Gwei,
255    /// `ether`
256    Ether,
257}
258
259impl fmt::Display for EtherSubDenomination {
260    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261        f.write_str(self.to_str())
262    }
263}
264
265impl EtherSubDenomination {
266    /// Returns the name of this sub-denomination as a string.
267    pub const fn to_str(self) -> &'static str {
268        match self {
269            Self::Wei => "wei",
270            Self::Gwei => "gwei",
271            Self::Ether => "ether",
272        }
273    }
274
275    /// Returns the symbol of this sub-denomination.
276    pub const fn to_symbol(self) -> Symbol {
277        match self {
278            Self::Wei => kw::Wei,
279            Self::Gwei => kw::Gwei,
280            Self::Ether => kw::Ether,
281        }
282    }
283
284    /// Returns the number of wei in this sub-denomination.
285    pub const fn wei(self) -> u64 {
286        // https://github.com/argotorg/solidity/blob/2a2a9d37ee69ca77ef530fe18524a3dc8b053104/libsolidity/ast/Types.cpp#L973
287        match self {
288            Self::Wei => 1,
289            Self::Gwei => 1_000_000_000,
290            Self::Ether => 1_000_000_000_000_000_000,
291        }
292    }
293}
294
295/// A time [`SubDenomination`].
296#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
297pub enum TimeSubDenomination {
298    /// `seconds`
299    Seconds,
300    /// `minutes`
301    Minutes,
302    /// `hours`
303    Hours,
304    /// `days`
305    Days,
306    /// `weeks`
307    Weeks,
308    /// `years`
309    Years,
310}
311
312impl fmt::Display for TimeSubDenomination {
313    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314        f.write_str(self.to_str())
315    }
316}
317
318impl TimeSubDenomination {
319    /// Returns the name of this sub-denomination as a string.
320    pub const fn to_str(self) -> &'static str {
321        match self {
322            Self::Seconds => "seconds",
323            Self::Minutes => "minutes",
324            Self::Hours => "hours",
325            Self::Days => "days",
326            Self::Weeks => "weeks",
327            Self::Years => "years",
328        }
329    }
330
331    /// Returns the symbol of this sub-denomination.
332    pub const fn to_symbol(self) -> Symbol {
333        match self {
334            Self::Seconds => kw::Seconds,
335            Self::Minutes => kw::Minutes,
336            Self::Hours => kw::Hours,
337            Self::Days => kw::Days,
338            Self::Weeks => kw::Weeks,
339            Self::Years => kw::Years,
340        }
341    }
342
343    /// Returns the number of seconds in this sub-denomination.
344    pub const fn seconds(self) -> u64 {
345        // https://github.com/argotorg/solidity/blob/2a2a9d37ee69ca77ef530fe18524a3dc8b053104/libsolidity/ast/Types.cpp#L973
346        match self {
347            Self::Seconds => 1,
348            Self::Minutes => 60,
349            Self::Hours => 3_600,
350            Self::Days => 86_400,
351            Self::Weeks => 604_800,
352            Self::Years => 31_536_000,
353        }
354    }
355}
356
357/// Base of numeric literal encoding according to its prefix.
358#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
359pub enum Base {
360    /// Literal starts with "0b".
361    Binary = 2,
362    /// Literal starts with "0o".
363    Octal = 8,
364    /// Literal doesn't contain a prefix.
365    Decimal = 10,
366    /// Literal starts with "0x".
367    Hexadecimal = 16,
368}
369
370impl fmt::Display for Base {
371    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
372        self.to_str().fmt(f)
373    }
374}
375
376impl Base {
377    /// Returns the name of the base as a string.
378    pub fn to_str(self) -> &'static str {
379        match self {
380            Self::Binary => "binary",
381            Self::Octal => "octal",
382            Self::Decimal => "decimal",
383            Self::Hexadecimal => "hexadecimal",
384        }
385    }
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391    use solar_interface::{BytePos, enter};
392
393    fn bs(s: &[u8]) -> ByteSymbol {
394        ByteSymbol::intern(s)
395    }
396
397    #[test]
398    fn literal_fmt() {
399        enter(|| {
400            let lit = LitKind::Str(StrKind::Str, bs(b"hello world"), &[]);
401            assert_eq!(lit.description(), "string");
402            assert_eq!(format!("{lit:?}"), "Str(\"hello world\")");
403
404            let lit = LitKind::Str(StrKind::Str, bs(b"hello\0world"), &[]);
405            assert_eq!(lit.description(), "string");
406            assert_eq!(format!("{lit:?}"), "Str(\"hello\\0world\")");
407
408            let lit = LitKind::Str(StrKind::Str, bs(&[255u8][..]), &[]);
409            assert_eq!(lit.description(), "string");
410            assert_eq!(format!("{lit:?}"), "Str(0xff)");
411
412            let extra = [(Span::new(BytePos(69), BytePos(420)), Symbol::intern("world"))];
413            let lit = LitKind::Str(StrKind::Str, bs(b"hello world"), &extra);
414            assert_eq!(lit.description(), "string");
415            assert_eq!(format!("{lit:?}"), "Str(\"hello world\", [(Span(69..420), \"world\")])");
416        })
417    }
418}