aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir/src/macros.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir/src/macros.rs')
-rw-r--r--crates/ra_hir/src/macros.rs181
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!
10use std::sync::Arc;
11
12use ra_db::LocalSyntaxPtr;
13use ra_syntax::{
14 TextRange, TextUnit, SourceFileNode, AstNode, SyntaxNode,
15 ast::{self, NameOwner},
16};
17
18use crate::{HirDatabase, MacroCallId};
19
20// Hard-coded defs for now :-(
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
22pub enum MacroDef {
23 CTry,
24 QueryGroup,
25}
26
27impl 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)]
116pub struct MacroInput {
117 // Should be token trees
118 pub text: String,
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
122pub 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
134impl 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
170pub(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}