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/db.rs80
-rw-r--r--crates/ra_hir_expand/src/expand.rs248
-rw-r--r--crates/ra_hir_expand/src/lib.rs170
3 files changed, 241 insertions, 257 deletions
diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs
index 7133b61db..912599e57 100644
--- a/crates/ra_hir_expand/src/db.rs
+++ b/crates/ra_hir_expand/src/db.rs
@@ -1,11 +1,13 @@
1use std::sync::Arc; 1use std::sync::Arc;
2 2
3use mbe::MacroRules;
3use ra_db::{salsa, SourceDatabase}; 4use ra_db::{salsa, SourceDatabase};
4use ra_syntax::{Parse, SyntaxNode}; 5use ra_prof::profile;
6use ra_syntax::{AstNode, Parse, SyntaxNode};
5 7
6use crate::{ 8use crate::{
7 ast_id_map::{AstIdMap, ErasedFileAstId}, 9 ast_id_map::{AstIdMap, ErasedFileAstId},
8 expand::{HirFileId, MacroCallId, MacroCallLoc, MacroDefId, MacroFile}, 10 HirFileId, HirFileIdRepr, MacroCallId, MacroCallLoc, MacroDefId, MacroFile, MacroFileKind,
9}; 11};
10 12
11#[salsa::query_group(AstDatabaseStorage)] 13#[salsa::query_group(AstDatabaseStorage)]
@@ -15,18 +17,13 @@ pub trait AstDatabase: SourceDatabase {
15 fn ast_id_to_node(&self, file_id: HirFileId, ast_id: ErasedFileAstId) -> SyntaxNode; 17 fn ast_id_to_node(&self, file_id: HirFileId, ast_id: ErasedFileAstId) -> SyntaxNode;
16 18
17 #[salsa::transparent] 19 #[salsa::transparent]
18 #[salsa::invoke(crate::expand::parse_or_expand_query)]
19 fn parse_or_expand(&self, file_id: HirFileId) -> Option<SyntaxNode>; 20 fn parse_or_expand(&self, file_id: HirFileId) -> Option<SyntaxNode>;
20 21
21 #[salsa::interned] 22 #[salsa::interned]
22 fn intern_macro(&self, macro_call: MacroCallLoc) -> MacroCallId; 23 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>>; 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>>; 25 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>>; 26 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>; 27 fn macro_expand(&self, macro_call: MacroCallId) -> Result<Arc<tt::Subtree>, String>;
31} 28}
32 29
@@ -44,3 +41,72 @@ pub(crate) fn ast_id_to_node(
44 let node = db.parse_or_expand(file_id).unwrap(); 41 let node = db.parse_or_expand(file_id).unwrap();
45 db.ast_id_map(file_id)[ast_id].to_node(&node) 42 db.ast_id_map(file_id)[ast_id].to_node(&node)
46} 43}
44
45pub(crate) fn macro_def(db: &impl AstDatabase, id: MacroDefId) -> Option<Arc<MacroRules>> {
46 let macro_call = id.ast_id.to_node(db);
47 let arg = macro_call.token_tree()?;
48 let (tt, _) = mbe::ast_to_token_tree(&arg).or_else(|| {
49 log::warn!("fail on macro_def to token tree: {:#?}", arg);
50 None
51 })?;
52 let rules = MacroRules::parse(&tt).ok().or_else(|| {
53 log::warn!("fail on macro_def parse: {:#?}", tt);
54 None
55 })?;
56 Some(Arc::new(rules))
57}
58
59pub(crate) fn macro_arg(db: &impl AstDatabase, id: MacroCallId) -> Option<Arc<tt::Subtree>> {
60 let loc = db.lookup_intern_macro(id);
61 let macro_call = loc.ast_id.to_node(db);
62 let arg = macro_call.token_tree()?;
63 let (tt, _) = mbe::ast_to_token_tree(&arg)?;
64 Some(Arc::new(tt))
65}
66
67pub(crate) fn macro_expand(
68 db: &impl AstDatabase,
69 id: MacroCallId,
70) -> Result<Arc<tt::Subtree>, String> {
71 let loc = db.lookup_intern_macro(id);
72 let macro_arg = db.macro_arg(id).ok_or("Fail to args in to tt::TokenTree")?;
73
74 let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?;
75 let tt = macro_rules.expand(&macro_arg).map_err(|err| format!("{:?}", err))?;
76 // Set a hard limit for the expanded tt
77 let count = tt.count();
78 if count > 65536 {
79 return Err(format!("Total tokens count exceed limit : count = {}", count));
80 }
81 Ok(Arc::new(tt))
82}
83
84pub(crate) fn parse_or_expand(db: &impl AstDatabase, file_id: HirFileId) -> Option<SyntaxNode> {
85 match file_id.0 {
86 HirFileIdRepr::FileId(file_id) => Some(db.parse(file_id).tree().syntax().clone()),
87 HirFileIdRepr::MacroFile(macro_file) => {
88 db.parse_macro(macro_file).map(|it| it.syntax_node())
89 }
90 }
91}
92
93pub(crate) fn parse_macro(
94 db: &impl AstDatabase,
95 macro_file: MacroFile,
96) -> Option<Parse<SyntaxNode>> {
97 let _p = profile("parse_macro_query");
98 let macro_call_id = macro_file.macro_call_id;
99 let tt = db
100 .macro_expand(macro_call_id)
101 .map_err(|err| {
102 // Note:
103 // The final goal we would like to make all parse_macro success,
104 // such that the following log will not call anyway.
105 log::warn!("fail on macro_parse: (reason: {})", err,);
106 })
107 .ok()?;
108 match macro_file.macro_file_kind {
109 MacroFileKind::Items => mbe::token_tree_to_items(&tt).ok().map(Parse::to_syntax),
110 MacroFileKind::Expr => mbe::token_tree_to_expr(&tt).ok().map(Parse::to_syntax),
111 }
112}
diff --git a/crates/ra_hir_expand/src/expand.rs b/crates/ra_hir_expand/src/expand.rs
deleted file mode 100644
index 3921175cb..000000000
--- a/crates/ra_hir_expand/src/expand.rs
+++ /dev/null
@@ -1,248 +0,0 @@
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 struct HirFileId(HirFileIdRepr);
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45enum HirFileIdRepr {
46 FileId(FileId),
47 MacroFile(MacroFile),
48}
49
50impl From<FileId> for HirFileId {
51 fn from(id: FileId) -> Self {
52 HirFileId(HirFileIdRepr::FileId(id))
53 }
54}
55
56impl From<MacroFile> for HirFileId {
57 fn from(id: MacroFile) -> Self {
58 HirFileId(HirFileIdRepr::MacroFile(id))
59 }
60}
61
62impl HirFileId {
63 /// For macro-expansion files, returns the file original source file the
64 /// expansion originated from.
65 pub fn original_file(self, db: &impl AstDatabase) -> FileId {
66 match self.0 {
67 HirFileIdRepr::FileId(file_id) => file_id,
68 HirFileIdRepr::MacroFile(macro_file) => {
69 let loc = db.lookup_intern_macro(macro_file.macro_call_id);
70 loc.ast_id.file_id().original_file(db)
71 }
72 }
73 }
74
75 /// Get the crate which the macro lives in, if it is a macro file.
76 pub fn macro_crate(self, db: &impl AstDatabase) -> Option<CrateId> {
77 match self.0 {
78 HirFileIdRepr::FileId(_) => None,
79 HirFileIdRepr::MacroFile(macro_file) => {
80 let loc = db.lookup_intern_macro(macro_file.macro_call_id);
81 Some(loc.def.krate)
82 }
83 }
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
88pub struct MacroFile {
89 macro_call_id: MacroCallId,
90 macro_file_kind: MacroFileKind,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
94pub enum MacroFileKind {
95 Items,
96 Expr,
97}
98
99/// `MacroCallId` identifies a particular macro invocation, like
100/// `println!("Hello, {}", world)`.
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
102pub struct MacroCallId(salsa::InternId);
103impl_intern_key!(MacroCallId);
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
106pub struct MacroDefId {
107 pub krate: CrateId,
108 pub ast_id: AstId<ast::MacroCall>,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Hash)]
112pub struct MacroCallLoc {
113 pub def: MacroDefId,
114 pub ast_id: AstId<ast::MacroCall>,
115}
116
117impl MacroCallId {
118 pub fn loc(self, db: &impl AstDatabase) -> MacroCallLoc {
119 db.lookup_intern_macro(self)
120 }
121
122 pub fn as_file(self, kind: MacroFileKind) -> HirFileId {
123 let macro_file = MacroFile { macro_call_id: self, macro_file_kind: kind };
124 macro_file.into()
125 }
126}
127
128impl MacroCallLoc {
129 pub fn id(self, db: &impl AstDatabase) -> MacroCallId {
130 db.intern_macro(self)
131 }
132}
133
134/// `AstId` points to an AST node in any file.
135///
136/// It is stable across reparses, and can be used as salsa key/value.
137// FIXME: isn't this just a `Source<FileAstId<N>>` ?
138#[derive(Debug)]
139pub struct AstId<N: AstNode> {
140 file_id: HirFileId,
141 file_ast_id: FileAstId<N>,
142}
143
144impl<N: AstNode> Clone for AstId<N> {
145 fn clone(&self) -> AstId<N> {
146 *self
147 }
148}
149impl<N: AstNode> Copy for AstId<N> {}
150
151impl<N: AstNode> PartialEq for AstId<N> {
152 fn eq(&self, other: &Self) -> bool {
153 (self.file_id, self.file_ast_id) == (other.file_id, other.file_ast_id)
154 }
155}
156impl<N: AstNode> Eq for AstId<N> {}
157impl<N: AstNode> Hash for AstId<N> {
158 fn hash<H: Hasher>(&self, hasher: &mut H) {
159 (self.file_id, self.file_ast_id).hash(hasher);
160 }
161}
162
163impl<N: AstNode> AstId<N> {
164 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
165 AstId { file_id, file_ast_id }
166 }
167
168 pub fn file_id(&self) -> HirFileId {
169 self.file_id
170 }
171
172 pub fn to_node(&self, db: &impl AstDatabase) -> N {
173 let syntax_node = db.ast_id_to_node(self.file_id, self.file_ast_id.into());
174 N::cast(syntax_node).unwrap()
175 }
176}
177
178pub(crate) fn macro_def_query(db: &impl AstDatabase, id: MacroDefId) -> Option<Arc<MacroRules>> {
179 let macro_call = id.ast_id.to_node(db);
180 let arg = macro_call.token_tree()?;
181 let (tt, _) = mbe::ast_to_token_tree(&arg).or_else(|| {
182 log::warn!("fail on macro_def to token tree: {:#?}", arg);
183 None
184 })?;
185 let rules = MacroRules::parse(&tt).ok().or_else(|| {
186 log::warn!("fail on macro_def parse: {:#?}", tt);
187 None
188 })?;
189 Some(Arc::new(rules))
190}
191
192pub(crate) fn macro_arg_query(db: &impl AstDatabase, id: MacroCallId) -> Option<Arc<tt::Subtree>> {
193 let loc = db.lookup_intern_macro(id);
194 let macro_call = loc.ast_id.to_node(db);
195 let arg = macro_call.token_tree()?;
196 let (tt, _) = mbe::ast_to_token_tree(&arg)?;
197 Some(Arc::new(tt))
198}
199
200pub(crate) fn macro_expand_query(
201 db: &impl AstDatabase,
202 id: MacroCallId,
203) -> Result<Arc<tt::Subtree>, String> {
204 let loc = db.lookup_intern_macro(id);
205 let macro_arg = db.macro_arg(id).ok_or("Fail to args in to tt::TokenTree")?;
206
207 let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?;
208 let tt = macro_rules.expand(&macro_arg).map_err(|err| format!("{:?}", err))?;
209 // Set a hard limit for the expanded tt
210 let count = tt.count();
211 if count > 65536 {
212 return Err(format!("Total tokens count exceed limit : count = {}", count));
213 }
214 Ok(Arc::new(tt))
215}
216
217pub(crate) fn parse_or_expand_query(
218 db: &impl AstDatabase,
219 file_id: HirFileId,
220) -> Option<SyntaxNode> {
221 match file_id.0 {
222 HirFileIdRepr::FileId(file_id) => Some(db.parse(file_id).tree().syntax().clone()),
223 HirFileIdRepr::MacroFile(macro_file) => {
224 db.parse_macro(macro_file).map(|it| it.syntax_node())
225 }
226 }
227}
228
229pub(crate) fn parse_macro_query(
230 db: &impl AstDatabase,
231 macro_file: MacroFile,
232) -> Option<Parse<SyntaxNode>> {
233 let _p = profile("parse_macro_query");
234 let macro_call_id = macro_file.macro_call_id;
235 let tt = db
236 .macro_expand(macro_call_id)
237 .map_err(|err| {
238 // Note:
239 // The final goal we would like to make all parse_macro success,
240 // such that the following log will not call anyway.
241 log::warn!("fail on macro_parse: (reason: {})", err,);
242 })
243 .ok()?;
244 match macro_file.macro_file_kind {
245 MacroFileKind::Items => mbe::token_tree_to_items(&tt).ok().map(Parse::to_syntax),
246 MacroFileKind::Expr => mbe::token_tree_to_expr(&tt).ok().map(Parse::to_syntax),
247 }
248}
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs
index 6ccb11068..002a5b45a 100644
--- a/crates/ra_hir_expand/src/lib.rs
+++ b/crates/ra_hir_expand/src/lib.rs
@@ -5,7 +5,173 @@
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; 7pub mod db;
8
9pub mod ast_id_map; 8pub mod ast_id_map;
10 9
11pub mod expand; 10use std::hash::{Hash, Hasher};
11
12use ra_db::{salsa, CrateId, FileId};
13use ra_syntax::ast::{self, AstNode};
14
15use crate::{ast_id_map::FileAstId, db::AstDatabase};
16
17macro_rules! impl_intern_key {
18 ($name:ident) => {
19 impl salsa::InternKey for $name {
20 fn from_intern_id(v: salsa::InternId) -> Self {
21 $name(v)
22 }
23 fn as_intern_id(&self) -> salsa::InternId {
24 self.0
25 }
26 }
27 };
28}
29
30/// Input to the analyzer is a set of files, where each file is identified by
31/// `FileId` and contains source code. However, another source of source code in
32/// Rust are macros: each macro can be thought of as producing a "temporary
33/// file". To assign an id to such a file, we use the id of the macro call that
34/// produced the file. So, a `HirFileId` is either a `FileId` (source code
35/// written by user), or a `MacroCallId` (source code produced by macro).
36///
37/// What is a `MacroCallId`? Simplifying, it's a `HirFileId` of a file
38/// containing the call plus the offset of the macro call in the file. Note that
39/// this is a recursive definition! However, the size_of of `HirFileId` is
40/// finite (because everything bottoms out at the real `FileId`) and small
41/// (`MacroCallId` uses the location interner).
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43pub struct HirFileId(HirFileIdRepr);
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
46enum HirFileIdRepr {
47 FileId(FileId),
48 MacroFile(MacroFile),
49}
50
51impl From<FileId> for HirFileId {
52 fn from(id: FileId) -> Self {
53 HirFileId(HirFileIdRepr::FileId(id))
54 }
55}
56
57impl From<MacroFile> for HirFileId {
58 fn from(id: MacroFile) -> Self {
59 HirFileId(HirFileIdRepr::MacroFile(id))
60 }
61}
62
63impl HirFileId {
64 /// For macro-expansion files, returns the file original source file the
65 /// expansion originated from.
66 pub fn original_file(self, db: &impl AstDatabase) -> FileId {
67 match self.0 {
68 HirFileIdRepr::FileId(file_id) => file_id,
69 HirFileIdRepr::MacroFile(macro_file) => {
70 let loc = db.lookup_intern_macro(macro_file.macro_call_id);
71 loc.ast_id.file_id().original_file(db)
72 }
73 }
74 }
75
76 /// Get the crate which the macro lives in, if it is a macro file.
77 pub fn macro_crate(self, db: &impl AstDatabase) -> Option<CrateId> {
78 match self.0 {
79 HirFileIdRepr::FileId(_) => None,
80 HirFileIdRepr::MacroFile(macro_file) => {
81 let loc = db.lookup_intern_macro(macro_file.macro_call_id);
82 Some(loc.def.krate)
83 }
84 }
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
89pub struct MacroFile {
90 macro_call_id: MacroCallId,
91 macro_file_kind: MacroFileKind,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
95pub enum MacroFileKind {
96 Items,
97 Expr,
98}
99
100/// `MacroCallId` identifies a particular macro invocation, like
101/// `println!("Hello, {}", world)`.
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
103pub struct MacroCallId(salsa::InternId);
104impl_intern_key!(MacroCallId);
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
107pub struct MacroDefId {
108 pub krate: CrateId,
109 pub ast_id: AstId<ast::MacroCall>,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Hash)]
113pub struct MacroCallLoc {
114 pub def: MacroDefId,
115 pub ast_id: AstId<ast::MacroCall>,
116}
117
118impl MacroCallId {
119 pub fn loc(self, db: &impl AstDatabase) -> MacroCallLoc {
120 db.lookup_intern_macro(self)
121 }
122
123 pub fn as_file(self, kind: MacroFileKind) -> HirFileId {
124 let macro_file = MacroFile { macro_call_id: self, macro_file_kind: kind };
125 macro_file.into()
126 }
127}
128
129impl MacroCallLoc {
130 pub fn id(self, db: &impl AstDatabase) -> MacroCallId {
131 db.intern_macro(self)
132 }
133}
134
135/// `AstId` points to an AST node in any file.
136///
137/// It is stable across reparses, and can be used as salsa key/value.
138// FIXME: isn't this just a `Source<FileAstId<N>>` ?
139#[derive(Debug)]
140pub struct AstId<N: AstNode> {
141 file_id: HirFileId,
142 file_ast_id: FileAstId<N>,
143}
144
145impl<N: AstNode> Clone for AstId<N> {
146 fn clone(&self) -> AstId<N> {
147 *self
148 }
149}
150impl<N: AstNode> Copy for AstId<N> {}
151
152impl<N: AstNode> PartialEq for AstId<N> {
153 fn eq(&self, other: &Self) -> bool {
154 (self.file_id, self.file_ast_id) == (other.file_id, other.file_ast_id)
155 }
156}
157impl<N: AstNode> Eq for AstId<N> {}
158impl<N: AstNode> Hash for AstId<N> {
159 fn hash<H: Hasher>(&self, hasher: &mut H) {
160 (self.file_id, self.file_ast_id).hash(hasher);
161 }
162}
163
164impl<N: AstNode> AstId<N> {
165 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
166 AstId { file_id, file_ast_id }
167 }
168
169 pub fn file_id(&self) -> HirFileId {
170 self.file_id
171 }
172
173 pub fn to_node(&self, db: &impl AstDatabase) -> N {
174 let syntax_node = db.ast_id_to_node(self.file_id, self.file_ast_id.into());
175 N::cast(syntax_node).unwrap()
176 }
177}