aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_expand/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir_expand/src')
-rw-r--r--crates/ra_hir_expand/src/ast_id_map.rs114
-rw-r--r--crates/ra_hir_expand/src/db.rs46
-rw-r--r--crates/ra_hir_expand/src/expand.rs243
-rw-r--r--crates/ra_hir_expand/src/lib.rs11
4 files changed, 414 insertions, 0 deletions
diff --git a/crates/ra_hir_expand/src/ast_id_map.rs b/crates/ra_hir_expand/src/ast_id_map.rs
new file mode 100644
index 000000000..c3b389102
--- /dev/null
+++ b/crates/ra_hir_expand/src/ast_id_map.rs
@@ -0,0 +1,114 @@
1//! `AstIdMap` allows to create stable IDs for "large" syntax nodes like items
2//! and macro calls.
3//!
4//! Specifically, it enumerates all items in a file and uses position of a an
5//! item as an ID. That way, id's don't change unless the set of items itself
6//! changes.
7
8use std::{
9 hash::{Hash, Hasher},
10 marker::PhantomData,
11 ops,
12};
13
14use ra_arena::{impl_arena_id, Arena, RawId};
15use ra_syntax::{ast, AstNode, SyntaxNode, SyntaxNodePtr};
16
17/// `AstId` points to an AST node in a specific file.
18#[derive(Debug)]
19pub struct FileAstId<N: AstNode> {
20 raw: ErasedFileAstId,
21 _ty: PhantomData<fn() -> N>,
22}
23
24impl<N: AstNode> Clone for FileAstId<N> {
25 fn clone(&self) -> FileAstId<N> {
26 *self
27 }
28}
29impl<N: AstNode> Copy for FileAstId<N> {}
30
31impl<N: AstNode> PartialEq for FileAstId<N> {
32 fn eq(&self, other: &Self) -> bool {
33 self.raw == other.raw
34 }
35}
36impl<N: AstNode> Eq for FileAstId<N> {}
37impl<N: AstNode> Hash for FileAstId<N> {
38 fn hash<H: Hasher>(&self, hasher: &mut H) {
39 self.raw.hash(hasher);
40 }
41}
42
43impl<N: AstNode> From<FileAstId<N>> for ErasedFileAstId {
44 fn from(id: FileAstId<N>) -> Self {
45 id.raw
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
50pub struct ErasedFileAstId(RawId);
51impl_arena_id!(ErasedFileAstId);
52
53/// Maps items' `SyntaxNode`s to `ErasedFileAstId`s and back.
54#[derive(Debug, PartialEq, Eq, Default)]
55pub struct AstIdMap {
56 arena: Arena<ErasedFileAstId, SyntaxNodePtr>,
57}
58
59impl AstIdMap {
60 pub fn from_source(node: &SyntaxNode) -> AstIdMap {
61 assert!(node.parent().is_none());
62 let mut res = AstIdMap { arena: Arena::default() };
63 // By walking the tree in bread-first order we make sure that parents
64 // get lower ids then children. That is, adding a new child does not
65 // change parent's id. This means that, say, adding a new function to a
66 // trait does not change ids of top-level items, which helps caching.
67 bfs(node, |it| {
68 if let Some(module_item) = ast::ModuleItem::cast(it.clone()) {
69 res.alloc(module_item.syntax());
70 } else if let Some(macro_call) = ast::MacroCall::cast(it) {
71 res.alloc(macro_call.syntax());
72 }
73 });
74 res
75 }
76
77 pub fn ast_id<N: AstNode>(&self, item: &N) -> FileAstId<N> {
78 let ptr = SyntaxNodePtr::new(item.syntax());
79 let raw = match self.arena.iter().find(|(_id, i)| **i == ptr) {
80 Some((it, _)) => it,
81 None => panic!(
82 "Can't find {:?} in AstIdMap:\n{:?}",
83 item.syntax(),
84 self.arena.iter().map(|(_id, i)| i).collect::<Vec<_>>(),
85 ),
86 };
87
88 FileAstId { raw, _ty: PhantomData }
89 }
90
91 fn alloc(&mut self, item: &SyntaxNode) -> ErasedFileAstId {
92 self.arena.alloc(SyntaxNodePtr::new(item))
93 }
94}
95
96impl ops::Index<ErasedFileAstId> for AstIdMap {
97 type Output = SyntaxNodePtr;
98 fn index(&self, index: ErasedFileAstId) -> &SyntaxNodePtr {
99 &self.arena[index]
100 }
101}
102
103/// Walks the subtree in bfs order, calling `f` for each node.
104fn bfs(node: &SyntaxNode, mut f: impl FnMut(SyntaxNode)) {
105 let mut curr_layer = vec![node.clone()];
106 let mut next_layer = vec![];
107 while !curr_layer.is_empty() {
108 curr_layer.drain(..).for_each(|node| {
109 next_layer.extend(node.children());
110 f(node);
111 });
112 std::mem::swap(&mut curr_layer, &mut next_layer);
113 }
114}
diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs
new file mode 100644
index 000000000..7133b61db
--- /dev/null
+++ b/crates/ra_hir_expand/src/db.rs
@@ -0,0 +1,46 @@
1use std::sync::Arc;
2
3use ra_db::{salsa, SourceDatabase};
4use ra_syntax::{Parse, SyntaxNode};
5
6use crate::{
7 ast_id_map::{AstIdMap, ErasedFileAstId},
8 expand::{HirFileId, MacroCallId, MacroCallLoc, MacroDefId, MacroFile},
9};
10
11#[salsa::query_group(AstDatabaseStorage)]
12pub trait AstDatabase: SourceDatabase {
13 fn ast_id_map(&self, file_id: HirFileId) -> Arc<AstIdMap>;
14 #[salsa::transparent]
15 fn ast_id_to_node(&self, file_id: HirFileId, ast_id: ErasedFileAstId) -> SyntaxNode;
16
17 #[salsa::transparent]
18 #[salsa::invoke(crate::expand::parse_or_expand_query)]
19 fn parse_or_expand(&self, file_id: HirFileId) -> Option<SyntaxNode>;
20
21 #[salsa::interned]
22 fn intern_macro(&self, macro_call: MacroCallLoc) -> MacroCallId;
23 #[salsa::invoke(crate::expand::macro_arg_query)]
24 fn macro_arg(&self, id: MacroCallId) -> Option<Arc<tt::Subtree>>;
25 #[salsa::invoke(crate::expand::macro_def_query)]
26 fn macro_def(&self, id: MacroDefId) -> Option<Arc<mbe::MacroRules>>;
27 #[salsa::invoke(crate::expand::parse_macro_query)]
28 fn parse_macro(&self, macro_file: MacroFile) -> Option<Parse<SyntaxNode>>;
29 #[salsa::invoke(crate::expand::macro_expand_query)]
30 fn macro_expand(&self, macro_call: MacroCallId) -> Result<Arc<tt::Subtree>, String>;
31}
32
33pub(crate) fn ast_id_map(db: &impl AstDatabase, file_id: HirFileId) -> Arc<AstIdMap> {
34 let map =
35 db.parse_or_expand(file_id).map_or_else(AstIdMap::default, |it| AstIdMap::from_source(&it));
36 Arc::new(map)
37}
38
39pub(crate) fn ast_id_to_node(
40 db: &impl AstDatabase,
41 file_id: HirFileId,
42 ast_id: ErasedFileAstId,
43) -> SyntaxNode {
44 let node = db.parse_or_expand(file_id).unwrap();
45 db.ast_id_map(file_id)[ast_id].to_node(&node)
46}
diff --git a/crates/ra_hir_expand/src/expand.rs b/crates/ra_hir_expand/src/expand.rs
new file mode 100644
index 000000000..6517ea84d
--- /dev/null
+++ b/crates/ra_hir_expand/src/expand.rs
@@ -0,0 +1,243 @@
1use std::{
2 hash::{Hash, Hasher},
3 sync::Arc,
4};
5
6use mbe::MacroRules;
7use ra_db::{salsa, CrateId, FileId};
8use ra_prof::profile;
9use ra_syntax::{
10 ast::{self, AstNode},
11 Parse, SyntaxNode,
12};
13
14use crate::{ast_id_map::FileAstId, db::AstDatabase};
15
16macro_rules! impl_intern_key {
17 ($name:ident) => {
18 impl salsa::InternKey for $name {
19 fn from_intern_id(v: salsa::InternId) -> Self {
20 $name(v)
21 }
22 fn as_intern_id(&self) -> salsa::InternId {
23 self.0
24 }
25 }
26 };
27}
28
29/// Input to the analyzer is a set of files, where each file is identified by
30/// `FileId` and contains source code. However, another source of source code in
31/// Rust are macros: each macro can be thought of as producing a "temporary
32/// file". To assign an id to such a file, we use the id of the macro call that
33/// produced the file. So, a `HirFileId` is either a `FileId` (source code
34/// written by user), or a `MacroCallId` (source code produced by macro).
35///
36/// What is a `MacroCallId`? Simplifying, it's a `HirFileId` of a file
37/// containing the call plus the offset of the macro call in the file. Note that
38/// this is a recursive definition! However, the size_of of `HirFileId` is
39/// finite (because everything bottoms out at the real `FileId`) and small
40/// (`MacroCallId` uses the location interner).
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub enum HirFileId {
43 FileId(FileId),
44 MacroFile(MacroFile),
45}
46
47impl From<FileId> for HirFileId {
48 fn from(id: FileId) -> Self {
49 HirFileId::FileId(id)
50 }
51}
52
53impl From<MacroFile> for HirFileId {
54 fn from(id: MacroFile) -> Self {
55 HirFileId::MacroFile(id)
56 }
57}
58
59impl HirFileId {
60 /// For macro-expansion files, returns the file original source file the
61 /// expansion originated from.
62 pub fn original_file(self, db: &impl AstDatabase) -> FileId {
63 match self {
64 HirFileId::FileId(file_id) => file_id,
65 HirFileId::MacroFile(macro_file) => {
66 let loc = db.lookup_intern_macro(macro_file.macro_call_id);
67 loc.ast_id.file_id().original_file(db)
68 }
69 }
70 }
71
72 /// Get the crate which the macro lives in, if it is a macro file.
73 pub fn macro_crate(self, db: &impl AstDatabase) -> Option<CrateId> {
74 match self {
75 HirFileId::FileId(_) => None,
76 HirFileId::MacroFile(macro_file) => {
77 let loc = db.lookup_intern_macro(macro_file.macro_call_id);
78 Some(loc.def.krate)
79 }
80 }
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85pub struct MacroFile {
86 macro_call_id: MacroCallId,
87 macro_file_kind: MacroFileKind,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
91pub enum MacroFileKind {
92 Items,
93 Expr,
94}
95
96/// `MacroCallId` identifies a particular macro invocation, like
97/// `println!("Hello, {}", world)`.
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
99pub struct MacroCallId(salsa::InternId);
100impl_intern_key!(MacroCallId);
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
103pub struct MacroDefId {
104 pub krate: CrateId,
105 pub ast_id: AstId<ast::MacroCall>,
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, Hash)]
109pub struct MacroCallLoc {
110 pub def: MacroDefId,
111 pub ast_id: AstId<ast::MacroCall>,
112}
113
114impl MacroCallId {
115 pub fn loc(self, db: &impl AstDatabase) -> MacroCallLoc {
116 db.lookup_intern_macro(self)
117 }
118
119 pub fn as_file(self, kind: MacroFileKind) -> HirFileId {
120 let macro_file = MacroFile { macro_call_id: self, macro_file_kind: kind };
121 macro_file.into()
122 }
123}
124
125impl MacroCallLoc {
126 pub fn id(self, db: &impl AstDatabase) -> MacroCallId {
127 db.intern_macro(self)
128 }
129}
130
131/// `AstId` points to an AST node in any file.
132///
133/// It is stable across reparses, and can be used as salsa key/value.
134// FIXME: isn't this just a `Source<FileAstId<N>>` ?
135#[derive(Debug)]
136pub struct AstId<N: AstNode> {
137 file_id: HirFileId,
138 file_ast_id: FileAstId<N>,
139}
140
141impl<N: AstNode> Clone for AstId<N> {
142 fn clone(&self) -> AstId<N> {
143 *self
144 }
145}
146impl<N: AstNode> Copy for AstId<N> {}
147
148impl<N: AstNode> PartialEq for AstId<N> {
149 fn eq(&self, other: &Self) -> bool {
150 (self.file_id, self.file_ast_id) == (other.file_id, other.file_ast_id)
151 }
152}
153impl<N: AstNode> Eq for AstId<N> {}
154impl<N: AstNode> Hash for AstId<N> {
155 fn hash<H: Hasher>(&self, hasher: &mut H) {
156 (self.file_id, self.file_ast_id).hash(hasher);
157 }
158}
159
160impl<N: AstNode> AstId<N> {
161 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
162 AstId { file_id, file_ast_id }
163 }
164
165 pub fn file_id(&self) -> HirFileId {
166 self.file_id
167 }
168
169 pub fn to_node(&self, db: &impl AstDatabase) -> N {
170 let syntax_node = db.ast_id_to_node(self.file_id, self.file_ast_id.into());
171 N::cast(syntax_node).unwrap()
172 }
173}
174
175pub(crate) fn macro_def_query(db: &impl AstDatabase, id: MacroDefId) -> Option<Arc<MacroRules>> {
176 let macro_call = id.ast_id.to_node(db);
177 let arg = macro_call.token_tree()?;
178 let (tt, _) = mbe::ast_to_token_tree(&arg).or_else(|| {
179 log::warn!("fail on macro_def to token tree: {:#?}", arg);
180 None
181 })?;
182 let rules = MacroRules::parse(&tt).ok().or_else(|| {
183 log::warn!("fail on macro_def parse: {:#?}", tt);
184 None
185 })?;
186 Some(Arc::new(rules))
187}
188
189pub(crate) fn macro_arg_query(db: &impl AstDatabase, id: MacroCallId) -> Option<Arc<tt::Subtree>> {
190 let loc = db.lookup_intern_macro(id);
191 let macro_call = loc.ast_id.to_node(db);
192 let arg = macro_call.token_tree()?;
193 let (tt, _) = mbe::ast_to_token_tree(&arg)?;
194 Some(Arc::new(tt))
195}
196
197pub(crate) fn macro_expand_query(
198 db: &impl AstDatabase,
199 id: MacroCallId,
200) -> Result<Arc<tt::Subtree>, String> {
201 let loc = db.lookup_intern_macro(id);
202 let macro_arg = db.macro_arg(id).ok_or("Fail to args in to tt::TokenTree")?;
203
204 let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?;
205 let tt = macro_rules.expand(&macro_arg).map_err(|err| format!("{:?}", err))?;
206 // Set a hard limit for the expanded tt
207 let count = tt.count();
208 if count > 65536 {
209 return Err(format!("Total tokens count exceed limit : count = {}", count));
210 }
211 Ok(Arc::new(tt))
212}
213
214pub(crate) fn parse_or_expand_query(
215 db: &impl AstDatabase,
216 file_id: HirFileId,
217) -> Option<SyntaxNode> {
218 match file_id {
219 HirFileId::FileId(file_id) => Some(db.parse(file_id).tree().syntax().clone()),
220 HirFileId::MacroFile(macro_file) => db.parse_macro(macro_file).map(|it| it.syntax_node()),
221 }
222}
223
224pub(crate) fn parse_macro_query(
225 db: &impl AstDatabase,
226 macro_file: MacroFile,
227) -> Option<Parse<SyntaxNode>> {
228 let _p = profile("parse_macro_query");
229 let macro_call_id = macro_file.macro_call_id;
230 let tt = db
231 .macro_expand(macro_call_id)
232 .map_err(|err| {
233 // Note:
234 // The final goal we would like to make all parse_macro success,
235 // such that the following log will not call anyway.
236 log::warn!("fail on macro_parse: (reason: {})", err,);
237 })
238 .ok()?;
239 match macro_file.macro_file_kind {
240 MacroFileKind::Items => mbe::token_tree_to_items(&tt).ok().map(Parse::to_syntax),
241 MacroFileKind::Expr => mbe::token_tree_to_expr(&tt).ok().map(Parse::to_syntax),
242 }
243}
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs
new file mode 100644
index 000000000..6ccb11068
--- /dev/null
+++ b/crates/ra_hir_expand/src/lib.rs
@@ -0,0 +1,11 @@
1//! `ra_hir_def` contains initial "phases" of the compiler. Roughly, everything
2//! before types.
3//!
4//! Note that we are in the process of moving parts of `ra_hir` into
5//! `ra_hir_def`, so this crates doesn't contain a lot at the moment.
6
7pub mod db;
8
9pub mod ast_id_map;
10
11pub mod expand;