syntax/
ptr.rs

1//! In rust-analyzer, syntax trees are transient objects.
2//!
3//! That means that we create trees when we need them, and tear them down to
4//! save memory. In this architecture, hanging on to a particular syntax node
5//! for a long time is ill-advisable, as that keeps the whole tree resident.
6//!
7//! Instead, we provide a [`SyntaxNodePtr`] type, which stores information about
8//! *location* of a particular syntax node in a tree. Its a small type which can
9//! be cheaply stored, and which can be resolved to a real [`SyntaxNode`] when
10//! necessary.
11
12use std::{
13    hash::{Hash, Hasher},
14    marker::PhantomData,
15};
16
17use rowan::TextRange;
18
19use crate::{AstNode, SyntaxNode, syntax_node::RustLanguage};
20
21/// A "pointer" to a [`SyntaxNode`], via location in the source code.
22pub type SyntaxNodePtr = rowan::ast::SyntaxNodePtr<RustLanguage>;
23
24/// Like `SyntaxNodePtr`, but remembers the type of node.
25pub struct AstPtr<N: AstNode> {
26    raw: SyntaxNodePtr,
27    _ty: PhantomData<fn() -> N>,
28}
29
30impl<N: AstNode> std::fmt::Debug for AstPtr<N> {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        f.debug_tuple("AstPtr").field(&self.raw).finish()
33    }
34}
35
36impl<N: AstNode> Copy for AstPtr<N> {}
37impl<N: AstNode> Clone for AstPtr<N> {
38    fn clone(&self) -> AstPtr<N> {
39        *self
40    }
41}
42
43impl<N: AstNode> Eq for AstPtr<N> {}
44
45impl<N: AstNode> PartialEq for AstPtr<N> {
46    fn eq(&self, other: &AstPtr<N>) -> bool {
47        self.raw == other.raw
48    }
49}
50
51impl<N: AstNode> Hash for AstPtr<N> {
52    fn hash<H: Hasher>(&self, state: &mut H) {
53        self.raw.hash(state);
54    }
55}
56
57impl<N: AstNode> AstPtr<N> {
58    pub fn new(node: &N) -> AstPtr<N> {
59        AstPtr { raw: SyntaxNodePtr::new(node.syntax()), _ty: PhantomData }
60    }
61
62    pub fn to_node(&self, root: &SyntaxNode) -> N {
63        let syntax_node = self.raw.to_node(root);
64        N::cast(syntax_node).unwrap()
65    }
66
67    pub fn syntax_node_ptr(&self) -> SyntaxNodePtr {
68        self.raw
69    }
70
71    pub fn text_range(&self) -> TextRange {
72        self.raw.text_range()
73    }
74
75    pub fn cast<U: AstNode>(self) -> Option<AstPtr<U>> {
76        if !U::can_cast(self.raw.kind()) {
77            return None;
78        }
79        Some(AstPtr { raw: self.raw, _ty: PhantomData })
80    }
81
82    pub fn kind(&self) -> parser::SyntaxKind {
83        self.raw.kind()
84    }
85
86    pub fn upcast<M: AstNode>(self) -> AstPtr<M>
87    where
88        N: Into<M>,
89    {
90        AstPtr { raw: self.raw, _ty: PhantomData }
91    }
92
93    /// Like `SyntaxNodePtr::cast` but the trait bounds work out.
94    pub fn try_from_raw(raw: SyntaxNodePtr) -> Option<AstPtr<N>> {
95        N::can_cast(raw.kind()).then_some(AstPtr { raw, _ty: PhantomData })
96    }
97
98    pub fn wrap_left<R>(self) -> AstPtr<either::Either<N, R>>
99    where
100        either::Either<N, R>: AstNode,
101    {
102        AstPtr { raw: self.raw, _ty: PhantomData }
103    }
104
105    pub fn wrap_right<L>(self) -> AstPtr<either::Either<L, N>>
106    where
107        either::Either<L, N>: AstNode,
108    {
109        AstPtr { raw: self.raw, _ty: PhantomData }
110    }
111}
112
113impl<N: AstNode> From<AstPtr<N>> for SyntaxNodePtr {
114    fn from(ptr: AstPtr<N>) -> SyntaxNodePtr {
115        ptr.raw
116    }
117}
118
119#[test]
120fn test_local_syntax_ptr() {
121    use crate::{AstNode, SourceFile, ast};
122
123    let file = SourceFile::parse("struct Foo { f: u32, }", parser::Edition::CURRENT).ok().unwrap();
124    let field = file.syntax().descendants().find_map(ast::RecordField::cast).unwrap();
125    let ptr = SyntaxNodePtr::new(field.syntax());
126    let field_syntax = ptr.to_node(file.syntax());
127    assert_eq!(field.syntax(), &field_syntax);
128}