diff options
Diffstat (limited to 'crates/ra_hir_expand/src/builtin_macro.rs')
-rw-r--r-- | crates/ra_hir_expand/src/builtin_macro.rs | 244 |
1 files changed, 180 insertions, 64 deletions
diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs index d370dfb34..2c119269c 100644 --- a/crates/ra_hir_expand/src/builtin_macro.rs +++ b/crates/ra_hir_expand/src/builtin_macro.rs | |||
@@ -2,8 +2,7 @@ | |||
2 | use crate::db::AstDatabase; | 2 | use crate::db::AstDatabase; |
3 | use crate::{ | 3 | use crate::{ |
4 | ast::{self, AstNode}, | 4 | ast::{self, AstNode}, |
5 | name, AstId, CrateId, HirFileId, MacroCallId, MacroDefId, MacroDefKind, MacroFileKind, | 5 | name, AstId, CrateId, HirFileId, MacroCallId, MacroDefId, MacroDefKind, TextUnit, |
6 | TextUnit, | ||
7 | }; | 6 | }; |
8 | 7 | ||
9 | use crate::quote; | 8 | use crate::quote; |
@@ -27,6 +26,13 @@ macro_rules! register_builtin { | |||
27 | }; | 26 | }; |
28 | expander(db, id, tt) | 27 | expander(db, id, tt) |
29 | } | 28 | } |
29 | |||
30 | fn by_name(ident: &name::Name) -> Option<BuiltinFnLikeExpander> { | ||
31 | match ident { | ||
32 | $( id if id == &name::name![$name] => Some(BuiltinFnLikeExpander::$kind), )* | ||
33 | _ => return None, | ||
34 | } | ||
35 | } | ||
30 | } | 36 | } |
31 | 37 | ||
32 | pub fn find_builtin_macro( | 38 | pub fn find_builtin_macro( |
@@ -34,22 +40,25 @@ macro_rules! register_builtin { | |||
34 | krate: CrateId, | 40 | krate: CrateId, |
35 | ast_id: AstId<ast::MacroCall>, | 41 | ast_id: AstId<ast::MacroCall>, |
36 | ) -> Option<MacroDefId> { | 42 | ) -> Option<MacroDefId> { |
37 | let kind = match ident { | 43 | let kind = BuiltinFnLikeExpander::by_name(ident)?; |
38 | $( id if id == &name::$name => BuiltinFnLikeExpander::$kind, )* | ||
39 | _ => return None, | ||
40 | }; | ||
41 | 44 | ||
42 | Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(kind) }) | 45 | Some(MacroDefId { krate: Some(krate), ast_id: Some(ast_id), kind: MacroDefKind::BuiltIn(kind) }) |
43 | } | 46 | } |
44 | }; | 47 | }; |
45 | } | 48 | } |
46 | 49 | ||
47 | register_builtin! { | 50 | register_builtin! { |
48 | (COLUMN_MACRO, Column) => column_expand, | 51 | (column, Column) => column_expand, |
49 | (COMPILE_ERROR_MACRO, CompileError) => compile_error_expand, | 52 | (compile_error, CompileError) => compile_error_expand, |
50 | (FILE_MACRO, File) => file_expand, | 53 | (file, File) => file_expand, |
51 | (LINE_MACRO, Line) => line_expand, | 54 | (line, Line) => line_expand, |
52 | (STRINGIFY_MACRO, Stringify) => stringify_expand | 55 | (stringify, Stringify) => stringify_expand, |
56 | (format_args, FormatArgs) => format_args_expand, | ||
57 | (env, Env) => env_expand, | ||
58 | (option_env, OptionEnv) => option_env_expand, | ||
59 | // format_args_nl only differs in that it adds a newline in the end, | ||
60 | // so we use the same stub expansion for now | ||
61 | (format_args_nl, FormatArgsNl) => format_args_expand | ||
53 | } | 62 | } |
54 | 63 | ||
55 | fn to_line_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize { | 64 | fn to_line_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize { |
@@ -82,12 +91,11 @@ fn line_expand( | |||
82 | _tt: &tt::Subtree, | 91 | _tt: &tt::Subtree, |
83 | ) -> Result<tt::Subtree, mbe::ExpandError> { | 92 | ) -> Result<tt::Subtree, mbe::ExpandError> { |
84 | let loc = db.lookup_intern_macro(id); | 93 | let loc = db.lookup_intern_macro(id); |
85 | let macro_call = loc.ast_id.to_node(db); | ||
86 | 94 | ||
87 | let arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; | 95 | let arg = loc.kind.arg(db).ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; |
88 | let arg_start = arg.syntax().text_range().start(); | 96 | let arg_start = arg.text_range().start(); |
89 | 97 | ||
90 | let file = id.as_file(MacroFileKind::Expr); | 98 | let file = id.as_file(); |
91 | let line_num = to_line_number(db, file, arg_start); | 99 | let line_num = to_line_number(db, file, arg_start); |
92 | 100 | ||
93 | let expanded = quote! { | 101 | let expanded = quote! { |
@@ -103,11 +111,10 @@ fn stringify_expand( | |||
103 | _tt: &tt::Subtree, | 111 | _tt: &tt::Subtree, |
104 | ) -> Result<tt::Subtree, mbe::ExpandError> { | 112 | ) -> Result<tt::Subtree, mbe::ExpandError> { |
105 | let loc = db.lookup_intern_macro(id); | 113 | let loc = db.lookup_intern_macro(id); |
106 | let macro_call = loc.ast_id.to_node(db); | ||
107 | 114 | ||
108 | let macro_content = { | 115 | let macro_content = { |
109 | let arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; | 116 | let arg = loc.kind.arg(db).ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; |
110 | let macro_args = arg.syntax().clone(); | 117 | let macro_args = arg; |
111 | let text = macro_args.text(); | 118 | let text = macro_args.text(); |
112 | let without_parens = TextUnit::of_char('(')..text.len() - TextUnit::of_char(')'); | 119 | let without_parens = TextUnit::of_char('(')..text.len() - TextUnit::of_char(')'); |
113 | text.slice(without_parens).to_string() | 120 | text.slice(without_parens).to_string() |
@@ -120,6 +127,28 @@ fn stringify_expand( | |||
120 | Ok(expanded) | 127 | Ok(expanded) |
121 | } | 128 | } |
122 | 129 | ||
130 | fn env_expand( | ||
131 | _db: &dyn AstDatabase, | ||
132 | _id: MacroCallId, | ||
133 | _tt: &tt::Subtree, | ||
134 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
135 | // dummy implementation for type-checking purposes | ||
136 | let expanded = quote! { "" }; | ||
137 | |||
138 | Ok(expanded) | ||
139 | } | ||
140 | |||
141 | fn option_env_expand( | ||
142 | _db: &dyn AstDatabase, | ||
143 | _id: MacroCallId, | ||
144 | _tt: &tt::Subtree, | ||
145 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
146 | // dummy implementation for type-checking purposes | ||
147 | let expanded = quote! { std::option::Option::None::<&str> }; | ||
148 | |||
149 | Ok(expanded) | ||
150 | } | ||
151 | |||
123 | fn to_col_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize { | 152 | fn to_col_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize { |
124 | // FIXME: Use expansion info | 153 | // FIXME: Use expansion info |
125 | let file_id = file.original_file(db); | 154 | let file_id = file.original_file(db); |
@@ -137,7 +166,7 @@ fn to_col_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize | |||
137 | if c == '\n' { | 166 | if c == '\n' { |
138 | break; | 167 | break; |
139 | } | 168 | } |
140 | col_num = col_num + 1; | 169 | col_num += 1; |
141 | } | 170 | } |
142 | col_num | 171 | col_num |
143 | } | 172 | } |
@@ -148,12 +177,15 @@ fn column_expand( | |||
148 | _tt: &tt::Subtree, | 177 | _tt: &tt::Subtree, |
149 | ) -> Result<tt::Subtree, mbe::ExpandError> { | 178 | ) -> Result<tt::Subtree, mbe::ExpandError> { |
150 | let loc = db.lookup_intern_macro(id); | 179 | let loc = db.lookup_intern_macro(id); |
151 | let macro_call = loc.ast_id.to_node(db); | 180 | let macro_call = match loc.kind { |
181 | crate::MacroCallKind::FnLike(ast_id) => ast_id.to_node(db), | ||
182 | _ => panic!("column macro called as attr"), | ||
183 | }; | ||
152 | 184 | ||
153 | let _arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; | 185 | let _arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; |
154 | let col_start = macro_call.syntax().text_range().start(); | 186 | let col_start = macro_call.syntax().text_range().start(); |
155 | 187 | ||
156 | let file = id.as_file(MacroFileKind::Expr); | 188 | let file = id.as_file(); |
157 | let col_num = to_col_number(db, file, col_start); | 189 | let col_num = to_col_number(db, file, col_start); |
158 | 190 | ||
159 | let expanded = quote! { | 191 | let expanded = quote! { |
@@ -164,15 +196,10 @@ fn column_expand( | |||
164 | } | 196 | } |
165 | 197 | ||
166 | fn file_expand( | 198 | fn file_expand( |
167 | db: &dyn AstDatabase, | 199 | _db: &dyn AstDatabase, |
168 | id: MacroCallId, | 200 | _id: MacroCallId, |
169 | _tt: &tt::Subtree, | 201 | _tt: &tt::Subtree, |
170 | ) -> Result<tt::Subtree, mbe::ExpandError> { | 202 | ) -> 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 | 203 | // FIXME: RA purposefully lacks knowledge of absolute file names |
177 | // so just return "". | 204 | // so just return "". |
178 | let file_name = ""; | 205 | let file_name = ""; |
@@ -204,13 +231,56 @@ fn compile_error_expand( | |||
204 | Err(mbe::ExpandError::BindingError("Must be a string".into())) | 231 | Err(mbe::ExpandError::BindingError("Must be a string".into())) |
205 | } | 232 | } |
206 | 233 | ||
234 | fn format_args_expand( | ||
235 | _db: &dyn AstDatabase, | ||
236 | _id: MacroCallId, | ||
237 | tt: &tt::Subtree, | ||
238 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
239 | // We expand `format_args!("", a1, a2)` to | ||
240 | // ``` | ||
241 | // std::fmt::Arguments::new_v1(&[], &[ | ||
242 | // std::fmt::ArgumentV1::new(&arg1,std::fmt::Display::fmt), | ||
243 | // std::fmt::ArgumentV1::new(&arg2,std::fmt::Display::fmt), | ||
244 | // ]) | ||
245 | // ```, | ||
246 | // which is still not really correct, but close enough for now | ||
247 | let mut args = Vec::new(); | ||
248 | let mut current = Vec::new(); | ||
249 | for tt in tt.token_trees.iter().cloned() { | ||
250 | match tt { | ||
251 | tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => { | ||
252 | args.push(current); | ||
253 | current = Vec::new(); | ||
254 | } | ||
255 | _ => { | ||
256 | current.push(tt); | ||
257 | } | ||
258 | } | ||
259 | } | ||
260 | if !current.is_empty() { | ||
261 | args.push(current); | ||
262 | } | ||
263 | if args.is_empty() { | ||
264 | return Err(mbe::ExpandError::NoMatchingRule); | ||
265 | } | ||
266 | let _format_string = args.remove(0); | ||
267 | let arg_tts = args.into_iter().flat_map(|arg| { | ||
268 | quote! { std::fmt::ArgumentV1::new(&(##arg), std::fmt::Display::fmt), } | ||
269 | }.token_trees).collect::<Vec<_>>(); | ||
270 | let expanded = quote! { | ||
271 | std::fmt::Arguments::new_v1(&[], &[##arg_tts]) | ||
272 | }; | ||
273 | Ok(expanded) | ||
274 | } | ||
275 | |||
207 | #[cfg(test)] | 276 | #[cfg(test)] |
208 | mod tests { | 277 | mod tests { |
209 | use super::*; | 278 | use super::*; |
210 | use crate::{test_db::TestDB, MacroCallLoc}; | 279 | use crate::{name::AsName, test_db::TestDB, MacroCallKind, MacroCallLoc}; |
211 | use ra_db::{fixture::WithFixture, SourceDatabase}; | 280 | use ra_db::{fixture::WithFixture, SourceDatabase}; |
281 | use ra_syntax::ast::NameOwner; | ||
212 | 282 | ||
213 | fn expand_builtin_macro(s: &str, expander: BuiltinFnLikeExpander) -> String { | 283 | fn expand_builtin_macro(s: &str) -> String { |
214 | let (db, file_id) = TestDB::with_single_file(&s); | 284 | let (db, file_id) = TestDB::with_single_file(&s); |
215 | let parsed = db.parse(file_id); | 285 | let parsed = db.parse(file_id); |
216 | let macro_calls: Vec<_> = | 286 | let macro_calls: Vec<_> = |
@@ -218,20 +288,26 @@ mod tests { | |||
218 | 288 | ||
219 | let ast_id_map = db.ast_id_map(file_id.into()); | 289 | let ast_id_map = db.ast_id_map(file_id.into()); |
220 | 290 | ||
291 | let expander = | ||
292 | BuiltinFnLikeExpander::by_name(¯o_calls[0].name().unwrap().as_name()).unwrap(); | ||
293 | |||
221 | // the first one should be a macro_rules | 294 | // the first one should be a macro_rules |
222 | let def = MacroDefId { | 295 | let def = MacroDefId { |
223 | krate: CrateId(0), | 296 | krate: Some(CrateId(0)), |
224 | ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0])), | 297 | ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), |
225 | kind: MacroDefKind::BuiltIn(expander), | 298 | kind: MacroDefKind::BuiltIn(expander), |
226 | }; | 299 | }; |
227 | 300 | ||
228 | let loc = MacroCallLoc { | 301 | let loc = MacroCallLoc { |
229 | def, | 302 | def, |
230 | ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[1])), | 303 | kind: MacroCallKind::FnLike(AstId::new( |
304 | file_id.into(), | ||
305 | ast_id_map.ast_id(¯o_calls[1]), | ||
306 | )), | ||
231 | }; | 307 | }; |
232 | 308 | ||
233 | let id = db.intern_macro(loc); | 309 | let id = db.intern_macro(loc); |
234 | let parsed = db.parse_or_expand(id.as_file(MacroFileKind::Expr)).unwrap(); | 310 | let parsed = db.parse_or_expand(id.as_file()).unwrap(); |
235 | 311 | ||
236 | parsed.text().to_string() | 312 | parsed.text().to_string() |
237 | } | 313 | } |
@@ -240,25 +316,23 @@ mod tests { | |||
240 | fn test_column_expand() { | 316 | fn test_column_expand() { |
241 | let expanded = expand_builtin_macro( | 317 | let expanded = expand_builtin_macro( |
242 | r#" | 318 | r#" |
243 | #[rustc_builtin_macro] | 319 | #[rustc_builtin_macro] |
244 | macro_rules! column {() => {}} | 320 | macro_rules! column {() => {}} |
245 | column!() | 321 | column!() |
246 | "#, | 322 | "#, |
247 | BuiltinFnLikeExpander::Column, | ||
248 | ); | 323 | ); |
249 | 324 | ||
250 | assert_eq!(expanded, "9"); | 325 | assert_eq!(expanded, "13"); |
251 | } | 326 | } |
252 | 327 | ||
253 | #[test] | 328 | #[test] |
254 | fn test_line_expand() { | 329 | fn test_line_expand() { |
255 | let expanded = expand_builtin_macro( | 330 | let expanded = expand_builtin_macro( |
256 | r#" | 331 | r#" |
257 | #[rustc_builtin_macro] | 332 | #[rustc_builtin_macro] |
258 | macro_rules! line {() => {}} | 333 | macro_rules! line {() => {}} |
259 | line!() | 334 | line!() |
260 | "#, | 335 | "#, |
261 | BuiltinFnLikeExpander::Line, | ||
262 | ); | 336 | ); |
263 | 337 | ||
264 | assert_eq!(expanded, "4"); | 338 | assert_eq!(expanded, "4"); |
@@ -268,25 +342,49 @@ mod tests { | |||
268 | fn test_stringify_expand() { | 342 | fn test_stringify_expand() { |
269 | let expanded = expand_builtin_macro( | 343 | let expanded = expand_builtin_macro( |
270 | r#" | 344 | r#" |
271 | #[rustc_builtin_macro] | 345 | #[rustc_builtin_macro] |
272 | macro_rules! stringify {() => {}} | 346 | macro_rules! stringify {() => {}} |
273 | stringify!(a b c) | 347 | stringify!(a b c) |
274 | "#, | 348 | "#, |
275 | BuiltinFnLikeExpander::Stringify, | ||
276 | ); | 349 | ); |
277 | 350 | ||
278 | assert_eq!(expanded, "\"a b c\""); | 351 | assert_eq!(expanded, "\"a b c\""); |
279 | } | 352 | } |
280 | 353 | ||
281 | #[test] | 354 | #[test] |
355 | fn test_env_expand() { | ||
356 | let expanded = expand_builtin_macro( | ||
357 | r#" | ||
358 | #[rustc_builtin_macro] | ||
359 | macro_rules! env {() => {}} | ||
360 | env!("TEST_ENV_VAR") | ||
361 | "#, | ||
362 | ); | ||
363 | |||
364 | assert_eq!(expanded, "\"\""); | ||
365 | } | ||
366 | |||
367 | #[test] | ||
368 | fn test_option_env_expand() { | ||
369 | let expanded = expand_builtin_macro( | ||
370 | r#" | ||
371 | #[rustc_builtin_macro] | ||
372 | macro_rules! option_env {() => {}} | ||
373 | option_env!("TEST_ENV_VAR") | ||
374 | "#, | ||
375 | ); | ||
376 | |||
377 | assert_eq!(expanded, "std::option::Option::None:: <&str>"); | ||
378 | } | ||
379 | |||
380 | #[test] | ||
282 | fn test_file_expand() { | 381 | fn test_file_expand() { |
283 | let expanded = expand_builtin_macro( | 382 | let expanded = expand_builtin_macro( |
284 | r#" | 383 | r#" |
285 | #[rustc_builtin_macro] | 384 | #[rustc_builtin_macro] |
286 | macro_rules! file {() => {}} | 385 | macro_rules! file {() => {}} |
287 | file!() | 386 | file!() |
288 | "#, | 387 | "#, |
289 | BuiltinFnLikeExpander::File, | ||
290 | ); | 388 | ); |
291 | 389 | ||
292 | assert_eq!(expanded, "\"\""); | 390 | assert_eq!(expanded, "\"\""); |
@@ -296,16 +394,34 @@ mod tests { | |||
296 | fn test_compile_error_expand() { | 394 | fn test_compile_error_expand() { |
297 | let expanded = expand_builtin_macro( | 395 | let expanded = expand_builtin_macro( |
298 | r#" | 396 | r#" |
299 | #[rustc_builtin_macro] | 397 | #[rustc_builtin_macro] |
300 | macro_rules! compile_error { | 398 | macro_rules! compile_error { |
301 | ($msg:expr) => ({ /* compiler built-in */ }); | 399 | ($msg:expr) => ({ /* compiler built-in */ }); |
302 | ($msg:expr,) => ({ /* compiler built-in */ }) | 400 | ($msg:expr,) => ({ /* compiler built-in */ }) |
303 | } | 401 | } |
304 | compile_error!("error!"); | 402 | compile_error!("error!"); |
305 | "#, | 403 | "#, |
306 | BuiltinFnLikeExpander::CompileError, | ||
307 | ); | 404 | ); |
308 | 405 | ||
309 | assert_eq!(expanded, r#"loop{"error!"}"#); | 406 | assert_eq!(expanded, r#"loop{"error!"}"#); |
310 | } | 407 | } |
408 | |||
409 | #[test] | ||
410 | fn test_format_args_expand() { | ||
411 | let expanded = expand_builtin_macro( | ||
412 | r#" | ||
413 | #[rustc_builtin_macro] | ||
414 | macro_rules! format_args { | ||
415 | ($fmt:expr) => ({ /* compiler built-in */ }); | ||
416 | ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) | ||
417 | } | ||
418 | format_args!("{} {:?}", arg1(a, b, c), arg2); | ||
419 | "#, | ||
420 | ); | ||
421 | |||
422 | assert_eq!( | ||
423 | expanded, | ||
424 | r#"std::fmt::Arguments::new_v1(&[] ,&[std::fmt::ArgumentV1::new(&(arg1(a,b,c)),std::fmt::Display::fmt),std::fmt::ArgumentV1::new(&(arg2),std::fmt::Display::fmt),])"# | ||
425 | ); | ||
426 | } | ||
311 | } | 427 | } |