diff options
Diffstat (limited to 'crates/ra_hir_expand/src')
-rw-r--r-- | crates/ra_hir_expand/src/builtin_macro.rs | 285 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/db.rs | 7 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/lib.rs | 8 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/name.rs | 4 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/test_db.rs | 50 |
5 files changed, 322 insertions, 32 deletions
diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs index 97fb0cb55..d370dfb34 100644 --- a/crates/ra_hir_expand/src/builtin_macro.rs +++ b/crates/ra_hir_expand/src/builtin_macro.rs | |||
@@ -8,35 +8,48 @@ use crate::{ | |||
8 | 8 | ||
9 | use crate::quote; | 9 | use crate::quote; |
10 | 10 | ||
11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | 11 | macro_rules! register_builtin { |
12 | pub enum BuiltinExpander { | 12 | ( $(($name:ident, $kind: ident) => $expand:ident),* ) => { |
13 | Line, | 13 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
14 | } | 14 | pub enum BuiltinFnLikeExpander { |
15 | $($kind),* | ||
16 | } | ||
15 | 17 | ||
16 | impl BuiltinExpander { | 18 | impl BuiltinFnLikeExpander { |
17 | pub fn expand( | 19 | pub fn expand( |
18 | &self, | 20 | &self, |
19 | db: &dyn AstDatabase, | 21 | db: &dyn AstDatabase, |
20 | id: MacroCallId, | 22 | id: MacroCallId, |
21 | tt: &tt::Subtree, | 23 | tt: &tt::Subtree, |
22 | ) -> Result<tt::Subtree, mbe::ExpandError> { | 24 | ) -> Result<tt::Subtree, mbe::ExpandError> { |
23 | match self { | 25 | let expander = match *self { |
24 | BuiltinExpander::Line => line_expand(db, id, tt), | 26 | $( BuiltinFnLikeExpander::$kind => $expand, )* |
27 | }; | ||
28 | expander(db, id, tt) | ||
29 | } | ||
25 | } | 30 | } |
26 | } | 31 | |
32 | pub fn find_builtin_macro( | ||
33 | ident: &name::Name, | ||
34 | krate: CrateId, | ||
35 | ast_id: AstId<ast::MacroCall>, | ||
36 | ) -> Option<MacroDefId> { | ||
37 | let kind = match ident { | ||
38 | $( id if id == &name::$name => BuiltinFnLikeExpander::$kind, )* | ||
39 | _ => return None, | ||
40 | }; | ||
41 | |||
42 | Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(kind) }) | ||
43 | } | ||
44 | }; | ||
27 | } | 45 | } |
28 | 46 | ||
29 | pub fn find_builtin_macro( | 47 | register_builtin! { |
30 | ident: &name::Name, | 48 | (COLUMN_MACRO, Column) => column_expand, |
31 | krate: CrateId, | 49 | (COMPILE_ERROR_MACRO, CompileError) => compile_error_expand, |
32 | ast_id: AstId<ast::MacroCall>, | 50 | (FILE_MACRO, File) => file_expand, |
33 | ) -> Option<MacroDefId> { | 51 | (LINE_MACRO, Line) => line_expand, |
34 | // FIXME: Better registering method | 52 | (STRINGIFY_MACRO, Stringify) => stringify_expand |
35 | if ident == &name::LINE_MACRO { | ||
36 | Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(BuiltinExpander::Line) }) | ||
37 | } else { | ||
38 | None | ||
39 | } | ||
40 | } | 53 | } |
41 | 54 | ||
42 | fn to_line_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize { | 55 | fn to_line_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize { |
@@ -45,16 +58,21 @@ fn to_line_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize | |||
45 | let text = db.file_text(file_id); | 58 | let text = db.file_text(file_id); |
46 | let mut line_num = 1; | 59 | let mut line_num = 1; |
47 | 60 | ||
61 | let pos = pos.to_usize(); | ||
62 | if pos > text.len() { | ||
63 | // FIXME: `pos` at the moment could be an offset inside the "wrong" file | ||
64 | // in this case, when we know it's wrong, we return a dummy value | ||
65 | return 0; | ||
66 | } | ||
48 | // Count line end | 67 | // Count line end |
49 | for (i, c) in text.chars().enumerate() { | 68 | for (i, c) in text.chars().enumerate() { |
50 | if i == pos.to_usize() { | 69 | if i == pos { |
51 | break; | 70 | break; |
52 | } | 71 | } |
53 | if c == '\n' { | 72 | if c == '\n' { |
54 | line_num += 1; | 73 | line_num += 1; |
55 | } | 74 | } |
56 | } | 75 | } |
57 | |||
58 | line_num | 76 | line_num |
59 | } | 77 | } |
60 | 78 | ||
@@ -78,3 +96,216 @@ fn line_expand( | |||
78 | 96 | ||
79 | Ok(expanded) | 97 | Ok(expanded) |
80 | } | 98 | } |
99 | |||
100 | fn stringify_expand( | ||
101 | db: &dyn AstDatabase, | ||
102 | id: MacroCallId, | ||
103 | _tt: &tt::Subtree, | ||
104 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
105 | let loc = db.lookup_intern_macro(id); | ||
106 | let macro_call = loc.ast_id.to_node(db); | ||
107 | |||
108 | let macro_content = { | ||
109 | let arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; | ||
110 | let macro_args = arg.syntax().clone(); | ||
111 | let text = macro_args.text(); | ||
112 | let without_parens = TextUnit::of_char('(')..text.len() - TextUnit::of_char(')'); | ||
113 | text.slice(without_parens).to_string() | ||
114 | }; | ||
115 | |||
116 | let expanded = quote! { | ||
117 | #macro_content | ||
118 | }; | ||
119 | |||
120 | Ok(expanded) | ||
121 | } | ||
122 | |||
123 | fn to_col_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize { | ||
124 | // FIXME: Use expansion info | ||
125 | let file_id = file.original_file(db); | ||
126 | let text = db.file_text(file_id); | ||
127 | |||
128 | let pos = pos.to_usize(); | ||
129 | if pos > text.len() { | ||
130 | // FIXME: `pos` at the moment could be an offset inside the "wrong" file | ||
131 | // in this case we return a dummy value so that we don't `panic!` | ||
132 | return 0; | ||
133 | } | ||
134 | |||
135 | let mut col_num = 1; | ||
136 | for c in text[..pos].chars().rev() { | ||
137 | if c == '\n' { | ||
138 | break; | ||
139 | } | ||
140 | col_num = col_num + 1; | ||
141 | } | ||
142 | col_num | ||
143 | } | ||
144 | |||
145 | fn column_expand( | ||
146 | db: &dyn AstDatabase, | ||
147 | id: MacroCallId, | ||
148 | _tt: &tt::Subtree, | ||
149 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
150 | let loc = db.lookup_intern_macro(id); | ||
151 | let macro_call = loc.ast_id.to_node(db); | ||
152 | |||
153 | let _arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; | ||
154 | let col_start = macro_call.syntax().text_range().start(); | ||
155 | |||
156 | let file = id.as_file(MacroFileKind::Expr); | ||
157 | let col_num = to_col_number(db, file, col_start); | ||
158 | |||
159 | let expanded = quote! { | ||
160 | #col_num | ||
161 | }; | ||
162 | |||
163 | Ok(expanded) | ||
164 | } | ||
165 | |||
166 | fn file_expand( | ||
167 | db: &dyn AstDatabase, | ||
168 | id: MacroCallId, | ||
169 | _tt: &tt::Subtree, | ||
170 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
171 | let loc = db.lookup_intern_macro(id); | ||
172 | let macro_call = loc.ast_id.to_node(db); | ||
173 | |||
174 | let _ = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; | ||
175 | |||
176 | // FIXME: RA purposefully lacks knowledge of absolute file names | ||
177 | // so just return "". | ||
178 | let file_name = ""; | ||
179 | |||
180 | let expanded = quote! { | ||
181 | #file_name | ||
182 | }; | ||
183 | |||
184 | Ok(expanded) | ||
185 | } | ||
186 | |||
187 | fn compile_error_expand( | ||
188 | _db: &dyn AstDatabase, | ||
189 | _id: MacroCallId, | ||
190 | tt: &tt::Subtree, | ||
191 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
192 | if tt.count() == 1 { | ||
193 | match &tt.token_trees[0] { | ||
194 | tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => { | ||
195 | let s = it.text.as_str(); | ||
196 | if s.contains(r#"""#) { | ||
197 | return Ok(quote! { loop { #it }}); | ||
198 | } | ||
199 | } | ||
200 | _ => {} | ||
201 | }; | ||
202 | } | ||
203 | |||
204 | Err(mbe::ExpandError::BindingError("Must be a string".into())) | ||
205 | } | ||
206 | |||
207 | #[cfg(test)] | ||
208 | mod tests { | ||
209 | use super::*; | ||
210 | use crate::{test_db::TestDB, MacroCallLoc}; | ||
211 | use ra_db::{fixture::WithFixture, SourceDatabase}; | ||
212 | |||
213 | fn expand_builtin_macro(s: &str, expander: BuiltinFnLikeExpander) -> String { | ||
214 | let (db, file_id) = TestDB::with_single_file(&s); | ||
215 | let parsed = db.parse(file_id); | ||
216 | let macro_calls: Vec<_> = | ||
217 | parsed.syntax_node().descendants().filter_map(|it| ast::MacroCall::cast(it)).collect(); | ||
218 | |||
219 | let ast_id_map = db.ast_id_map(file_id.into()); | ||
220 | |||
221 | // the first one should be a macro_rules | ||
222 | let def = MacroDefId { | ||
223 | krate: CrateId(0), | ||
224 | ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0])), | ||
225 | kind: MacroDefKind::BuiltIn(expander), | ||
226 | }; | ||
227 | |||
228 | let loc = MacroCallLoc { | ||
229 | def, | ||
230 | ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[1])), | ||
231 | }; | ||
232 | |||
233 | let id = db.intern_macro(loc); | ||
234 | let parsed = db.parse_or_expand(id.as_file(MacroFileKind::Expr)).unwrap(); | ||
235 | |||
236 | parsed.text().to_string() | ||
237 | } | ||
238 | |||
239 | #[test] | ||
240 | fn test_column_expand() { | ||
241 | let expanded = expand_builtin_macro( | ||
242 | r#" | ||
243 | #[rustc_builtin_macro] | ||
244 | macro_rules! column {() => {}} | ||
245 | column!() | ||
246 | "#, | ||
247 | BuiltinFnLikeExpander::Column, | ||
248 | ); | ||
249 | |||
250 | assert_eq!(expanded, "9"); | ||
251 | } | ||
252 | |||
253 | #[test] | ||
254 | fn test_line_expand() { | ||
255 | let expanded = expand_builtin_macro( | ||
256 | r#" | ||
257 | #[rustc_builtin_macro] | ||
258 | macro_rules! line {() => {}} | ||
259 | line!() | ||
260 | "#, | ||
261 | BuiltinFnLikeExpander::Line, | ||
262 | ); | ||
263 | |||
264 | assert_eq!(expanded, "4"); | ||
265 | } | ||
266 | |||
267 | #[test] | ||
268 | fn test_stringify_expand() { | ||
269 | let expanded = expand_builtin_macro( | ||
270 | r#" | ||
271 | #[rustc_builtin_macro] | ||
272 | macro_rules! stringify {() => {}} | ||
273 | stringify!(a b c) | ||
274 | "#, | ||
275 | BuiltinFnLikeExpander::Stringify, | ||
276 | ); | ||
277 | |||
278 | assert_eq!(expanded, "\"a b c\""); | ||
279 | } | ||
280 | |||
281 | #[test] | ||
282 | fn test_file_expand() { | ||
283 | let expanded = expand_builtin_macro( | ||
284 | r#" | ||
285 | #[rustc_builtin_macro] | ||
286 | macro_rules! file {() => {}} | ||
287 | file!() | ||
288 | "#, | ||
289 | BuiltinFnLikeExpander::File, | ||
290 | ); | ||
291 | |||
292 | assert_eq!(expanded, "\"\""); | ||
293 | } | ||
294 | |||
295 | #[test] | ||
296 | fn test_compile_error_expand() { | ||
297 | let expanded = expand_builtin_macro( | ||
298 | r#" | ||
299 | #[rustc_builtin_macro] | ||
300 | macro_rules! compile_error { | ||
301 | ($msg:expr) => ({ /* compiler built-in */ }); | ||
302 | ($msg:expr,) => ({ /* compiler built-in */ }) | ||
303 | } | ||
304 | compile_error!("error!"); | ||
305 | "#, | ||
306 | BuiltinFnLikeExpander::CompileError, | ||
307 | ); | ||
308 | |||
309 | assert_eq!(expanded, r#"loop{"error!"}"#); | ||
310 | } | ||
311 | } | ||
diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs index 3c11c8a22..8e46fa177 100644 --- a/crates/ra_hir_expand/src/db.rs +++ b/crates/ra_hir_expand/src/db.rs | |||
@@ -9,14 +9,14 @@ use ra_prof::profile; | |||
9 | use ra_syntax::{AstNode, Parse, SyntaxNode}; | 9 | use ra_syntax::{AstNode, Parse, SyntaxNode}; |
10 | 10 | ||
11 | use crate::{ | 11 | use crate::{ |
12 | ast_id_map::AstIdMap, BuiltinExpander, HirFileId, HirFileIdRepr, MacroCallId, MacroCallLoc, | 12 | ast_id_map::AstIdMap, BuiltinFnLikeExpander, HirFileId, HirFileIdRepr, MacroCallId, |
13 | MacroDefId, MacroDefKind, MacroFile, MacroFileKind, | 13 | MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, MacroFileKind, |
14 | }; | 14 | }; |
15 | 15 | ||
16 | #[derive(Debug, Clone, Eq, PartialEq)] | 16 | #[derive(Debug, Clone, Eq, PartialEq)] |
17 | pub enum TokenExpander { | 17 | pub enum TokenExpander { |
18 | MacroRules(mbe::MacroRules), | 18 | MacroRules(mbe::MacroRules), |
19 | Builtin(BuiltinExpander), | 19 | Builtin(BuiltinFnLikeExpander), |
20 | } | 20 | } |
21 | 21 | ||
22 | impl TokenExpander { | 22 | impl TokenExpander { |
@@ -151,6 +151,7 @@ pub(crate) fn parse_macro( | |||
151 | let fragment_kind = match macro_file.macro_file_kind { | 151 | let fragment_kind = match macro_file.macro_file_kind { |
152 | MacroFileKind::Items => FragmentKind::Items, | 152 | MacroFileKind::Items => FragmentKind::Items, |
153 | MacroFileKind::Expr => FragmentKind::Expr, | 153 | MacroFileKind::Expr => FragmentKind::Expr, |
154 | MacroFileKind::Statements => FragmentKind::Statements, | ||
154 | }; | 155 | }; |
155 | let (parse, rev_token_map) = mbe::token_tree_to_syntax_node(&tt, fragment_kind).ok()?; | 156 | let (parse, rev_token_map) = mbe::token_tree_to_syntax_node(&tt, fragment_kind).ok()?; |
156 | Some((parse, Arc::new(rev_token_map))) | 157 | Some((parse, Arc::new(rev_token_map))) |
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs index 1389f64ce..4f3ccf1d0 100644 --- a/crates/ra_hir_expand/src/lib.rs +++ b/crates/ra_hir_expand/src/lib.rs | |||
@@ -24,7 +24,10 @@ use ra_syntax::{ | |||
24 | }; | 24 | }; |
25 | 25 | ||
26 | use crate::ast_id_map::FileAstId; | 26 | use crate::ast_id_map::FileAstId; |
27 | use crate::builtin_macro::BuiltinExpander; | 27 | use crate::builtin_macro::BuiltinFnLikeExpander; |
28 | |||
29 | #[cfg(test)] | ||
30 | mod test_db; | ||
28 | 31 | ||
29 | /// Input to the analyzer is a set of files, where each file is identified by | 32 | /// 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 | 33 | /// `FileId` and contains source code. However, another source of source code in |
@@ -109,6 +112,7 @@ pub struct MacroFile { | |||
109 | pub enum MacroFileKind { | 112 | pub enum MacroFileKind { |
110 | Items, | 113 | Items, |
111 | Expr, | 114 | Expr, |
115 | Statements, | ||
112 | } | 116 | } |
113 | 117 | ||
114 | /// `MacroCallId` identifies a particular macro invocation, like | 118 | /// `MacroCallId` identifies a particular macro invocation, like |
@@ -134,7 +138,7 @@ pub struct MacroDefId { | |||
134 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | 138 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
135 | pub enum MacroDefKind { | 139 | pub enum MacroDefKind { |
136 | Declarative, | 140 | Declarative, |
137 | BuiltIn(BuiltinExpander), | 141 | BuiltIn(BuiltinFnLikeExpander), |
138 | } | 142 | } |
139 | 143 | ||
140 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | 144 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs index 1bf17d12b..7824489d7 100644 --- a/crates/ra_hir_expand/src/name.rs +++ b/crates/ra_hir_expand/src/name.rs | |||
@@ -142,4 +142,8 @@ pub const TARGET_TYPE: Name = Name::new_inline_ascii(6, b"Target"); | |||
142 | pub const BOX_TYPE: Name = Name::new_inline_ascii(3, b"Box"); | 142 | pub const BOX_TYPE: Name = Name::new_inline_ascii(3, b"Box"); |
143 | 143 | ||
144 | // Builtin Macros | 144 | // Builtin Macros |
145 | pub const FILE_MACRO: Name = Name::new_inline_ascii(4, b"file"); | ||
146 | pub const COLUMN_MACRO: Name = Name::new_inline_ascii(6, b"column"); | ||
147 | pub const COMPILE_ERROR_MACRO: Name = Name::new_inline_ascii(13, b"compile_error"); | ||
145 | pub const LINE_MACRO: Name = Name::new_inline_ascii(4, b"line"); | 148 | pub const LINE_MACRO: Name = Name::new_inline_ascii(4, b"line"); |
149 | pub const STRINGIFY_MACRO: Name = Name::new_inline_ascii(9, b"stringify"); | ||
diff --git a/crates/ra_hir_expand/src/test_db.rs b/crates/ra_hir_expand/src/test_db.rs new file mode 100644 index 000000000..d23e75d9e --- /dev/null +++ b/crates/ra_hir_expand/src/test_db.rs | |||
@@ -0,0 +1,50 @@ | |||
1 | //! Database used for testing `hir_expand`. | ||
2 | |||
3 | use std::{ | ||
4 | panic, | ||
5 | sync::{Arc, Mutex}, | ||
6 | }; | ||
7 | |||
8 | use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath}; | ||
9 | |||
10 | #[salsa::database( | ||
11 | ra_db::SourceDatabaseExtStorage, | ||
12 | ra_db::SourceDatabaseStorage, | ||
13 | crate::db::AstDatabaseStorage | ||
14 | )] | ||
15 | #[derive(Debug, Default)] | ||
16 | pub struct TestDB { | ||
17 | runtime: salsa::Runtime<TestDB>, | ||
18 | events: Mutex<Option<Vec<salsa::Event<TestDB>>>>, | ||
19 | } | ||
20 | |||
21 | impl salsa::Database for TestDB { | ||
22 | fn salsa_runtime(&self) -> &salsa::Runtime<Self> { | ||
23 | &self.runtime | ||
24 | } | ||
25 | |||
26 | fn salsa_event(&self, event: impl Fn() -> salsa::Event<TestDB>) { | ||
27 | let mut events = self.events.lock().unwrap(); | ||
28 | if let Some(events) = &mut *events { | ||
29 | events.push(event()); | ||
30 | } | ||
31 | } | ||
32 | } | ||
33 | |||
34 | impl panic::RefUnwindSafe for TestDB {} | ||
35 | |||
36 | impl FileLoader for TestDB { | ||
37 | fn file_text(&self, file_id: FileId) -> Arc<String> { | ||
38 | FileLoaderDelegate(self).file_text(file_id) | ||
39 | } | ||
40 | fn resolve_relative_path( | ||
41 | &self, | ||
42 | anchor: FileId, | ||
43 | relative_path: &RelativePath, | ||
44 | ) -> Option<FileId> { | ||
45 | FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path) | ||
46 | } | ||
47 | fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> { | ||
48 | FileLoaderDelegate(self).relevant_crates(file_id) | ||
49 | } | ||
50 | } | ||