aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_def
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2019-10-29 11:55:39 +0000
committerAleksey Kladov <[email protected]>2019-10-29 11:55:39 +0000
commit541387564483ee3a42a1969fd048f94e57599ca4 (patch)
tree65575cdef1622e15d2fea79e886951a8b38c3406 /crates/ra_hir_def
parent4f22d2f3b0852f32c0ba5e4545ec8cc2d986cfcc (diff)
move expansion-related code to a separate crate
Diffstat (limited to 'crates/ra_hir_def')
-rw-r--r--crates/ra_hir_def/Cargo.toml5
-rw-r--r--crates/ra_hir_def/src/db.rs46
-rw-r--r--crates/ra_hir_def/src/expand.rs243
-rw-r--r--crates/ra_hir_def/src/lib.rs4
4 files changed, 298 insertions, 0 deletions
diff --git a/crates/ra_hir_def/Cargo.toml b/crates/ra_hir_def/Cargo.toml
index 7c57d56bd..049f8a4fc 100644
--- a/crates/ra_hir_def/Cargo.toml
+++ b/crates/ra_hir_def/Cargo.toml
@@ -5,6 +5,11 @@ version = "0.1.0"
5authors = ["rust-analyzer developers"] 5authors = ["rust-analyzer developers"]
6 6
7[dependencies] 7[dependencies]
8log = "0.4.5"
9
8ra_arena = { path = "../ra_arena" } 10ra_arena = { path = "../ra_arena" }
9ra_db = { path = "../ra_db" } 11ra_db = { path = "../ra_db" }
10ra_syntax = { path = "../ra_syntax" } 12ra_syntax = { path = "../ra_syntax" }
13ra_prof = { path = "../ra_prof" }
14tt = { path = "../ra_tt", package = "ra_tt" }
15mbe = { path = "../ra_mbe", package = "ra_mbe" }
diff --git a/crates/ra_hir_def/src/db.rs b/crates/ra_hir_def/src/db.rs
new file mode 100644
index 000000000..7133b61db
--- /dev/null
+++ b/crates/ra_hir_def/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_def/src/expand.rs b/crates/ra_hir_def/src/expand.rs
new file mode 100644
index 000000000..6517ea84d
--- /dev/null
+++ b/crates/ra_hir_def/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_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs
index 4d4d2cb19..6ccb11068 100644
--- a/crates/ra_hir_def/src/lib.rs
+++ b/crates/ra_hir_def/src/lib.rs
@@ -4,4 +4,8 @@
4//! Note that we are in the process of moving parts of `ra_hir` into 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. 5//! `ra_hir_def`, so this crates doesn't contain a lot at the moment.
6 6
7pub mod db;
8
7pub mod ast_id_map; 9pub mod ast_id_map;
10
11pub mod expand;