solar_ast/ast/
mod.rs

1//! Solidity AST.
2
3use solar_data_structures::{BumpExt, ThinSlice, index::IndexSlice, newtype_index};
4use std::fmt;
5
6pub use crate::token::CommentKind;
7pub use either::{self, Either};
8pub use solar_interface::{Ident, Span, Spanned, Symbol};
9
10mod expr;
11pub use expr::*;
12
13mod item;
14pub use item::*;
15
16mod lit;
17pub use lit::*;
18
19mod path;
20pub use path::*;
21
22mod semver;
23pub use semver::*;
24
25mod stmt;
26pub use stmt::*;
27
28mod ty;
29pub use ty::*;
30
31pub mod yul;
32
33/// AST box. Allocated on the AST arena.
34pub type Box<'ast, T> = &'ast mut T;
35
36/// AST box slice. Allocated on the AST arena.
37pub type BoxSlice<'ast, T> = Box<'ast, ThinSlice<T>>;
38
39/// AST arena allocator.
40pub struct Arena {
41    bump: bumpalo::Bump,
42}
43
44impl Arena {
45    /// Creates a new AST arena.
46    pub fn new() -> Self {
47        Self { bump: bumpalo::Bump::new() }
48    }
49
50    /// Returns a reference to the arena's bump allocator.
51    pub fn bump(&self) -> &bumpalo::Bump {
52        &self.bump
53    }
54
55    /// Returns a mutable reference to the arena's bump allocator.
56    pub fn bump_mut(&mut self) -> &mut bumpalo::Bump {
57        &mut self.bump
58    }
59
60    /// Calculates the number of bytes currently allocated in the entire arena.
61    pub fn allocated_bytes(&self) -> usize {
62        self.bump.allocated_bytes()
63    }
64
65    /// Returns the number of bytes currently in use.
66    pub fn used_bytes(&self) -> usize {
67        self.bump.used_bytes()
68    }
69}
70
71impl Default for Arena {
72    fn default() -> Self {
73        Self::new()
74    }
75}
76
77impl std::ops::Deref for Arena {
78    type Target = bumpalo::Bump;
79
80    #[inline]
81    fn deref(&self) -> &Self::Target {
82        &self.bump
83    }
84}
85
86/// A list of doc-comments.
87#[derive(Default)]
88pub struct DocComments<'ast>(pub BoxSlice<'ast, DocComment>);
89
90impl<'ast> std::ops::Deref for DocComments<'ast> {
91    type Target = BoxSlice<'ast, DocComment>;
92
93    #[inline]
94    fn deref(&self) -> &Self::Target {
95        &self.0
96    }
97}
98
99impl std::ops::DerefMut for DocComments<'_> {
100    #[inline]
101    fn deref_mut(&mut self) -> &mut Self::Target {
102        &mut self.0
103    }
104}
105
106impl<'ast> From<BoxSlice<'ast, DocComment>> for DocComments<'ast> {
107    fn from(comments: BoxSlice<'ast, DocComment>) -> Self {
108        Self(comments)
109    }
110}
111
112impl fmt::Debug for DocComments<'_> {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        f.write_str("DocComments")?;
115        self.0.fmt(f)
116    }
117}
118
119impl DocComments<'_> {
120    /// Returns the span containing all doc-comments.
121    pub fn span(&self) -> Span {
122        Span::join_first_last(self.iter().map(|d| d.span))
123    }
124}
125
126/// A single doc-comment: `/// foo`, `/** bar */`.
127#[derive(Clone, Copy, Debug)]
128pub struct DocComment {
129    /// The comment kind.
130    pub kind: CommentKind,
131    /// The comment's span including its "quotes" (`//`, `/**`).
132    pub span: Span,
133    /// The comment's contents excluding its "quotes" (`//`, `/**`)
134    /// similarly to symbols in string literal tokens.
135    pub symbol: Symbol,
136}
137
138/// A Solidity source file.
139pub struct SourceUnit<'ast> {
140    /// The source unit's items.
141    pub items: Box<'ast, IndexSlice<ItemId, [Item<'ast>]>>,
142}
143
144impl fmt::Debug for SourceUnit<'_> {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        f.write_str("SourceUnit")?;
147        self.items.fmt(f)
148    }
149}
150
151impl<'ast> SourceUnit<'ast> {
152    /// Creates a new source unit from the given items.
153    pub fn new(items: BoxSlice<'ast, Item<'ast>>) -> Self {
154        Self { items: IndexSlice::from_slice_mut(items) }
155    }
156
157    /// Counts the number of contracts in the source unit.
158    pub fn count_contracts(&self) -> usize {
159        self.items.iter().filter(|item| matches!(item.kind, ItemKind::Contract(_))).count()
160    }
161
162    /// Returns an iterator over the source unit's imports.
163    pub fn imports(&self) -> impl Iterator<Item = (Span, &ImportDirective<'ast>)> {
164        self.items.iter().filter_map(|item| match &item.kind {
165            ItemKind::Import(import) => Some((item.span, import)),
166            _ => None,
167        })
168    }
169}
170
171newtype_index! {
172    /// A [source unit item](Item) ID. Only used in [`SourceUnit`].
173    pub struct ItemId;
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn no_drop() {
182        #[track_caller]
183        fn assert_no_drop<T>() {
184            assert!(!std::mem::needs_drop::<T>(), "{}", std::any::type_name::<T>());
185        }
186        assert_no_drop::<Type<'_>>();
187        assert_no_drop::<Expr<'_>>();
188        assert_no_drop::<Stmt<'_>>();
189        assert_no_drop::<Item<'_>>();
190        assert_no_drop::<SourceUnit<'_>>();
191    }
192
193    // Ensure that we track the size of individual AST nodes.
194    #[test]
195    #[cfg_attr(not(target_pointer_width = "64"), ignore = "64-bit only")]
196    #[cfg_attr(feature = "nightly", ignore = "stable only")]
197    fn sizes() {
198        use snapbox::{assert_data_eq, str};
199        #[track_caller]
200        fn assert_size<T>(size: impl snapbox::IntoData) {
201            assert_size_(std::mem::size_of::<T>(), size.into_data());
202        }
203        #[track_caller]
204        fn assert_size_(actual: usize, expected: snapbox::Data) {
205            assert_data_eq!(actual.to_string(), expected);
206        }
207
208        assert_size::<Span>(str!["8"]);
209        assert_size::<DocComments<'_>>(str!["8"]);
210
211        assert_size::<SourceUnit<'_>>(str!["16"]);
212
213        assert_size::<PragmaDirective<'_>>(str!["32"]);
214        assert_size::<ImportDirective<'_>>(str!["32"]);
215        assert_size::<UsingDirective<'_>>(str!["48"]);
216        assert_size::<ItemContract<'_>>(str!["48"]);
217        assert_size::<ItemFunction<'_>>(str!["144"]);
218        assert_size::<VariableDefinition<'_>>(str!["72"]);
219        assert_size::<ItemStruct<'_>>(str!["24"]);
220        assert_size::<ItemEnum<'_>>(str!["24"]);
221        assert_size::<ItemUdvt<'_>>(str!["40"]);
222        assert_size::<ItemError<'_>>(str!["32"]);
223        assert_size::<ItemEvent<'_>>(str!["32"]);
224        assert_size::<ItemKind<'_>>(str!["144"]);
225        assert_size::<Item<'_>>(str!["160"]);
226
227        assert_size::<FunctionHeader<'_>>(str!["112"]);
228        assert_size::<ParameterList<'_>>(str!["16"]);
229
230        assert_size::<ElementaryType>(str!["3"]);
231        assert_size::<TypeKind<'_>>(str!["16"]);
232        assert_size::<Type<'_>>(str!["24"]);
233
234        assert_size::<ExprKind<'_>>(str!["40"]);
235        assert_size::<Expr<'_>>(str!["48"]);
236
237        assert_size::<StmtKind<'_>>(str!["48"]);
238        assert_size::<Stmt<'_>>(str!["64"]);
239        assert_size::<Block<'_>>(str!["16"]);
240
241        assert_size::<yul::ExprCall<'_>>(str!["24"]);
242        assert_size::<yul::ExprKind<'_>>(str!["32"]);
243        assert_size::<yul::Expr<'_>>(str!["40"]);
244
245        assert_size::<yul::StmtKind<'_>>(str!["56"]);
246        assert_size::<yul::Stmt<'_>>(str!["72"]);
247        assert_size::<yul::Block<'_>>(str!["16"]);
248        assert_size::<yul::StmtFor<'_>>(str!["88"]);
249    }
250}