diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-08-13 15:31:49 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-08-13 15:31:49 +0100 |
commit | e9926948ca267932ccc1341388bfd1b3fa88a001 (patch) | |
tree | cc4b797cb39a40b59e9e3d37178e8a1907f12358 /crates/hir_expand/src/lib.rs | |
parent | 902f74c2697cc2a50de9067845814a2a852fccfd (diff) | |
parent | 50f8c1ebf23f634b68529603a917e3feeda457fa (diff) |
Merge #5747
5747: Rename crate r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/hir_expand/src/lib.rs')
-rw-r--r-- | crates/hir_expand/src/lib.rs | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs new file mode 100644 index 000000000..2be15e841 --- /dev/null +++ b/crates/hir_expand/src/lib.rs | |||
@@ -0,0 +1,453 @@ | |||
1 | //! `hir_expand` deals with macro expansion. | ||
2 | //! | ||
3 | //! Specifically, it implements a concept of `MacroFile` -- a file whose syntax | ||
4 | //! tree originates not from the text of some `FileId`, but from some macro | ||
5 | //! expansion. | ||
6 | |||
7 | pub mod db; | ||
8 | pub mod ast_id_map; | ||
9 | pub mod name; | ||
10 | pub mod hygiene; | ||
11 | pub mod diagnostics; | ||
12 | pub mod builtin_derive; | ||
13 | pub mod builtin_macro; | ||
14 | pub mod proc_macro; | ||
15 | pub mod quote; | ||
16 | pub mod eager; | ||
17 | |||
18 | use std::hash::Hash; | ||
19 | use std::sync::Arc; | ||
20 | |||
21 | use base_db::{impl_intern_key, salsa, CrateId, FileId}; | ||
22 | use syntax::{ | ||
23 | algo, | ||
24 | ast::{self, AstNode}, | ||
25 | SyntaxNode, SyntaxToken, TextSize, | ||
26 | }; | ||
27 | |||
28 | use crate::ast_id_map::FileAstId; | ||
29 | use crate::builtin_derive::BuiltinDeriveExpander; | ||
30 | use crate::builtin_macro::{BuiltinFnLikeExpander, EagerExpander}; | ||
31 | use crate::proc_macro::ProcMacroExpander; | ||
32 | |||
33 | #[cfg(test)] | ||
34 | mod test_db; | ||
35 | |||
36 | /// Input to the analyzer is a set of files, where each file is identified by | ||
37 | /// `FileId` and contains source code. However, another source of source code in | ||
38 | /// Rust are macros: each macro can be thought of as producing a "temporary | ||
39 | /// file". To assign an id to such a file, we use the id of the macro call that | ||
40 | /// produced the file. So, a `HirFileId` is either a `FileId` (source code | ||
41 | /// written by user), or a `MacroCallId` (source code produced by macro). | ||
42 | /// | ||
43 | /// What is a `MacroCallId`? Simplifying, it's a `HirFileId` of a file | ||
44 | /// containing the call plus the offset of the macro call in the file. Note that | ||
45 | /// this is a recursive definition! However, the size_of of `HirFileId` is | ||
46 | /// finite (because everything bottoms out at the real `FileId`) and small | ||
47 | /// (`MacroCallId` uses the location interning. You can check details here: | ||
48 | /// https://en.wikipedia.org/wiki/String_interning). | ||
49 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
50 | pub struct HirFileId(HirFileIdRepr); | ||
51 | |||
52 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
53 | enum HirFileIdRepr { | ||
54 | FileId(FileId), | ||
55 | MacroFile(MacroFile), | ||
56 | } | ||
57 | |||
58 | impl From<FileId> for HirFileId { | ||
59 | fn from(id: FileId) -> Self { | ||
60 | HirFileId(HirFileIdRepr::FileId(id)) | ||
61 | } | ||
62 | } | ||
63 | |||
64 | impl From<MacroFile> for HirFileId { | ||
65 | fn from(id: MacroFile) -> Self { | ||
66 | HirFileId(HirFileIdRepr::MacroFile(id)) | ||
67 | } | ||
68 | } | ||
69 | |||
70 | impl HirFileId { | ||
71 | /// For macro-expansion files, returns the file original source file the | ||
72 | /// expansion originated from. | ||
73 | pub fn original_file(self, db: &dyn db::AstDatabase) -> FileId { | ||
74 | match self.0 { | ||
75 | HirFileIdRepr::FileId(file_id) => file_id, | ||
76 | HirFileIdRepr::MacroFile(macro_file) => { | ||
77 | let file_id = match macro_file.macro_call_id { | ||
78 | MacroCallId::LazyMacro(id) => { | ||
79 | let loc = db.lookup_intern_macro(id); | ||
80 | loc.kind.file_id() | ||
81 | } | ||
82 | MacroCallId::EagerMacro(id) => { | ||
83 | let loc = db.lookup_intern_eager_expansion(id); | ||
84 | loc.file_id | ||
85 | } | ||
86 | }; | ||
87 | file_id.original_file(db) | ||
88 | } | ||
89 | } | ||
90 | } | ||
91 | |||
92 | pub fn expansion_level(self, db: &dyn db::AstDatabase) -> u32 { | ||
93 | let mut level = 0; | ||
94 | let mut curr = self; | ||
95 | while let HirFileIdRepr::MacroFile(macro_file) = curr.0 { | ||
96 | level += 1; | ||
97 | curr = match macro_file.macro_call_id { | ||
98 | MacroCallId::LazyMacro(id) => { | ||
99 | let loc = db.lookup_intern_macro(id); | ||
100 | loc.kind.file_id() | ||
101 | } | ||
102 | MacroCallId::EagerMacro(id) => { | ||
103 | let loc = db.lookup_intern_eager_expansion(id); | ||
104 | loc.file_id | ||
105 | } | ||
106 | }; | ||
107 | } | ||
108 | level | ||
109 | } | ||
110 | |||
111 | /// If this is a macro call, returns the syntax node of the call. | ||
112 | pub fn call_node(self, db: &dyn db::AstDatabase) -> Option<InFile<SyntaxNode>> { | ||
113 | match self.0 { | ||
114 | HirFileIdRepr::FileId(_) => None, | ||
115 | HirFileIdRepr::MacroFile(macro_file) => { | ||
116 | let lazy_id = match macro_file.macro_call_id { | ||
117 | MacroCallId::LazyMacro(id) => id, | ||
118 | MacroCallId::EagerMacro(_id) => { | ||
119 | // FIXME: handle call node for eager macro | ||
120 | return None; | ||
121 | } | ||
122 | }; | ||
123 | let loc = db.lookup_intern_macro(lazy_id); | ||
124 | Some(loc.kind.node(db)) | ||
125 | } | ||
126 | } | ||
127 | } | ||
128 | |||
129 | /// Return expansion information if it is a macro-expansion file | ||
130 | pub fn expansion_info(self, db: &dyn db::AstDatabase) -> Option<ExpansionInfo> { | ||
131 | match self.0 { | ||
132 | HirFileIdRepr::FileId(_) => None, | ||
133 | HirFileIdRepr::MacroFile(macro_file) => { | ||
134 | let lazy_id = match macro_file.macro_call_id { | ||
135 | MacroCallId::LazyMacro(id) => id, | ||
136 | MacroCallId::EagerMacro(_id) => { | ||
137 | // FIXME: handle expansion_info for eager macro | ||
138 | return None; | ||
139 | } | ||
140 | }; | ||
141 | let loc: MacroCallLoc = db.lookup_intern_macro(lazy_id); | ||
142 | |||
143 | let arg_tt = loc.kind.arg(db)?; | ||
144 | let def_tt = loc.def.ast_id?.to_node(db).token_tree()?; | ||
145 | |||
146 | let macro_def = db.macro_def(loc.def)?; | ||
147 | let (parse, exp_map) = db.parse_macro(macro_file)?; | ||
148 | let macro_arg = db.macro_arg(macro_file.macro_call_id)?; | ||
149 | |||
150 | Some(ExpansionInfo { | ||
151 | expanded: InFile::new(self, parse.syntax_node()), | ||
152 | arg: InFile::new(loc.kind.file_id(), arg_tt), | ||
153 | def: InFile::new(loc.def.ast_id?.file_id, def_tt), | ||
154 | macro_arg, | ||
155 | macro_def, | ||
156 | exp_map, | ||
157 | }) | ||
158 | } | ||
159 | } | ||
160 | } | ||
161 | |||
162 | /// Indicate it is macro file generated for builtin derive | ||
163 | pub fn is_builtin_derive(&self, db: &dyn db::AstDatabase) -> Option<InFile<ast::Item>> { | ||
164 | match self.0 { | ||
165 | HirFileIdRepr::FileId(_) => None, | ||
166 | HirFileIdRepr::MacroFile(macro_file) => { | ||
167 | let lazy_id = match macro_file.macro_call_id { | ||
168 | MacroCallId::LazyMacro(id) => id, | ||
169 | MacroCallId::EagerMacro(_id) => { | ||
170 | return None; | ||
171 | } | ||
172 | }; | ||
173 | let loc: MacroCallLoc = db.lookup_intern_macro(lazy_id); | ||
174 | let item = match loc.def.kind { | ||
175 | MacroDefKind::BuiltInDerive(_) => loc.kind.node(db), | ||
176 | _ => return None, | ||
177 | }; | ||
178 | Some(item.with_value(ast::Item::cast(item.value.clone())?)) | ||
179 | } | ||
180 | } | ||
181 | } | ||
182 | } | ||
183 | |||
184 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
185 | pub struct MacroFile { | ||
186 | macro_call_id: MacroCallId, | ||
187 | } | ||
188 | |||
189 | /// `MacroCallId` identifies a particular macro invocation, like | ||
190 | /// `println!("Hello, {}", world)`. | ||
191 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
192 | pub enum MacroCallId { | ||
193 | LazyMacro(LazyMacroId), | ||
194 | EagerMacro(EagerMacroId), | ||
195 | } | ||
196 | |||
197 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
198 | pub struct LazyMacroId(salsa::InternId); | ||
199 | impl_intern_key!(LazyMacroId); | ||
200 | |||
201 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
202 | pub struct EagerMacroId(salsa::InternId); | ||
203 | impl_intern_key!(EagerMacroId); | ||
204 | |||
205 | impl From<LazyMacroId> for MacroCallId { | ||
206 | fn from(it: LazyMacroId) -> Self { | ||
207 | MacroCallId::LazyMacro(it) | ||
208 | } | ||
209 | } | ||
210 | impl From<EagerMacroId> for MacroCallId { | ||
211 | fn from(it: EagerMacroId) -> Self { | ||
212 | MacroCallId::EagerMacro(it) | ||
213 | } | ||
214 | } | ||
215 | |||
216 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
217 | pub struct MacroDefId { | ||
218 | // FIXME: krate and ast_id are currently optional because we don't have a | ||
219 | // definition location for built-in derives. There is one, though: the | ||
220 | // standard library defines them. The problem is that it uses the new | ||
221 | // `macro` syntax for this, which we don't support yet. As soon as we do | ||
222 | // (which will probably require touching this code), we can instead use | ||
223 | // that (and also remove the hacks for resolving built-in derives). | ||
224 | pub krate: Option<CrateId>, | ||
225 | pub ast_id: Option<AstId<ast::MacroCall>>, | ||
226 | pub kind: MacroDefKind, | ||
227 | |||
228 | pub local_inner: bool, | ||
229 | } | ||
230 | |||
231 | impl MacroDefId { | ||
232 | pub fn as_lazy_macro( | ||
233 | self, | ||
234 | db: &dyn db::AstDatabase, | ||
235 | krate: CrateId, | ||
236 | kind: MacroCallKind, | ||
237 | ) -> LazyMacroId { | ||
238 | db.intern_macro(MacroCallLoc { def: self, krate, kind }) | ||
239 | } | ||
240 | } | ||
241 | |||
242 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
243 | pub enum MacroDefKind { | ||
244 | Declarative, | ||
245 | BuiltIn(BuiltinFnLikeExpander), | ||
246 | // FIXME: maybe just Builtin and rename BuiltinFnLikeExpander to BuiltinExpander | ||
247 | BuiltInDerive(BuiltinDeriveExpander), | ||
248 | BuiltInEager(EagerExpander), | ||
249 | CustomDerive(ProcMacroExpander), | ||
250 | } | ||
251 | |||
252 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
253 | pub struct MacroCallLoc { | ||
254 | pub(crate) def: MacroDefId, | ||
255 | pub(crate) krate: CrateId, | ||
256 | pub(crate) kind: MacroCallKind, | ||
257 | } | ||
258 | |||
259 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
260 | pub enum MacroCallKind { | ||
261 | FnLike(AstId<ast::MacroCall>), | ||
262 | Attr(AstId<ast::Item>, String), | ||
263 | } | ||
264 | |||
265 | impl MacroCallKind { | ||
266 | fn file_id(&self) -> HirFileId { | ||
267 | match self { | ||
268 | MacroCallKind::FnLike(ast_id) => ast_id.file_id, | ||
269 | MacroCallKind::Attr(ast_id, _) => ast_id.file_id, | ||
270 | } | ||
271 | } | ||
272 | |||
273 | fn node(&self, db: &dyn db::AstDatabase) -> InFile<SyntaxNode> { | ||
274 | match self { | ||
275 | MacroCallKind::FnLike(ast_id) => ast_id.with_value(ast_id.to_node(db).syntax().clone()), | ||
276 | MacroCallKind::Attr(ast_id, _) => { | ||
277 | ast_id.with_value(ast_id.to_node(db).syntax().clone()) | ||
278 | } | ||
279 | } | ||
280 | } | ||
281 | |||
282 | fn arg(&self, db: &dyn db::AstDatabase) -> Option<SyntaxNode> { | ||
283 | match self { | ||
284 | MacroCallKind::FnLike(ast_id) => { | ||
285 | Some(ast_id.to_node(db).token_tree()?.syntax().clone()) | ||
286 | } | ||
287 | MacroCallKind::Attr(ast_id, _) => Some(ast_id.to_node(db).syntax().clone()), | ||
288 | } | ||
289 | } | ||
290 | } | ||
291 | |||
292 | impl MacroCallId { | ||
293 | pub fn as_file(self) -> HirFileId { | ||
294 | MacroFile { macro_call_id: self }.into() | ||
295 | } | ||
296 | } | ||
297 | |||
298 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
299 | pub struct EagerCallLoc { | ||
300 | pub(crate) def: MacroDefId, | ||
301 | pub(crate) fragment: FragmentKind, | ||
302 | pub(crate) subtree: Arc<tt::Subtree>, | ||
303 | pub(crate) krate: CrateId, | ||
304 | pub(crate) file_id: HirFileId, | ||
305 | } | ||
306 | |||
307 | /// ExpansionInfo mainly describes how to map text range between src and expanded macro | ||
308 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
309 | pub struct ExpansionInfo { | ||
310 | expanded: InFile<SyntaxNode>, | ||
311 | arg: InFile<SyntaxNode>, | ||
312 | def: InFile<ast::TokenTree>, | ||
313 | |||
314 | macro_def: Arc<(db::TokenExpander, mbe::TokenMap)>, | ||
315 | macro_arg: Arc<(tt::Subtree, mbe::TokenMap)>, | ||
316 | exp_map: Arc<mbe::TokenMap>, | ||
317 | } | ||
318 | |||
319 | pub use mbe::Origin; | ||
320 | use parser::FragmentKind; | ||
321 | |||
322 | impl ExpansionInfo { | ||
323 | pub fn call_node(&self) -> Option<InFile<SyntaxNode>> { | ||
324 | Some(self.arg.with_value(self.arg.value.parent()?)) | ||
325 | } | ||
326 | |||
327 | pub fn map_token_down(&self, token: InFile<&SyntaxToken>) -> Option<InFile<SyntaxToken>> { | ||
328 | assert_eq!(token.file_id, self.arg.file_id); | ||
329 | let range = token.value.text_range().checked_sub(self.arg.value.text_range().start())?; | ||
330 | let token_id = self.macro_arg.1.token_by_range(range)?; | ||
331 | let token_id = self.macro_def.0.map_id_down(token_id); | ||
332 | |||
333 | let range = self.exp_map.range_by_token(token_id)?.by_kind(token.value.kind())?; | ||
334 | |||
335 | let token = algo::find_covering_element(&self.expanded.value, range).into_token()?; | ||
336 | |||
337 | Some(self.expanded.with_value(token)) | ||
338 | } | ||
339 | |||
340 | pub fn map_token_up( | ||
341 | &self, | ||
342 | token: InFile<&SyntaxToken>, | ||
343 | ) -> Option<(InFile<SyntaxToken>, Origin)> { | ||
344 | let token_id = self.exp_map.token_by_range(token.value.text_range())?; | ||
345 | |||
346 | let (token_id, origin) = self.macro_def.0.map_id_up(token_id); | ||
347 | let (token_map, tt) = match origin { | ||
348 | mbe::Origin::Call => (&self.macro_arg.1, self.arg.clone()), | ||
349 | mbe::Origin::Def => { | ||
350 | (&self.macro_def.1, self.def.as_ref().map(|tt| tt.syntax().clone())) | ||
351 | } | ||
352 | }; | ||
353 | |||
354 | let range = token_map.range_by_token(token_id)?.by_kind(token.value.kind())?; | ||
355 | let token = algo::find_covering_element(&tt.value, range + tt.value.text_range().start()) | ||
356 | .into_token()?; | ||
357 | Some((tt.with_value(token), origin)) | ||
358 | } | ||
359 | } | ||
360 | |||
361 | /// `AstId` points to an AST node in any file. | ||
362 | /// | ||
363 | /// It is stable across reparses, and can be used as salsa key/value. | ||
364 | // FIXME: isn't this just a `Source<FileAstId<N>>` ? | ||
365 | pub type AstId<N> = InFile<FileAstId<N>>; | ||
366 | |||
367 | impl<N: AstNode> AstId<N> { | ||
368 | pub fn to_node(&self, db: &dyn db::AstDatabase) -> N { | ||
369 | let root = db.parse_or_expand(self.file_id).unwrap(); | ||
370 | db.ast_id_map(self.file_id).get(self.value).to_node(&root) | ||
371 | } | ||
372 | } | ||
373 | |||
374 | /// `InFile<T>` stores a value of `T` inside a particular file/syntax tree. | ||
375 | /// | ||
376 | /// Typical usages are: | ||
377 | /// | ||
378 | /// * `InFile<SyntaxNode>` -- syntax node in a file | ||
379 | /// * `InFile<ast::FnDef>` -- ast node in a file | ||
380 | /// * `InFile<TextSize>` -- offset in a file | ||
381 | #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] | ||
382 | pub struct InFile<T> { | ||
383 | pub file_id: HirFileId, | ||
384 | pub value: T, | ||
385 | } | ||
386 | |||
387 | impl<T> InFile<T> { | ||
388 | pub fn new(file_id: HirFileId, value: T) -> InFile<T> { | ||
389 | InFile { file_id, value } | ||
390 | } | ||
391 | |||
392 | // Similarly, naming here is stupid... | ||
393 | pub fn with_value<U>(&self, value: U) -> InFile<U> { | ||
394 | InFile::new(self.file_id, value) | ||
395 | } | ||
396 | |||
397 | pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> InFile<U> { | ||
398 | InFile::new(self.file_id, f(self.value)) | ||
399 | } | ||
400 | pub fn as_ref(&self) -> InFile<&T> { | ||
401 | self.with_value(&self.value) | ||
402 | } | ||
403 | pub fn file_syntax(&self, db: &dyn db::AstDatabase) -> SyntaxNode { | ||
404 | db.parse_or_expand(self.file_id).expect("source created from invalid file") | ||
405 | } | ||
406 | } | ||
407 | |||
408 | impl<T: Clone> InFile<&T> { | ||
409 | pub fn cloned(&self) -> InFile<T> { | ||
410 | self.with_value(self.value.clone()) | ||
411 | } | ||
412 | } | ||
413 | |||
414 | impl<T> InFile<Option<T>> { | ||
415 | pub fn transpose(self) -> Option<InFile<T>> { | ||
416 | let value = self.value?; | ||
417 | Some(InFile::new(self.file_id, value)) | ||
418 | } | ||
419 | } | ||
420 | |||
421 | impl InFile<SyntaxNode> { | ||
422 | pub fn ancestors_with_macros( | ||
423 | self, | ||
424 | db: &dyn db::AstDatabase, | ||
425 | ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ { | ||
426 | std::iter::successors(Some(self), move |node| match node.value.parent() { | ||
427 | Some(parent) => Some(node.with_value(parent)), | ||
428 | None => { | ||
429 | let parent_node = node.file_id.call_node(db)?; | ||
430 | Some(parent_node) | ||
431 | } | ||
432 | }) | ||
433 | } | ||
434 | } | ||
435 | |||
436 | impl InFile<SyntaxToken> { | ||
437 | pub fn ancestors_with_macros( | ||
438 | self, | ||
439 | db: &dyn db::AstDatabase, | ||
440 | ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ { | ||
441 | self.map(|it| it.parent()).ancestors_with_macros(db) | ||
442 | } | ||
443 | } | ||
444 | |||
445 | impl<N: AstNode> InFile<N> { | ||
446 | pub fn descendants<T: AstNode>(self) -> impl Iterator<Item = InFile<T>> { | ||
447 | self.value.syntax().descendants().filter_map(T::cast).map(move |n| self.with_value(n)) | ||
448 | } | ||
449 | |||
450 | pub fn syntax(&self) -> InFile<&SyntaxNode> { | ||
451 | self.with_value(self.value.syntax()) | ||
452 | } | ||
453 | } | ||