solar_ast/ast/
lit.rs

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