aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir/src/source_id.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir/src/source_id.rs')
-rw-r--r--crates/ra_hir/src/source_id.rs150
1 files changed, 150 insertions, 0 deletions
diff --git a/crates/ra_hir/src/source_id.rs b/crates/ra_hir/src/source_id.rs
new file mode 100644
index 000000000..0a8fb6d32
--- /dev/null
+++ b/crates/ra_hir/src/source_id.rs
@@ -0,0 +1,150 @@
1use std::{marker::PhantomData, sync::Arc, hash::{Hash, Hasher}};
2
3use ra_arena::{Arena, RawId, impl_arena_id};
4use ra_syntax::{SyntaxNodePtr, TreeArc, SyntaxNode, SourceFile, AstNode, ast};
5
6use crate::{HirFileId, DefDatabase};
7
8/// `AstId` points to an AST node in any file.
9///
10/// It is stable across reparses, and can be used as salsa key/value.
11#[derive(Debug)]
12pub(crate) struct AstId<N: AstNode> {
13 file_id: HirFileId,
14 file_ast_id: FileAstId<N>,
15}
16
17impl<N: AstNode> Clone for AstId<N> {
18 fn clone(&self) -> AstId<N> {
19 *self
20 }
21}
22impl<N: AstNode> Copy for AstId<N> {}
23
24impl<N: AstNode> PartialEq for AstId<N> {
25 fn eq(&self, other: &Self) -> bool {
26 (self.file_id, self.file_ast_id) == (other.file_id, other.file_ast_id)
27 }
28}
29impl<N: AstNode> Eq for AstId<N> {}
30impl<N: AstNode> Hash for AstId<N> {
31 fn hash<H: Hasher>(&self, hasher: &mut H) {
32 (self.file_id, self.file_ast_id).hash(hasher);
33 }
34}
35
36impl<N: AstNode> AstId<N> {
37 pub(crate) fn file_id(&self) -> HirFileId {
38 self.file_id
39 }
40
41 pub(crate) fn to_node(&self, db: &impl DefDatabase) -> TreeArc<N> {
42 let syntax_node = db.ast_id_to_node(self.file_id, self.file_ast_id.raw);
43 N::cast(&syntax_node).unwrap().to_owned()
44 }
45}
46
47/// `AstId` points to an AST node in a specific file.
48#[derive(Debug)]
49pub(crate) struct FileAstId<N: AstNode> {
50 raw: ErasedFileAstId,
51 _ty: PhantomData<N>,
52}
53
54impl<N: AstNode> Clone for FileAstId<N> {
55 fn clone(&self) -> FileAstId<N> {
56 *self
57 }
58}
59impl<N: AstNode> Copy for FileAstId<N> {}
60
61impl<N: AstNode> PartialEq for FileAstId<N> {
62 fn eq(&self, other: &Self) -> bool {
63 self.raw == other.raw
64 }
65}
66impl<N: AstNode> Eq for FileAstId<N> {}
67impl<N: AstNode> Hash for FileAstId<N> {
68 fn hash<H: Hasher>(&self, hasher: &mut H) {
69 self.raw.hash(hasher);
70 }
71}
72
73impl<N: AstNode> FileAstId<N> {
74 pub(crate) fn with_file_id(self, file_id: HirFileId) -> AstId<N> {
75 AstId { file_id, file_ast_id: self }
76 }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
80pub struct ErasedFileAstId(RawId);
81impl_arena_id!(ErasedFileAstId);
82
83/// Maps items' `SyntaxNode`s to `ErasedFileAstId`s and back.
84#[derive(Debug, PartialEq, Eq)]
85pub struct AstIdMap {
86 arena: Arena<ErasedFileAstId, SyntaxNodePtr>,
87}
88
89impl AstIdMap {
90 pub(crate) fn ast_id_map_query(db: &impl DefDatabase, file_id: HirFileId) -> Arc<AstIdMap> {
91 let source_file = db.hir_parse(file_id);
92 Arc::new(AstIdMap::from_source_file(&source_file))
93 }
94
95 pub(crate) fn file_item_query(
96 db: &impl DefDatabase,
97 file_id: HirFileId,
98 ast_id: ErasedFileAstId,
99 ) -> TreeArc<SyntaxNode> {
100 let source_file = db.hir_parse(file_id);
101 db.ast_id_map(file_id).arena[ast_id].to_node(&source_file).to_owned()
102 }
103
104 pub(crate) fn ast_id<N: AstNode>(&self, item: &N) -> FileAstId<N> {
105 let ptr = SyntaxNodePtr::new(item.syntax());
106 let raw = match self.arena.iter().find(|(_id, i)| **i == ptr) {
107 Some((it, _)) => it,
108 None => panic!(
109 "Can't find {:?} in AstIdMap:\n{:?}",
110 item.syntax(),
111 self.arena.iter().map(|(_id, i)| i).collect::<Vec<_>>(),
112 ),
113 };
114
115 FileAstId { raw, _ty: PhantomData }
116 }
117
118 fn from_source_file(source_file: &SourceFile) -> AstIdMap {
119 let mut res = AstIdMap { arena: Arena::default() };
120 // By walking the tree in bread-first order we make sure that parents
121 // get lower ids then children. That is, adding a new child does not
122 // change parent's id. This means that, say, adding a new function to a
123 // trait does not change ids of top-level items, which helps caching.
124 bfs(source_file.syntax(), |it| {
125 if let Some(module_item) = ast::ModuleItem::cast(it) {
126 res.alloc(module_item.syntax());
127 } else if let Some(macro_call) = ast::MacroCall::cast(it) {
128 res.alloc(macro_call.syntax());
129 }
130 });
131 res
132 }
133
134 fn alloc(&mut self, item: &SyntaxNode) -> ErasedFileAstId {
135 self.arena.alloc(SyntaxNodePtr::new(item))
136 }
137}
138
139/// Walks the subtree in bfs order, calling `f` for each node.
140fn bfs(node: &SyntaxNode, mut f: impl FnMut(&SyntaxNode)) {
141 let mut curr_layer = vec![node];
142 let mut next_layer = vec![];
143 while !curr_layer.is_empty() {
144 curr_layer.drain(..).for_each(|node| {
145 next_layer.extend(node.children());
146 f(node);
147 });
148 std::mem::swap(&mut curr_layer, &mut next_layer);
149 }
150}