diff options
Diffstat (limited to 'crates/hir_expand/src/db.rs')
-rw-r--r-- | crates/hir_expand/src/db.rs | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs new file mode 100644 index 000000000..dcc038bcd --- /dev/null +++ b/crates/hir_expand/src/db.rs | |||
@@ -0,0 +1,403 @@ | |||
1 | //! Defines database & queries for macro expansion. | ||
2 | |||
3 | use std::sync::Arc; | ||
4 | |||
5 | use base_db::{salsa, SourceDatabase}; | ||
6 | use mbe::{ExpandResult, MacroRules}; | ||
7 | use parser::FragmentKind; | ||
8 | use syntax::{algo::diff, AstNode, GreenNode, Parse, SyntaxKind::*, SyntaxNode}; | ||
9 | |||
10 | use crate::{ | ||
11 | ast_id_map::AstIdMap, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerCallLoc, EagerMacroId, | ||
12 | HirFileId, HirFileIdRepr, LazyMacroId, MacroCallId, MacroCallLoc, MacroDefId, MacroDefKind, | ||
13 | MacroFile, ProcMacroExpander, | ||
14 | }; | ||
15 | |||
16 | #[derive(Debug, Clone, Eq, PartialEq)] | ||
17 | pub enum TokenExpander { | ||
18 | MacroRules(mbe::MacroRules), | ||
19 | Builtin(BuiltinFnLikeExpander), | ||
20 | BuiltinDerive(BuiltinDeriveExpander), | ||
21 | ProcMacro(ProcMacroExpander), | ||
22 | } | ||
23 | |||
24 | impl TokenExpander { | ||
25 | pub fn expand( | ||
26 | &self, | ||
27 | db: &dyn AstDatabase, | ||
28 | id: LazyMacroId, | ||
29 | tt: &tt::Subtree, | ||
30 | ) -> mbe::ExpandResult<tt::Subtree> { | ||
31 | match self { | ||
32 | TokenExpander::MacroRules(it) => it.expand(tt), | ||
33 | // FIXME switch these to ExpandResult as well | ||
34 | TokenExpander::Builtin(it) => it.expand(db, id, tt).into(), | ||
35 | TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt).into(), | ||
36 | TokenExpander::ProcMacro(_) => { | ||
37 | // We store the result in salsa db to prevent non-determinisc behavior in | ||
38 | // some proc-macro implementation | ||
39 | // See #4315 for details | ||
40 | db.expand_proc_macro(id.into()).into() | ||
41 | } | ||
42 | } | ||
43 | } | ||
44 | |||
45 | pub fn map_id_down(&self, id: tt::TokenId) -> tt::TokenId { | ||
46 | match self { | ||
47 | TokenExpander::MacroRules(it) => it.map_id_down(id), | ||
48 | TokenExpander::Builtin(..) => id, | ||
49 | TokenExpander::BuiltinDerive(..) => id, | ||
50 | TokenExpander::ProcMacro(..) => id, | ||
51 | } | ||
52 | } | ||
53 | |||
54 | pub fn map_id_up(&self, id: tt::TokenId) -> (tt::TokenId, mbe::Origin) { | ||
55 | match self { | ||
56 | TokenExpander::MacroRules(it) => it.map_id_up(id), | ||
57 | TokenExpander::Builtin(..) => (id, mbe::Origin::Call), | ||
58 | TokenExpander::BuiltinDerive(..) => (id, mbe::Origin::Call), | ||
59 | TokenExpander::ProcMacro(..) => (id, mbe::Origin::Call), | ||
60 | } | ||
61 | } | ||
62 | } | ||
63 | |||
64 | // FIXME: rename to ExpandDatabase | ||
65 | #[salsa::query_group(AstDatabaseStorage)] | ||
66 | pub trait AstDatabase: SourceDatabase { | ||
67 | fn ast_id_map(&self, file_id: HirFileId) -> Arc<AstIdMap>; | ||
68 | |||
69 | #[salsa::transparent] | ||
70 | fn parse_or_expand(&self, file_id: HirFileId) -> Option<SyntaxNode>; | ||
71 | |||
72 | #[salsa::interned] | ||
73 | fn intern_macro(&self, macro_call: MacroCallLoc) -> LazyMacroId; | ||
74 | fn macro_arg_text(&self, id: MacroCallId) -> Option<GreenNode>; | ||
75 | #[salsa::transparent] | ||
76 | fn macro_arg(&self, id: MacroCallId) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>>; | ||
77 | fn macro_def(&self, id: MacroDefId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>>; | ||
78 | fn parse_macro(&self, macro_file: MacroFile) | ||
79 | -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)>; | ||
80 | fn macro_expand(&self, macro_call: MacroCallId) -> (Option<Arc<tt::Subtree>>, Option<String>); | ||
81 | |||
82 | #[salsa::interned] | ||
83 | fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId; | ||
84 | |||
85 | fn expand_proc_macro(&self, call: MacroCallId) -> Result<tt::Subtree, mbe::ExpandError>; | ||
86 | } | ||
87 | |||
88 | /// This expands the given macro call, but with different arguments. This is | ||
89 | /// used for completion, where we want to see what 'would happen' if we insert a | ||
90 | /// token. The `token_to_map` mapped down into the expansion, with the mapped | ||
91 | /// token returned. | ||
92 | pub fn expand_hypothetical( | ||
93 | db: &dyn AstDatabase, | ||
94 | actual_macro_call: MacroCallId, | ||
95 | hypothetical_args: &syntax::ast::TokenTree, | ||
96 | token_to_map: syntax::SyntaxToken, | ||
97 | ) -> Option<(SyntaxNode, syntax::SyntaxToken)> { | ||
98 | let macro_file = MacroFile { macro_call_id: actual_macro_call }; | ||
99 | let (tt, tmap_1) = mbe::syntax_node_to_token_tree(hypothetical_args.syntax()).unwrap(); | ||
100 | let range = | ||
101 | token_to_map.text_range().checked_sub(hypothetical_args.syntax().text_range().start())?; | ||
102 | let token_id = tmap_1.token_by_range(range)?; | ||
103 | let macro_def = expander(db, actual_macro_call)?; | ||
104 | let (node, tmap_2) = | ||
105 | parse_macro_with_arg(db, macro_file, Some(std::sync::Arc::new((tt, tmap_1))))?; | ||
106 | let token_id = macro_def.0.map_id_down(token_id); | ||
107 | let range = tmap_2.range_by_token(token_id)?.by_kind(token_to_map.kind())?; | ||
108 | let token = syntax::algo::find_covering_element(&node.syntax_node(), range).into_token()?; | ||
109 | Some((node.syntax_node(), token)) | ||
110 | } | ||
111 | |||
112 | pub(crate) fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<AstIdMap> { | ||
113 | let map = | ||
114 | db.parse_or_expand(file_id).map_or_else(AstIdMap::default, |it| AstIdMap::from_source(&it)); | ||
115 | Arc::new(map) | ||
116 | } | ||
117 | |||
118 | pub(crate) fn macro_def( | ||
119 | db: &dyn AstDatabase, | ||
120 | id: MacroDefId, | ||
121 | ) -> Option<Arc<(TokenExpander, mbe::TokenMap)>> { | ||
122 | match id.kind { | ||
123 | MacroDefKind::Declarative => { | ||
124 | let macro_call = id.ast_id?.to_node(db); | ||
125 | let arg = macro_call.token_tree()?; | ||
126 | let (tt, tmap) = mbe::ast_to_token_tree(&arg).or_else(|| { | ||
127 | log::warn!("fail on macro_def to token tree: {:#?}", arg); | ||
128 | None | ||
129 | })?; | ||
130 | let rules = match MacroRules::parse(&tt) { | ||
131 | Ok(it) => it, | ||
132 | Err(err) => { | ||
133 | log::warn!("fail on macro_def parse: error: {:#?} {:#?}", err, tt); | ||
134 | return None; | ||
135 | } | ||
136 | }; | ||
137 | Some(Arc::new((TokenExpander::MacroRules(rules), tmap))) | ||
138 | } | ||
139 | MacroDefKind::BuiltIn(expander) => { | ||
140 | Some(Arc::new((TokenExpander::Builtin(expander), mbe::TokenMap::default()))) | ||
141 | } | ||
142 | MacroDefKind::BuiltInDerive(expander) => { | ||
143 | Some(Arc::new((TokenExpander::BuiltinDerive(expander), mbe::TokenMap::default()))) | ||
144 | } | ||
145 | MacroDefKind::BuiltInEager(_) => None, | ||
146 | MacroDefKind::CustomDerive(expander) => { | ||
147 | Some(Arc::new((TokenExpander::ProcMacro(expander), mbe::TokenMap::default()))) | ||
148 | } | ||
149 | } | ||
150 | } | ||
151 | |||
152 | pub(crate) fn macro_arg_text(db: &dyn AstDatabase, id: MacroCallId) -> Option<GreenNode> { | ||
153 | let id = match id { | ||
154 | MacroCallId::LazyMacro(id) => id, | ||
155 | MacroCallId::EagerMacro(_id) => { | ||
156 | // FIXME: support macro_arg for eager macro | ||
157 | return None; | ||
158 | } | ||
159 | }; | ||
160 | let loc = db.lookup_intern_macro(id); | ||
161 | let arg = loc.kind.arg(db)?; | ||
162 | Some(arg.green().clone()) | ||
163 | } | ||
164 | |||
165 | pub(crate) fn macro_arg( | ||
166 | db: &dyn AstDatabase, | ||
167 | id: MacroCallId, | ||
168 | ) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>> { | ||
169 | let arg = db.macro_arg_text(id)?; | ||
170 | let (tt, tmap) = mbe::syntax_node_to_token_tree(&SyntaxNode::new_root(arg))?; | ||
171 | Some(Arc::new((tt, tmap))) | ||
172 | } | ||
173 | |||
174 | pub(crate) fn macro_expand( | ||
175 | db: &dyn AstDatabase, | ||
176 | id: MacroCallId, | ||
177 | ) -> (Option<Arc<tt::Subtree>>, Option<String>) { | ||
178 | macro_expand_with_arg(db, id, None) | ||
179 | } | ||
180 | |||
181 | fn expander(db: &dyn AstDatabase, id: MacroCallId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>> { | ||
182 | let lazy_id = match id { | ||
183 | MacroCallId::LazyMacro(id) => id, | ||
184 | MacroCallId::EagerMacro(_id) => { | ||
185 | return None; | ||
186 | } | ||
187 | }; | ||
188 | |||
189 | let loc = db.lookup_intern_macro(lazy_id); | ||
190 | let macro_rules = db.macro_def(loc.def)?; | ||
191 | Some(macro_rules) | ||
192 | } | ||
193 | |||
194 | fn macro_expand_with_arg( | ||
195 | db: &dyn AstDatabase, | ||
196 | id: MacroCallId, | ||
197 | arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>, | ||
198 | ) -> (Option<Arc<tt::Subtree>>, Option<String>) { | ||
199 | let lazy_id = match id { | ||
200 | MacroCallId::LazyMacro(id) => id, | ||
201 | MacroCallId::EagerMacro(id) => { | ||
202 | if arg.is_some() { | ||
203 | return ( | ||
204 | None, | ||
205 | Some("hypothetical macro expansion not implemented for eager macro".to_owned()), | ||
206 | ); | ||
207 | } else { | ||
208 | return (Some(db.lookup_intern_eager_expansion(id).subtree), None); | ||
209 | } | ||
210 | } | ||
211 | }; | ||
212 | |||
213 | let loc = db.lookup_intern_macro(lazy_id); | ||
214 | let macro_arg = match arg.or_else(|| db.macro_arg(id)) { | ||
215 | Some(it) => it, | ||
216 | None => return (None, Some("Fail to args in to tt::TokenTree".into())), | ||
217 | }; | ||
218 | |||
219 | let macro_rules = match db.macro_def(loc.def) { | ||
220 | Some(it) => it, | ||
221 | None => return (None, Some("Fail to find macro definition".into())), | ||
222 | }; | ||
223 | let ExpandResult(tt, err) = macro_rules.0.expand(db, lazy_id, ¯o_arg.0); | ||
224 | // Set a hard limit for the expanded tt | ||
225 | let count = tt.count(); | ||
226 | if count > 65536 { | ||
227 | return (None, Some(format!("Total tokens count exceed limit : count = {}", count))); | ||
228 | } | ||
229 | (Some(Arc::new(tt)), err.map(|e| format!("{:?}", e))) | ||
230 | } | ||
231 | |||
232 | pub(crate) fn expand_proc_macro( | ||
233 | db: &dyn AstDatabase, | ||
234 | id: MacroCallId, | ||
235 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
236 | let lazy_id = match id { | ||
237 | MacroCallId::LazyMacro(id) => id, | ||
238 | MacroCallId::EagerMacro(_) => unreachable!(), | ||
239 | }; | ||
240 | |||
241 | let loc = db.lookup_intern_macro(lazy_id); | ||
242 | let macro_arg = match db.macro_arg(id) { | ||
243 | Some(it) => it, | ||
244 | None => { | ||
245 | return Err( | ||
246 | tt::ExpansionError::Unknown("No arguments for proc-macro".to_string()).into() | ||
247 | ) | ||
248 | } | ||
249 | }; | ||
250 | |||
251 | let expander = match loc.def.kind { | ||
252 | MacroDefKind::CustomDerive(expander) => expander, | ||
253 | _ => unreachable!(), | ||
254 | }; | ||
255 | |||
256 | expander.expand(db, lazy_id, ¯o_arg.0) | ||
257 | } | ||
258 | |||
259 | pub(crate) fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Option<SyntaxNode> { | ||
260 | match file_id.0 { | ||
261 | HirFileIdRepr::FileId(file_id) => Some(db.parse(file_id).tree().syntax().clone()), | ||
262 | HirFileIdRepr::MacroFile(macro_file) => { | ||
263 | db.parse_macro(macro_file).map(|(it, _)| it.syntax_node()) | ||
264 | } | ||
265 | } | ||
266 | } | ||
267 | |||
268 | pub(crate) fn parse_macro( | ||
269 | db: &dyn AstDatabase, | ||
270 | macro_file: MacroFile, | ||
271 | ) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> { | ||
272 | parse_macro_with_arg(db, macro_file, None) | ||
273 | } | ||
274 | |||
275 | pub fn parse_macro_with_arg( | ||
276 | db: &dyn AstDatabase, | ||
277 | macro_file: MacroFile, | ||
278 | arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>, | ||
279 | ) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> { | ||
280 | let _p = profile::span("parse_macro_query"); | ||
281 | |||
282 | let macro_call_id = macro_file.macro_call_id; | ||
283 | let (tt, err) = if let Some(arg) = arg { | ||
284 | macro_expand_with_arg(db, macro_call_id, Some(arg)) | ||
285 | } else { | ||
286 | db.macro_expand(macro_call_id) | ||
287 | }; | ||
288 | if let Some(err) = &err { | ||
289 | // Note: | ||
290 | // The final goal we would like to make all parse_macro success, | ||
291 | // such that the following log will not call anyway. | ||
292 | match macro_call_id { | ||
293 | MacroCallId::LazyMacro(id) => { | ||
294 | let loc: MacroCallLoc = db.lookup_intern_macro(id); | ||
295 | let node = loc.kind.node(db); | ||
296 | |||
297 | // collect parent information for warning log | ||
298 | let parents = std::iter::successors(loc.kind.file_id().call_node(db), |it| { | ||
299 | it.file_id.call_node(db) | ||
300 | }) | ||
301 | .map(|n| format!("{:#}", n.value)) | ||
302 | .collect::<Vec<_>>() | ||
303 | .join("\n"); | ||
304 | |||
305 | log::warn!( | ||
306 | "fail on macro_parse: (reason: {} macro_call: {:#}) parents: {}", | ||
307 | err, | ||
308 | node.value, | ||
309 | parents | ||
310 | ); | ||
311 | } | ||
312 | _ => { | ||
313 | log::warn!("fail on macro_parse: (reason: {})", err); | ||
314 | } | ||
315 | } | ||
316 | }; | ||
317 | let tt = tt?; | ||
318 | |||
319 | let fragment_kind = to_fragment_kind(db, macro_call_id); | ||
320 | |||
321 | let (parse, rev_token_map) = mbe::token_tree_to_syntax_node(&tt, fragment_kind).ok()?; | ||
322 | |||
323 | if err.is_none() { | ||
324 | Some((parse, Arc::new(rev_token_map))) | ||
325 | } else { | ||
326 | // FIXME: | ||
327 | // In future, we should propagate the actual error with recovery information | ||
328 | // instead of ignore the error here. | ||
329 | |||
330 | // Safe check for recurisve identity macro | ||
331 | let node = parse.syntax_node(); | ||
332 | let file: HirFileId = macro_file.into(); | ||
333 | let call_node = file.call_node(db)?; | ||
334 | |||
335 | if !diff(&node, &call_node.value).is_empty() { | ||
336 | Some((parse, Arc::new(rev_token_map))) | ||
337 | } else { | ||
338 | None | ||
339 | } | ||
340 | } | ||
341 | } | ||
342 | |||
343 | /// Given a `MacroCallId`, return what `FragmentKind` it belongs to. | ||
344 | /// FIXME: Not completed | ||
345 | fn to_fragment_kind(db: &dyn AstDatabase, id: MacroCallId) -> FragmentKind { | ||
346 | let lazy_id = match id { | ||
347 | MacroCallId::LazyMacro(id) => id, | ||
348 | MacroCallId::EagerMacro(id) => { | ||
349 | return db.lookup_intern_eager_expansion(id).fragment; | ||
350 | } | ||
351 | }; | ||
352 | let syn = db.lookup_intern_macro(lazy_id).kind.node(db).value; | ||
353 | |||
354 | let parent = match syn.parent() { | ||
355 | Some(it) => it, | ||
356 | None => { | ||
357 | // FIXME: | ||
358 | // If it is root, which means the parent HirFile | ||
359 | // MacroKindFile must be non-items | ||
360 | // return expr now. | ||
361 | return FragmentKind::Expr; | ||
362 | } | ||
363 | }; | ||
364 | |||
365 | match parent.kind() { | ||
366 | MACRO_ITEMS | SOURCE_FILE => FragmentKind::Items, | ||
367 | ITEM_LIST => FragmentKind::Items, | ||
368 | LET_STMT => { | ||
369 | // FIXME: Handle Pattern | ||
370 | FragmentKind::Expr | ||
371 | } | ||
372 | // FIXME: Expand to statements in appropriate positions; HIR lowering needs to handle that | ||
373 | EXPR_STMT | BLOCK_EXPR => FragmentKind::Expr, | ||
374 | ARG_LIST => FragmentKind::Expr, | ||
375 | TRY_EXPR => FragmentKind::Expr, | ||
376 | TUPLE_EXPR => FragmentKind::Expr, | ||
377 | PAREN_EXPR => FragmentKind::Expr, | ||
378 | |||
379 | FOR_EXPR => FragmentKind::Expr, | ||
380 | PATH_EXPR => FragmentKind::Expr, | ||
381 | CLOSURE_EXPR => FragmentKind::Expr, | ||
382 | CONDITION => FragmentKind::Expr, | ||
383 | BREAK_EXPR => FragmentKind::Expr, | ||
384 | RETURN_EXPR => FragmentKind::Expr, | ||
385 | MATCH_EXPR => FragmentKind::Expr, | ||
386 | MATCH_ARM => FragmentKind::Expr, | ||
387 | MATCH_GUARD => FragmentKind::Expr, | ||
388 | RECORD_EXPR_FIELD => FragmentKind::Expr, | ||
389 | CALL_EXPR => FragmentKind::Expr, | ||
390 | INDEX_EXPR => FragmentKind::Expr, | ||
391 | METHOD_CALL_EXPR => FragmentKind::Expr, | ||
392 | AWAIT_EXPR => FragmentKind::Expr, | ||
393 | CAST_EXPR => FragmentKind::Expr, | ||
394 | REF_EXPR => FragmentKind::Expr, | ||
395 | PREFIX_EXPR => FragmentKind::Expr, | ||
396 | RANGE_EXPR => FragmentKind::Expr, | ||
397 | BIN_EXPR => FragmentKind::Expr, | ||
398 | _ => { | ||
399 | // Unknown , Just guess it is `Items` | ||
400 | FragmentKind::Items | ||
401 | } | ||
402 | } | ||
403 | } | ||