Skip to main content

hermes_ast/
node_child.rs

1//! Child/leaf field types and the NodeList for the AST.
2use std::cell::Cell;
3use std::marker::PhantomData;
4
5use hermes_support::location::{SMLoc, SMRange};
6
7use crate::context::{GCLock, NodeListElement};
8use crate::NodeId;
9use crate::node::{EmptyStatement, Node};
10use crate::visitor::{Path, TransformResult, VisitorMut};
11
12/// JS identifier / operator / keyword bytes, interned in the AtomTable.
13pub type NodeLabel = hermes_atom_table::AtomBytes;
14
15/// JS string-literal bytes, interned in the AtomTable (C++ `NodeString = UniqueString*`).
16pub type NodeString = hermes_atom_table::AtomBytes;
17
18/// Function strictness state (mirrors `ESTree.h` `enum class Strictness`).
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum Strictness {
21    /// Sema has not decided yet.
22    NotSet,
23    /// The function is not in strict mode.
24    NonStrictMode,
25    /// The function is in strict mode.
26    StrictMode,
27}
28
29/// Sentinel for an unset label index (mirrors `LabelDecorationBase::INVALID_LABEL`, `~0u`).
30pub const INVALID_LABEL: u32 = u32::MAX;
31
32/// Metadata common to all AST nodes.
33///
34/// Stored inside [`Node`] and must not be constructed directly by users.
35/// `range`/`parens`/`debug_loc` are attributes → `Cell`.
36#[derive(Debug)]
37pub struct NodeMetadata<'gc> {
38    pub(crate) phantom: PhantomData<&'gc Node<'gc>>,
39    /// The node's source range, mirroring ESTree.h `Node::sourceRange_`.
40    pub range: Cell<SMRange>,
41    /// Debug location, mirroring ESTree.h Node debug loc set by
42    /// JSParserImpl::setLocation. Defaults to range start.
43    pub debug_loc: Cell<SMLoc>,
44    /// 0, 1, or 2 (meaning "2 or more"), mirroring ESTree.h Node::parens_.
45    pub parens: Cell<u8>,
46    /// Identity of the arena slot this metadata is (or will be) stored in.
47    /// `UNASSIGNED` until `Context::alloc` stamps a fresh id; belongs to the
48    /// slot's occupant, not to this metadata value across rebuilds.
49    pub id: Cell<NodeId>,
50}
51
52impl<'gc> NodeMetadata<'gc> {
53    /// Create metadata for `range`. `debug_loc` defaults to `range.start`,
54    /// matching the C++ 3-arg `setLocation` overload.
55    pub fn new(range: SMRange) -> Self {
56        NodeMetadata {
57            phantom: PhantomData,
58            range: Cell::new(range),
59            debug_loc: Cell::new(range.start),
60            parens: Cell::new(0),
61            id: Cell::new(NodeId::UNASSIGNED),
62        }
63    }
64
65    /// Like `new`, but with an explicit debug location (C++ 4-arg setLocation).
66    pub fn new_with_debug(range: SMRange, debug_loc: SMLoc) -> Self {
67        NodeMetadata {
68            phantom: PhantomData,
69            range: Cell::new(range),
70            debug_loc: Cell::new(debug_loc),
71            parens: Cell::new(0),
72            id: Cell::new(NodeId::UNASSIGNED),
73        }
74    }
75
76    /// Deep-copy the metadata, copying `Cell` values into fresh `Cell`s.
77    /// Used by builders when cloning a node. The id resets to `UNASSIGNED`:
78    /// it belongs to the arena slot's occupant, and `Context::alloc` stamps
79    /// a fresh one when the duplicate is stored.
80    pub(crate) fn duplicate(&self) -> NodeMetadata<'gc> {
81        NodeMetadata {
82            phantom: self.phantom,
83            range: Cell::new(self.range.get()),
84            debug_loc: Cell::new(self.debug_loc.get()),
85            parens: Cell::new(self.parens.get()),
86            id: Cell::new(NodeId::UNASSIGNED),
87        }
88    }
89
90    /// Expose `duplicate` for integration-test crates.
91    /// Not intended for production use.
92    #[doc(hidden)]
93    pub fn duplicate_pub_for_test(&self) -> NodeMetadata<'gc> {
94        self.duplicate()
95    }
96}
97
98/// An ordered list of nodes used as a property in the AST.
99///
100/// Implemented as a linked list internally to avoid extra overhead that would
101/// exist if it were to allocate a `Vec` or some other structure that required
102/// allocating on the native heap.
103///
104/// Because this is just a `Copy` head pointer into context-allocated
105/// `NodeListElement`s (juno model), it implements `Copy` much like any other
106/// pointer/reference, allowing the user to handle it much like `&Node` in many
107/// cases. Empty == null head.
108#[derive(Debug, Copy, Clone)]
109pub struct NodeList<'gc> {
110    /// If non-null, pointer to the first element of the list.
111    /// If null, the list is empty.
112    pub(crate) head: *const NodeListElement<'gc>,
113}
114
115impl<'gc> NodeList<'gc> {
116    /// Create a new empty list.
117    /// Guaranteed to be fast, performs no allocations.
118    pub fn empty() -> Self {
119        NodeList {
120            head: std::ptr::null(),
121        }
122    }
123
124    /// Connect the provided pre-existing nodes into a `NodeList` via iteration.
125    /// `NodeList` doesn't implement `FromIterator` directly due to the `GCLock`
126    /// requirement.
127    pub fn from_iter<'a, I: IntoIterator<Item = &'a Node<'a>>>(
128        lock: &'a GCLock<'_, '_>,
129        nodes: I,
130    ) -> NodeList<'a> {
131        let mut it = nodes.into_iter();
132        match it.next() {
133            Some(first) => {
134                // At least one element in the list.
135                // Allocate the `NodeListElement`s in the context.
136                let head_elem: &'a NodeListElement<'a> =
137                    lock.append_list_element(None, first);
138                let mut prev_elem = head_elem;
139                // Exhaust the rest of the iterator.
140                for next in it {
141                    let next_elem =
142                        lock.append_list_element(Some(prev_elem), next);
143                    prev_elem = next_elem;
144                }
145                NodeList { head: head_elem }
146            }
147            _ => {
148                // No elements, return the empty `NodeList`.
149                NodeList::empty()
150            }
151        }
152    }
153
154    /// Whether this `NodeList` has no elements.
155    /// Cost: `O(1)`
156    pub fn is_empty(&self) -> bool {
157        self.head.is_null()
158    }
159
160    /// Iterate the list front to back. Cost: `O(1)` to start, `O(1)` per step.
161    pub fn iter(self) -> NodeListIter<'gc> {
162        NodeListIter {
163            ptr: self.head,
164            _pd: PhantomData,
165        }
166    }
167}
168
169impl<'gc> IntoIterator for NodeList<'gc> {
170    type Item = &'gc Node<'gc>;
171    type IntoIter = NodeListIter<'gc>;
172
173    fn into_iter(self) -> Self::IntoIter {
174        self.iter()
175    }
176}
177
178/// Iterator for `Node`s in the `NodeList`.
179pub struct NodeListIter<'gc> {
180    /// The upcoming element in the iteration order.
181    /// `null` if the iteration is complete (`next` will return `None`).
182    ptr: *const NodeListElement<'gc>,
183    _pd: PhantomData<&'gc Node<'gc>>,
184}
185
186impl<'gc> Iterator for NodeListIter<'gc> {
187    type Item = &'gc Node<'gc>;
188    fn next(&mut self) -> Option<&'gc Node<'gc>> {
189        if self.ptr.is_null() {
190            None
191        } else {
192            // SAFETY note: dereference is sound because list elements live in
193            // the Context for the GCLock lifetime. The single `unsafe` lives in
194            // context.rs; we expose the deref via a context.rs helper so
195            // node_child stays safe.
196            let (node, next) = crate::context::list_elem_parts(self.ptr);
197            self.ptr = next;
198            Some(node)
199        }
200    }
201}
202
203/// Build a zero-width `EmptyStatement` at the start of `at`'s range, used to
204/// replace a required single child that a `VisitorMut` asked to remove.
205fn empty_statement<'gc>(gc: &'gc GCLock<'_, '_>, at: SMRange) -> &'gc Node<'gc> {
206    let range = SMRange {
207        start: at.start,
208        end: at.start,
209    };
210    gc.alloc(Node::EmptyStatement(EmptyStatement::new(NodeMetadata::new(range))))
211}
212
213/// The mutating field-transform trait. Implemented for the three structural
214/// child field types. `visit_child_mut` transforms a child (recursing via
215/// `visitor.call`); `duplicate` clones a child field without `Clone` (so callers
216/// can't fabricate `Node` refs).
217pub(crate) trait NodeChild<'gc>: Sized {
218    type Out;
219    fn visit_child_mut<V: VisitorMut<'gc>>(
220        self,
221        ctx: &'gc GCLock<'_, '_>,
222        visitor: &mut V,
223        path: Path<'gc>,
224    ) -> TransformResult<Self::Out>;
225    fn duplicate(self) -> Self::Out;
226}
227
228impl<'gc> NodeChild<'gc> for &'gc Node<'gc> {
229    type Out = &'gc Node<'gc>;
230    fn visit_child_mut<V: VisitorMut<'gc>>(
231        self,
232        ctx: &'gc GCLock<'_, '_>,
233        visitor: &mut V,
234        path: Path<'gc>,
235    ) -> TransformResult<Self::Out> {
236        match visitor.call(ctx, self, Some(path)) {
237            // A required child cannot be null: removing it yields an EmptyStatement.
238            TransformResult::Removed => {
239                TransformResult::Changed(empty_statement(ctx, self.range()))
240            }
241            TransformResult::Expanded(_) => {
242                panic!("cannot expand a single required child into multiple nodes")
243            }
244            other => other,
245        }
246    }
247    fn duplicate(self) -> Self::Out {
248        self
249    }
250}
251
252impl<'gc> NodeChild<'gc> for Option<&'gc Node<'gc>> {
253    type Out = Option<&'gc Node<'gc>>;
254    fn visit_child_mut<V: VisitorMut<'gc>>(
255        self,
256        ctx: &'gc GCLock<'_, '_>,
257        visitor: &mut V,
258        path: Path<'gc>,
259    ) -> TransformResult<Self::Out> {
260        use TransformResult::*;
261        match self {
262            None => Unchanged,
263            // Route through visitor.call directly (NOT the &Node impl) so that a
264            // Removed on an optional child becomes None, not an EmptyStatement.
265            Some(inner) => match visitor.call(ctx, inner, Some(path)) {
266                Unchanged => Unchanged,
267                Removed => Changed(None),
268                Changed(new_node) => Changed(Some(new_node)),
269                Expanded(_) => {
270                    panic!("cannot expand a single optional child into multiple nodes")
271                }
272            },
273        }
274    }
275    fn duplicate(self) -> Self::Out {
276        self
277    }
278}
279
280impl<'gc> NodeChild<'gc> for NodeList<'gc> {
281    type Out = NodeList<'gc>;
282    fn visit_child_mut<V: VisitorMut<'gc>>(
283        self,
284        ctx: &'gc GCLock<'_, '_>,
285        visitor: &mut V,
286        path: Path<'gc>,
287    ) -> TransformResult<Self::Out> {
288        use TransformResult::*;
289        let mut index = 0usize;
290        let mut it = self.iter();
291        // Fast path: assume no change until the first element that changes.
292        while let Some(elem) = it.next() {
293            let res = visitor.call(ctx, elem, Some(path));
294            if let Unchanged = res {
295                index += 1;
296                continue;
297            }
298            // First change found: copy the unchanged prefix, then this element,
299            // then the rest, and rebuild the list.
300            let mut result: Vec<&'gc Node<'gc>> = self.iter().take(index).collect();
301            match res {
302                Changed(new_node) => result.push(new_node),
303                Expanded(new_nodes) => result.extend(new_nodes),
304                Removed => {}
305                Unchanged => unreachable!("checked above"),
306            }
307            for elem in it.by_ref() {
308                match visitor.call(ctx, elem, Some(path)) {
309                    Unchanged => result.push(elem),
310                    Changed(new_node) => result.push(new_node),
311                    Expanded(new_nodes) => result.extend(new_nodes),
312                    Removed => {}
313                }
314            }
315            return Changed(NodeList::from_iter(ctx, result));
316        }
317        Unchanged
318    }
319    fn duplicate(self) -> Self::Out {
320        self
321    }
322}
323
324impl<'gc> Node<'gc> {
325    /// Top-level transforming entry point. Returns the (maybe-new) root, or
326    /// `None` if it was removed.
327    pub fn visit_mut<V: VisitorMut<'gc>>(
328        &'gc self,
329        ctx: &'gc GCLock<'_, '_>,
330        visitor: &mut V,
331        path: Option<Path<'gc>>,
332    ) -> Option<&'gc Node<'gc>> {
333        match visitor.call(ctx, self, path) {
334            TransformResult::Unchanged => Some(self),
335            TransformResult::Removed => None,
336            TransformResult::Changed(new_node) => Some(new_node),
337            TransformResult::Expanded(_) => panic!("cannot expand the root node into multiple"),
338        }
339    }
340}
341
342#[cfg(test)]
343mod tests {
344    use super::*;
345    #[test]
346    fn strictness_and_constants() {
347        assert_eq!(INVALID_LABEL, u32::MAX);
348        assert_ne!(Strictness::StrictMode, Strictness::NotSet);
349        // NodeString and NodeLabel are the same interned-bytes handle type.
350        fn _same(_a: NodeString, b: NodeLabel) -> NodeString { b }
351    }
352}