diff options
Diffstat (limited to 'crates/ra_hir/src/macros.rs')
-rw-r--r-- | crates/ra_hir/src/macros.rs | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/crates/ra_hir/src/macros.rs b/crates/ra_hir/src/macros.rs new file mode 100644 index 000000000..b7b75e702 --- /dev/null +++ b/crates/ra_hir/src/macros.rs | |||
@@ -0,0 +1,181 @@ | |||
1 | /// Machinery for macro expansion. | ||
2 | /// | ||
3 | /// One of the more complicated things about macros is managing the source code | ||
4 | /// that is produced after expansion. See `HirFileId` and `MacroCallId` for how | ||
5 | /// do we do that. | ||
6 | /// | ||
7 | /// When file-management question is resolved, all that is left is a token tree | ||
8 | /// to token tree transformation plus hygent. We don't have either of thouse | ||
9 | /// yet, so all macros are string based at the moment! | ||
10 | use std::sync::Arc; | ||
11 | |||
12 | use ra_db::LocalSyntaxPtr; | ||
13 | use ra_syntax::{ | ||
14 | TextRange, TextUnit, SourceFileNode, AstNode, SyntaxNode, | ||
15 | ast::{self, NameOwner}, | ||
16 | }; | ||
17 | |||
18 | use crate::{HirDatabase, MacroCallId}; | ||
19 | |||
20 | // Hard-coded defs for now :-( | ||
21 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
22 | pub enum MacroDef { | ||
23 | CTry, | ||
24 | QueryGroup, | ||
25 | } | ||
26 | |||
27 | impl MacroDef { | ||
28 | /// Expands macro call, returning the expansion and offset to be used to | ||
29 | /// convert ranges between expansion and original source. | ||
30 | pub fn ast_expand(macro_call: ast::MacroCall) -> Option<(TextUnit, MacroExpansion)> { | ||
31 | let (def, input) = MacroDef::from_call(macro_call)?; | ||
32 | let exp = def.expand(input)?; | ||
33 | let off = macro_call.token_tree()?.syntax().range().start(); | ||
34 | Some((off, exp)) | ||
35 | } | ||
36 | |||
37 | fn from_call(macro_call: ast::MacroCall) -> Option<(MacroDef, MacroInput)> { | ||
38 | let def = { | ||
39 | let path = macro_call.path()?; | ||
40 | let name_ref = path.segment()?.name_ref()?; | ||
41 | if name_ref.text() == "ctry" { | ||
42 | MacroDef::CTry | ||
43 | } else if name_ref.text() == "query_group" { | ||
44 | MacroDef::QueryGroup | ||
45 | } else { | ||
46 | return None; | ||
47 | } | ||
48 | }; | ||
49 | |||
50 | let input = { | ||
51 | let arg = macro_call.token_tree()?.syntax(); | ||
52 | MacroInput { | ||
53 | text: arg.text().to_string(), | ||
54 | } | ||
55 | }; | ||
56 | Some((def, input)) | ||
57 | } | ||
58 | |||
59 | fn expand(self, input: MacroInput) -> Option<MacroExpansion> { | ||
60 | match self { | ||
61 | MacroDef::CTry => self.expand_ctry(input), | ||
62 | MacroDef::QueryGroup => self.expand_query_group(input), | ||
63 | } | ||
64 | } | ||
65 | fn expand_ctry(self, input: MacroInput) -> Option<MacroExpansion> { | ||
66 | let text = format!( | ||
67 | r" | ||
68 | fn dummy() {{ | ||
69 | match {} {{ | ||
70 | None => return Ok(None), | ||
71 | Some(it) => it, | ||
72 | }} | ||
73 | }}", | ||
74 | input.text | ||
75 | ); | ||
76 | let file = SourceFileNode::parse(&text); | ||
77 | let match_expr = file.syntax().descendants().find_map(ast::MatchExpr::cast)?; | ||
78 | let match_arg = match_expr.expr()?; | ||
79 | let ptr = LocalSyntaxPtr::new(match_arg.syntax()); | ||
80 | let src_range = TextRange::offset_len(0.into(), TextUnit::of_str(&input.text)); | ||
81 | let ranges_map = vec![(src_range, match_arg.syntax().range())]; | ||
82 | let res = MacroExpansion { | ||
83 | text, | ||
84 | ranges_map, | ||
85 | ptr, | ||
86 | }; | ||
87 | Some(res) | ||
88 | } | ||
89 | fn expand_query_group(self, input: MacroInput) -> Option<MacroExpansion> { | ||
90 | let anchor = "trait "; | ||
91 | let pos = input.text.find(anchor)? + anchor.len(); | ||
92 | let trait_name = input.text[pos..] | ||
93 | .chars() | ||
94 | .take_while(|c| c.is_alphabetic()) | ||
95 | .collect::<String>(); | ||
96 | if trait_name.is_empty() { | ||
97 | return None; | ||
98 | } | ||
99 | let src_range = TextRange::offset_len((pos as u32).into(), TextUnit::of_str(&trait_name)); | ||
100 | let text = format!(r"trait {} {{ }}", trait_name); | ||
101 | let file = SourceFileNode::parse(&text); | ||
102 | let trait_def = file.syntax().descendants().find_map(ast::TraitDef::cast)?; | ||
103 | let name = trait_def.name()?; | ||
104 | let ptr = LocalSyntaxPtr::new(trait_def.syntax()); | ||
105 | let ranges_map = vec![(src_range, name.syntax().range())]; | ||
106 | let res = MacroExpansion { | ||
107 | text, | ||
108 | ranges_map, | ||
109 | ptr, | ||
110 | }; | ||
111 | Some(res) | ||
112 | } | ||
113 | } | ||
114 | |||
115 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
116 | pub struct MacroInput { | ||
117 | // Should be token trees | ||
118 | pub text: String, | ||
119 | } | ||
120 | |||
121 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
122 | pub struct MacroExpansion { | ||
123 | /// The result of macro expansion. Should be token tree as well. | ||
124 | text: String, | ||
125 | /// Correspondence between ranges in the original source code and ranges in | ||
126 | /// the macro. | ||
127 | ranges_map: Vec<(TextRange, TextRange)>, | ||
128 | /// Implementation detail: internally, a macro is expanded to the whole file, | ||
129 | /// even if it is an expression. This `ptr` selects the actual expansion from | ||
130 | /// the expanded file. | ||
131 | ptr: LocalSyntaxPtr, | ||
132 | } | ||
133 | |||
134 | impl MacroExpansion { | ||
135 | // FIXME: does not really make sense, macro expansion is not neccessary a | ||
136 | // whole file. See `MacroExpansion::ptr` as well. | ||
137 | pub(crate) fn file(&self) -> SourceFileNode { | ||
138 | SourceFileNode::parse(&self.text) | ||
139 | } | ||
140 | |||
141 | pub fn syntax(&self) -> SyntaxNode { | ||
142 | self.ptr.resolve(&self.file()) | ||
143 | } | ||
144 | /// Maps range in the source code to the range in the expanded code. | ||
145 | pub fn map_range_forward(&self, src_range: TextRange) -> Option<TextRange> { | ||
146 | for (s_range, t_range) in self.ranges_map.iter() { | ||
147 | if src_range.is_subrange(&s_range) { | ||
148 | let src_at_zero_range = src_range - src_range.start(); | ||
149 | let src_range_offset = src_range.start() - s_range.start(); | ||
150 | let src_range = src_at_zero_range + src_range_offset + t_range.start(); | ||
151 | return Some(src_range); | ||
152 | } | ||
153 | } | ||
154 | None | ||
155 | } | ||
156 | /// Maps range in the expanded code to the range in the source code. | ||
157 | pub fn map_range_back(&self, tgt_range: TextRange) -> Option<TextRange> { | ||
158 | for (s_range, t_range) in self.ranges_map.iter() { | ||
159 | if tgt_range.is_subrange(&t_range) { | ||
160 | let tgt_at_zero_range = tgt_range - tgt_range.start(); | ||
161 | let tgt_range_offset = tgt_range.start() - t_range.start(); | ||
162 | let src_range = tgt_at_zero_range + tgt_range_offset + s_range.start(); | ||
163 | return Some(src_range); | ||
164 | } | ||
165 | } | ||
166 | None | ||
167 | } | ||
168 | } | ||
169 | |||
170 | pub(crate) fn expand_macro_invocation( | ||
171 | db: &impl HirDatabase, | ||
172 | invoc: MacroCallId, | ||
173 | ) -> Option<Arc<MacroExpansion>> { | ||
174 | let loc = invoc.loc(db); | ||
175 | let syntax = db.file_item(loc.source_item_id); | ||
176 | let syntax = syntax.borrowed(); | ||
177 | let macro_call = ast::MacroCall::cast(syntax).unwrap(); | ||
178 | |||
179 | let (def, input) = MacroDef::from_call(macro_call)?; | ||
180 | def.expand(input).map(Arc::new) | ||
181 | } | ||