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
12 use std::{
13 hash::{Hash, Hasher},
14 marker::PhantomData,
15 };
16
17 use rowan::TextRange;
18
19 use crate::{syntax_node::RustLanguage, AstNode, SyntaxNode};
20
21 /// A "pointer" to a [`SyntaxNode`], via location in the source code.
22 pub type SyntaxNodePtr = rowan::ast::SyntaxNodePtr<RustLanguage>;
23
24 /// Like `SyntaxNodePtr`, but remembers the type of node.
25 #[derive(Debug)]
26 pub struct AstPtr<N: AstNode> {
27 raw: SyntaxNodePtr,
28 _ty: PhantomData<fn() -> N>,
29 }
30
31 impl<N: AstNode> Clone for AstPtr<N> {
clone(&self) -> AstPtr<N>32 fn clone(&self) -> AstPtr<N> {
33 AstPtr { raw: self.raw.clone(), _ty: PhantomData }
34 }
35 }
36
37 impl<N: AstNode> Eq for AstPtr<N> {}
38
39 impl<N: AstNode> PartialEq for AstPtr<N> {
eq(&self, other: &AstPtr<N>) -> bool40 fn eq(&self, other: &AstPtr<N>) -> bool {
41 self.raw == other.raw
42 }
43 }
44
45 impl<N: AstNode> Hash for AstPtr<N> {
hash<H: Hasher>(&self, state: &mut H)46 fn hash<H: Hasher>(&self, state: &mut H) {
47 self.raw.hash(state);
48 }
49 }
50
51 impl<N: AstNode> AstPtr<N> {
new(node: &N) -> AstPtr<N>52 pub fn new(node: &N) -> AstPtr<N> {
53 AstPtr { raw: SyntaxNodePtr::new(node.syntax()), _ty: PhantomData }
54 }
55
to_node(&self, root: &SyntaxNode) -> N56 pub fn to_node(&self, root: &SyntaxNode) -> N {
57 let syntax_node = self.raw.to_node(root);
58 N::cast(syntax_node).unwrap()
59 }
60
syntax_node_ptr(&self) -> SyntaxNodePtr61 pub fn syntax_node_ptr(&self) -> SyntaxNodePtr {
62 self.raw.clone()
63 }
64
text_range(&self) -> TextRange65 pub fn text_range(&self) -> TextRange {
66 self.raw.text_range()
67 }
68
cast<U: AstNode>(self) -> Option<AstPtr<U>>69 pub fn cast<U: AstNode>(self) -> Option<AstPtr<U>> {
70 if !U::can_cast(self.raw.kind()) {
71 return None;
72 }
73 Some(AstPtr { raw: self.raw, _ty: PhantomData })
74 }
75
upcast<M: AstNode>(self) -> AstPtr<M> where N: Into<M>,76 pub fn upcast<M: AstNode>(self) -> AstPtr<M>
77 where
78 N: Into<M>,
79 {
80 AstPtr { raw: self.raw, _ty: PhantomData }
81 }
82
83 /// Like `SyntaxNodePtr::cast` but the trait bounds work out.
try_from_raw(raw: SyntaxNodePtr) -> Option<AstPtr<N>>84 pub fn try_from_raw(raw: SyntaxNodePtr) -> Option<AstPtr<N>> {
85 N::can_cast(raw.kind()).then_some(AstPtr { raw, _ty: PhantomData })
86 }
87 }
88
89 impl<N: AstNode> From<AstPtr<N>> for SyntaxNodePtr {
from(ptr: AstPtr<N>) -> SyntaxNodePtr90 fn from(ptr: AstPtr<N>) -> SyntaxNodePtr {
91 ptr.raw
92 }
93 }
94
95 #[test]
test_local_syntax_ptr()96 fn test_local_syntax_ptr() {
97 use crate::{ast, AstNode, SourceFile};
98
99 let file = SourceFile::parse("struct Foo { f: u32, }").ok().unwrap();
100 let field = file.syntax().descendants().find_map(ast::RecordField::cast).unwrap();
101 let ptr = SyntaxNodePtr::new(field.syntax());
102 let field_syntax = ptr.to_node(file.syntax());
103 assert_eq!(field.syntax(), &field_syntax);
104 }
105