aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_expand/src/builtin_macro.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir_expand/src/builtin_macro.rs')
-rw-r--r--crates/ra_hir_expand/src/builtin_macro.rs244
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 @@
2use crate::db::AstDatabase; 2use crate::db::AstDatabase;
3use crate::{ 3use 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
9use crate::quote; 8use 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
47register_builtin! { 50register_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
55fn to_line_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize { 64fn 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
130fn 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
141fn 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
123fn to_col_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize { 152fn 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
166fn file_expand( 198fn 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
234fn 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)]
208mod tests { 277mod 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(&macro_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(&macro_calls[0])), 297 ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(&macro_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(&macro_calls[1])), 303 kind: MacroCallKind::FnLike(AstId::new(
304 file_id.into(),
305 ast_id_map.ast_id(&macro_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}