diff options
Diffstat (limited to 'crates/ra_hir/src/source_id.rs')
-rw-r--r-- | crates/ra_hir/src/source_id.rs | 150 |
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 @@ | |||
1 | use std::{marker::PhantomData, sync::Arc, hash::{Hash, Hasher}}; | ||
2 | |||
3 | use ra_arena::{Arena, RawId, impl_arena_id}; | ||
4 | use ra_syntax::{SyntaxNodePtr, TreeArc, SyntaxNode, SourceFile, AstNode, ast}; | ||
5 | |||
6 | use 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)] | ||
12 | pub(crate) struct AstId<N: AstNode> { | ||
13 | file_id: HirFileId, | ||
14 | file_ast_id: FileAstId<N>, | ||
15 | } | ||
16 | |||
17 | impl<N: AstNode> Clone for AstId<N> { | ||
18 | fn clone(&self) -> AstId<N> { | ||
19 | *self | ||
20 | } | ||
21 | } | ||
22 | impl<N: AstNode> Copy for AstId<N> {} | ||
23 | |||
24 | impl<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 | } | ||
29 | impl<N: AstNode> Eq for AstId<N> {} | ||
30 | impl<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 | |||
36 | impl<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)] | ||
49 | pub(crate) struct FileAstId<N: AstNode> { | ||
50 | raw: ErasedFileAstId, | ||
51 | _ty: PhantomData<N>, | ||
52 | } | ||
53 | |||
54 | impl<N: AstNode> Clone for FileAstId<N> { | ||
55 | fn clone(&self) -> FileAstId<N> { | ||
56 | *self | ||
57 | } | ||
58 | } | ||
59 | impl<N: AstNode> Copy for FileAstId<N> {} | ||
60 | |||
61 | impl<N: AstNode> PartialEq for FileAstId<N> { | ||
62 | fn eq(&self, other: &Self) -> bool { | ||
63 | self.raw == other.raw | ||
64 | } | ||
65 | } | ||
66 | impl<N: AstNode> Eq for FileAstId<N> {} | ||
67 | impl<N: AstNode> Hash for FileAstId<N> { | ||
68 | fn hash<H: Hasher>(&self, hasher: &mut H) { | ||
69 | self.raw.hash(hasher); | ||
70 | } | ||
71 | } | ||
72 | |||
73 | impl<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)] | ||
80 | pub struct ErasedFileAstId(RawId); | ||
81 | impl_arena_id!(ErasedFileAstId); | ||
82 | |||
83 | /// Maps items' `SyntaxNode`s to `ErasedFileAstId`s and back. | ||
84 | #[derive(Debug, PartialEq, Eq)] | ||
85 | pub struct AstIdMap { | ||
86 | arena: Arena<ErasedFileAstId, SyntaxNodePtr>, | ||
87 | } | ||
88 | |||
89 | impl 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. | ||
140 | fn 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 | } | ||