Skip to main content

brink_syntax/ast/
ptr.rs

1use std::marker::PhantomData;
2
3use rowan::TextRange;
4
5use crate::{SyntaxKind, SyntaxNode};
6
7use super::AstNode;
8
9// ─── SyntaxNodePtr (untyped) ────────────────────────────────────────
10
11/// An untyped lightweight pointer to a syntax node, resolvable against a tree.
12///
13/// Stores `SyntaxKind + TextRange` — the same data as [`AstPtr`] but without
14/// a type parameter, so it can point at nodes of heterogeneous kinds.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct SyntaxNodePtr {
17    kind: SyntaxKind,
18    range: TextRange,
19}
20
21impl SyntaxNodePtr {
22    /// Create a dummy pointer from a range — for test helpers only.
23    #[doc(hidden)]
24    pub fn from_range(range: TextRange) -> Self {
25        Self {
26            kind: SyntaxKind::ERROR,
27            range,
28        }
29    }
30
31    /// Create a pointer from a live syntax node.
32    pub fn from_node(node: &SyntaxNode) -> Self {
33        Self {
34            kind: node.kind(),
35            range: node.text_range(),
36        }
37    }
38
39    /// The text range this pointer points to.
40    pub fn text_range(&self) -> TextRange {
41        self.range
42    }
43
44    /// The syntax kind of the pointed-to node.
45    pub fn syntax_kind(&self) -> SyntaxKind {
46        self.kind
47    }
48
49    /// Resolve this pointer back to a live syntax node.
50    pub fn resolve(&self, root: &SyntaxNode) -> Option<SyntaxNode> {
51        let mut node = root.covering_element(self.range);
52        loop {
53            match &node {
54                rowan::NodeOrToken::Node(n) => {
55                    if n.text_range() == self.range && n.kind() == self.kind {
56                        return Some(n.clone());
57                    }
58                    if n.text_range().start() < self.range.start() {
59                        return None;
60                    }
61                    {
62                        let parent = n.parent()?;
63                        node = rowan::NodeOrToken::Node(parent);
64                    }
65                }
66                rowan::NodeOrToken::Token(t) => {
67                    let parent = t.parent()?;
68                    node = rowan::NodeOrToken::Node(parent);
69                }
70            }
71        }
72    }
73}
74
75impl<N: AstNode> From<AstPtr<N>> for SyntaxNodePtr {
76    fn from(ptr: AstPtr<N>) -> Self {
77        Self {
78            kind: ptr.syntax_kind(),
79            range: ptr.text_range(),
80        }
81    }
82}
83
84// ─── AstPtr (typed) ─────────────────────────────────────────────────
85
86/// A lightweight pointer to an AST node, resolvable against a syntax tree.
87///
88/// Stores the node's `SyntaxKind` and `TextRange` — enough to find it again
89/// given the tree root, without holding an `Arc` reference to the green tree.
90///
91/// Follows the pattern used by rust-analyzer's `AstPtr`.
92pub struct AstPtr<N: AstNode> {
93    kind: SyntaxKind,
94    range: TextRange,
95    _phantom: PhantomData<fn() -> N>,
96}
97
98// Manual impls to avoid requiring `N: Clone/PartialEq/Eq/Hash` bounds.
99// The PhantomData<fn() -> N> is always Copy regardless of N.
100
101impl<N: AstNode> Clone for AstPtr<N> {
102    fn clone(&self) -> Self {
103        *self
104    }
105}
106
107impl<N: AstNode> PartialEq for AstPtr<N> {
108    fn eq(&self, other: &Self) -> bool {
109        self.kind == other.kind && self.range == other.range
110    }
111}
112
113impl<N: AstNode> Eq for AstPtr<N> {}
114
115impl<N: AstNode> std::hash::Hash for AstPtr<N> {
116    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
117        self.kind.hash(state);
118        self.range.hash(state);
119    }
120}
121
122impl<N: AstNode> AstPtr<N> {
123    /// Create a dummy pointer from a range — for test helpers only.
124    #[doc(hidden)]
125    pub fn from_range(range: TextRange) -> Self {
126        Self {
127            kind: SyntaxKind::ERROR,
128            range,
129            _phantom: PhantomData,
130        }
131    }
132
133    /// Create a pointer from a live AST node.
134    pub fn new(node: &N) -> Self {
135        let syntax = node.syntax();
136        Self {
137            kind: syntax.kind(),
138            range: syntax.text_range(),
139            _phantom: PhantomData,
140        }
141    }
142
143    /// The text range this pointer points to.
144    pub fn text_range(&self) -> TextRange {
145        self.range
146    }
147
148    /// The syntax kind of the pointed-to node.
149    pub fn syntax_kind(&self) -> SyntaxKind {
150        self.kind
151    }
152
153    /// Resolve this pointer back to a live AST node.
154    ///
155    /// Walks the tree from `root` to find a node with matching kind and range.
156    /// Returns `None` if the tree has been reparsed and no matching node exists
157    /// (stale pointer).
158    pub fn resolve(&self, root: &SyntaxNode) -> Option<N> {
159        // Find the node that covers this range, then walk ancestors to find
160        // the one with the right kind.
161        self.resolve_syntax(root).and_then(N::cast)
162    }
163
164    fn resolve_syntax(&self, root: &SyntaxNode) -> Option<SyntaxNode> {
165        // Start with the covering element at this range
166        let mut node = root.covering_element(self.range);
167
168        // Walk up to find our exact match
169        loop {
170            match &node {
171                rowan::NodeOrToken::Node(n) => {
172                    if n.text_range() == self.range && n.kind() == self.kind {
173                        return Some(n.clone());
174                    }
175                    // If we've gone past our range, bail
176                    if n.text_range().start() < self.range.start() {
177                        return None;
178                    }
179                    {
180                        let parent = n.parent()?;
181                        node = rowan::NodeOrToken::Node(parent);
182                    }
183                }
184                rowan::NodeOrToken::Token(t) => {
185                    let parent = t.parent()?;
186                    node = rowan::NodeOrToken::Node(parent);
187                }
188            }
189        }
190    }
191}
192
193impl<N: AstNode> std::fmt::Debug for AstPtr<N> {
194    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195        f.debug_struct("AstPtr")
196            .field("kind", &self.kind)
197            .field("range", &self.range)
198            .finish()
199    }
200}
201
202impl<N: AstNode> Copy for AstPtr<N> {}