aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_expand/src/builtin_macro.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_expand/src/builtin_macro.rs')
-rw-r--r--crates/hir_expand/src/builtin_macro.rs649
1 files changed, 649 insertions, 0 deletions
diff --git a/crates/hir_expand/src/builtin_macro.rs b/crates/hir_expand/src/builtin_macro.rs
new file mode 100644
index 000000000..86918b626
--- /dev/null
+++ b/crates/hir_expand/src/builtin_macro.rs
@@ -0,0 +1,649 @@
1//! Builtin macro
2use crate::{
3 db::AstDatabase, name, quote, AstId, CrateId, EagerMacroId, LazyMacroId, MacroCallId,
4 MacroDefId, MacroDefKind, TextSize,
5};
6
7use base_db::FileId;
8use either::Either;
9use mbe::parse_to_token_tree;
10use parser::FragmentKind;
11use syntax::ast::{self, AstToken, HasStringValue};
12
13macro_rules! register_builtin {
14 ( LAZY: $(($name:ident, $kind: ident) => $expand:ident),* , EAGER: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => {
15 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16 pub enum BuiltinFnLikeExpander {
17 $($kind),*
18 }
19
20 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21 pub enum EagerExpander {
22 $($e_kind),*
23 }
24
25 impl BuiltinFnLikeExpander {
26 pub fn expand(
27 &self,
28 db: &dyn AstDatabase,
29 id: LazyMacroId,
30 tt: &tt::Subtree,
31 ) -> Result<tt::Subtree, mbe::ExpandError> {
32 let expander = match *self {
33 $( BuiltinFnLikeExpander::$kind => $expand, )*
34 };
35 expander(db, id, tt)
36 }
37 }
38
39 impl EagerExpander {
40 pub fn expand(
41 &self,
42 db: &dyn AstDatabase,
43 arg_id: EagerMacroId,
44 tt: &tt::Subtree,
45 ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
46 let expander = match *self {
47 $( EagerExpander::$e_kind => $e_expand, )*
48 };
49 expander(db,arg_id,tt)
50 }
51 }
52
53 fn find_by_name(ident: &name::Name) -> Option<Either<BuiltinFnLikeExpander, EagerExpander>> {
54 match ident {
55 $( id if id == &name::name![$name] => Some(Either::Left(BuiltinFnLikeExpander::$kind)), )*
56 $( id if id == &name::name![$e_name] => Some(Either::Right(EagerExpander::$e_kind)), )*
57 _ => return None,
58 }
59 }
60 };
61}
62
63pub fn find_builtin_macro(
64 ident: &name::Name,
65 krate: CrateId,
66 ast_id: AstId<ast::MacroCall>,
67) -> Option<MacroDefId> {
68 let kind = find_by_name(ident)?;
69
70 match kind {
71 Either::Left(kind) => Some(MacroDefId {
72 krate: Some(krate),
73 ast_id: Some(ast_id),
74 kind: MacroDefKind::BuiltIn(kind),
75 local_inner: false,
76 }),
77 Either::Right(kind) => Some(MacroDefId {
78 krate: Some(krate),
79 ast_id: Some(ast_id),
80 kind: MacroDefKind::BuiltInEager(kind),
81 local_inner: false,
82 }),
83 }
84}
85
86register_builtin! {
87 LAZY:
88 (column, Column) => column_expand,
89 (compile_error, CompileError) => compile_error_expand,
90 (file, File) => file_expand,
91 (line, Line) => line_expand,
92 (assert, Assert) => assert_expand,
93 (stringify, Stringify) => stringify_expand,
94 (format_args, FormatArgs) => format_args_expand,
95 // format_args_nl only differs in that it adds a newline in the end,
96 // so we use the same stub expansion for now
97 (format_args_nl, FormatArgsNl) => format_args_expand,
98
99 EAGER:
100 (concat, Concat) => concat_expand,
101 (include, Include) => include_expand,
102 (include_bytes, IncludeBytes) => include_bytes_expand,
103 (include_str, IncludeStr) => include_str_expand,
104 (env, Env) => env_expand,
105 (option_env, OptionEnv) => option_env_expand
106}
107
108fn line_expand(
109 _db: &dyn AstDatabase,
110 _id: LazyMacroId,
111 _tt: &tt::Subtree,
112) -> Result<tt::Subtree, mbe::ExpandError> {
113 // dummy implementation for type-checking purposes
114 let line_num = 0;
115 let expanded = quote! {
116 #line_num
117 };
118
119 Ok(expanded)
120}
121
122fn stringify_expand(
123 db: &dyn AstDatabase,
124 id: LazyMacroId,
125 _tt: &tt::Subtree,
126) -> Result<tt::Subtree, mbe::ExpandError> {
127 let loc = db.lookup_intern_macro(id);
128
129 let macro_content = {
130 let arg = loc.kind.arg(db).ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;
131 let macro_args = arg;
132 let text = macro_args.text();
133 let without_parens = TextSize::of('(')..text.len() - TextSize::of(')');
134 text.slice(without_parens).to_string()
135 };
136
137 let expanded = quote! {
138 #macro_content
139 };
140
141 Ok(expanded)
142}
143
144fn column_expand(
145 _db: &dyn AstDatabase,
146 _id: LazyMacroId,
147 _tt: &tt::Subtree,
148) -> Result<tt::Subtree, mbe::ExpandError> {
149 // dummy implementation for type-checking purposes
150 let col_num = 0;
151 let expanded = quote! {
152 #col_num
153 };
154
155 Ok(expanded)
156}
157
158fn assert_expand(
159 _db: &dyn AstDatabase,
160 _id: LazyMacroId,
161 tt: &tt::Subtree,
162) -> Result<tt::Subtree, mbe::ExpandError> {
163 // A hacky implementation for goto def and hover
164 // We expand `assert!(cond, arg1, arg2)` to
165 // ```
166 // {(cond, &(arg1), &(arg2));}
167 // ```,
168 // which is wrong but useful.
169
170 let mut args = Vec::new();
171 let mut current = Vec::new();
172 for tt in tt.token_trees.iter().cloned() {
173 match tt {
174 tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => {
175 args.push(current);
176 current = Vec::new();
177 }
178 _ => {
179 current.push(tt);
180 }
181 }
182 }
183 if !current.is_empty() {
184 args.push(current);
185 }
186
187 let arg_tts = args.into_iter().flat_map(|arg| {
188 quote! { &(##arg), }
189 }.token_trees).collect::<Vec<_>>();
190
191 let expanded = quote! {
192 { { (##arg_tts); } }
193 };
194 Ok(expanded)
195}
196
197fn file_expand(
198 _db: &dyn AstDatabase,
199 _id: LazyMacroId,
200 _tt: &tt::Subtree,
201) -> Result<tt::Subtree, mbe::ExpandError> {
202 // FIXME: RA purposefully lacks knowledge of absolute file names
203 // so just return "".
204 let file_name = "";
205
206 let expanded = quote! {
207 #file_name
208 };
209
210 Ok(expanded)
211}
212
213fn compile_error_expand(
214 _db: &dyn AstDatabase,
215 _id: LazyMacroId,
216 tt: &tt::Subtree,
217) -> Result<tt::Subtree, mbe::ExpandError> {
218 if tt.count() == 1 {
219 if let tt::TokenTree::Leaf(tt::Leaf::Literal(it)) = &tt.token_trees[0] {
220 let s = it.text.as_str();
221 if s.contains('"') {
222 return Ok(quote! { loop { #it }});
223 }
224 };
225 }
226
227 Err(mbe::ExpandError::BindingError("Must be a string".into()))
228}
229
230fn format_args_expand(
231 _db: &dyn AstDatabase,
232 _id: LazyMacroId,
233 tt: &tt::Subtree,
234) -> Result<tt::Subtree, mbe::ExpandError> {
235 // We expand `format_args!("", a1, a2)` to
236 // ```
237 // std::fmt::Arguments::new_v1(&[], &[
238 // std::fmt::ArgumentV1::new(&arg1,std::fmt::Display::fmt),
239 // std::fmt::ArgumentV1::new(&arg2,std::fmt::Display::fmt),
240 // ])
241 // ```,
242 // which is still not really correct, but close enough for now
243 let mut args = Vec::new();
244 let mut current = Vec::new();
245 for tt in tt.token_trees.iter().cloned() {
246 match tt {
247 tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => {
248 args.push(current);
249 current = Vec::new();
250 }
251 _ => {
252 current.push(tt);
253 }
254 }
255 }
256 if !current.is_empty() {
257 args.push(current);
258 }
259 if args.is_empty() {
260 return Err(mbe::ExpandError::NoMatchingRule);
261 }
262 let _format_string = args.remove(0);
263 let arg_tts = args.into_iter().flat_map(|arg| {
264 quote! { std::fmt::ArgumentV1::new(&(##arg), std::fmt::Display::fmt), }
265 }.token_trees).collect::<Vec<_>>();
266 let expanded = quote! {
267 std::fmt::Arguments::new_v1(&[], &[##arg_tts])
268 };
269 Ok(expanded)
270}
271
272fn unquote_str(lit: &tt::Literal) -> Option<String> {
273 let lit = ast::make::tokens::literal(&lit.to_string());
274 let token = ast::String::cast(lit)?;
275 token.value().map(|it| it.into_owned())
276}
277
278fn concat_expand(
279 _db: &dyn AstDatabase,
280 _arg_id: EagerMacroId,
281 tt: &tt::Subtree,
282) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
283 let mut text = String::new();
284 for (i, t) in tt.token_trees.iter().enumerate() {
285 match t {
286 tt::TokenTree::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => {
287 text += &unquote_str(&it).ok_or_else(|| mbe::ExpandError::ConversionError)?;
288 }
289 tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
290 _ => return Err(mbe::ExpandError::UnexpectedToken),
291 }
292 }
293
294 Ok((quote!(#text), FragmentKind::Expr))
295}
296
297fn relative_file(
298 db: &dyn AstDatabase,
299 call_id: MacroCallId,
300 path: &str,
301 allow_recursion: bool,
302) -> Option<FileId> {
303 let call_site = call_id.as_file().original_file(db);
304 let res = db.resolve_path(call_site, path)?;
305 // Prevent include itself
306 if res == call_site && !allow_recursion {
307 None
308 } else {
309 Some(res)
310 }
311}
312
313fn parse_string(tt: &tt::Subtree) -> Result<String, mbe::ExpandError> {
314 tt.token_trees
315 .get(0)
316 .and_then(|tt| match tt {
317 tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(&it),
318 _ => None,
319 })
320 .ok_or_else(|| mbe::ExpandError::ConversionError)
321}
322
323fn include_expand(
324 db: &dyn AstDatabase,
325 arg_id: EagerMacroId,
326 tt: &tt::Subtree,
327) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
328 let path = parse_string(tt)?;
329 let file_id = relative_file(db, arg_id.into(), &path, false)
330 .ok_or_else(|| mbe::ExpandError::ConversionError)?;
331
332 // FIXME:
333 // Handle include as expression
334 let res = parse_to_token_tree(&db.file_text(file_id))
335 .ok_or_else(|| mbe::ExpandError::ConversionError)?
336 .0;
337
338 Ok((res, FragmentKind::Items))
339}
340
341fn include_bytes_expand(
342 _db: &dyn AstDatabase,
343 _arg_id: EagerMacroId,
344 tt: &tt::Subtree,
345) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
346 let _path = parse_string(tt)?;
347
348 // FIXME: actually read the file here if the user asked for macro expansion
349 let res = tt::Subtree {
350 delimiter: None,
351 token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal {
352 text: r#"b"""#.into(),
353 id: tt::TokenId::unspecified(),
354 }))],
355 };
356 Ok((res, FragmentKind::Expr))
357}
358
359fn include_str_expand(
360 db: &dyn AstDatabase,
361 arg_id: EagerMacroId,
362 tt: &tt::Subtree,
363) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
364 let path = parse_string(tt)?;
365
366 // FIXME: we're not able to read excluded files (which is most of them because
367 // it's unusual to `include_str!` a Rust file), but we can return an empty string.
368 // Ideally, we'd be able to offer a precise expansion if the user asks for macro
369 // expansion.
370 let file_id = match relative_file(db, arg_id.into(), &path, true) {
371 Some(file_id) => file_id,
372 None => {
373 return Ok((quote!(""), FragmentKind::Expr));
374 }
375 };
376
377 let text = db.file_text(file_id);
378 let text = &*text;
379
380 Ok((quote!(#text), FragmentKind::Expr))
381}
382
383fn get_env_inner(db: &dyn AstDatabase, arg_id: EagerMacroId, key: &str) -> Option<String> {
384 let krate = db.lookup_intern_eager_expansion(arg_id).krate;
385 db.crate_graph()[krate].env.get(key)
386}
387
388fn env_expand(
389 db: &dyn AstDatabase,
390 arg_id: EagerMacroId,
391 tt: &tt::Subtree,
392) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
393 let key = parse_string(tt)?;
394
395 // FIXME:
396 // If the environment variable is not defined int rustc, then a compilation error will be emitted.
397 // We might do the same if we fully support all other stuffs.
398 // But for now on, we should return some dummy string for better type infer purpose.
399 // However, we cannot use an empty string here, because for
400 // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become
401 // `include!("foo.rs"), which might go to infinite loop
402 let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| "__RA_UNIMPLEMENTED__".to_string());
403 let expanded = quote! { #s };
404
405 Ok((expanded, FragmentKind::Expr))
406}
407
408fn option_env_expand(
409 db: &dyn AstDatabase,
410 arg_id: EagerMacroId,
411 tt: &tt::Subtree,
412) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
413 let key = parse_string(tt)?;
414 let expanded = match get_env_inner(db, arg_id, &key) {
415 None => quote! { std::option::Option::None::<&str> },
416 Some(s) => quote! { std::option::Some(#s) },
417 };
418
419 Ok((expanded, FragmentKind::Expr))
420}
421
422#[cfg(test)]
423mod tests {
424 use super::*;
425 use crate::{
426 name::AsName, test_db::TestDB, AstNode, EagerCallLoc, MacroCallId, MacroCallKind,
427 MacroCallLoc,
428 };
429 use base_db::{fixture::WithFixture, SourceDatabase};
430 use std::sync::Arc;
431 use syntax::ast::NameOwner;
432
433 fn expand_builtin_macro(ra_fixture: &str) -> String {
434 let (db, file_id) = TestDB::with_single_file(&ra_fixture);
435 let parsed = db.parse(file_id);
436 let macro_calls: Vec<_> =
437 parsed.syntax_node().descendants().filter_map(ast::MacroCall::cast).collect();
438
439 let ast_id_map = db.ast_id_map(file_id.into());
440
441 let expander = find_by_name(&macro_calls[0].name().unwrap().as_name()).unwrap();
442
443 let krate = CrateId(0);
444 let file_id = match expander {
445 Either::Left(expander) => {
446 // the first one should be a macro_rules
447 let def = MacroDefId {
448 krate: Some(CrateId(0)),
449 ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(&macro_calls[0]))),
450 kind: MacroDefKind::BuiltIn(expander),
451 local_inner: false,
452 };
453
454 let loc = MacroCallLoc {
455 def,
456 krate,
457 kind: MacroCallKind::FnLike(AstId::new(
458 file_id.into(),
459 ast_id_map.ast_id(&macro_calls[1]),
460 )),
461 };
462
463 let id: MacroCallId = db.intern_macro(loc).into();
464 id.as_file()
465 }
466 Either::Right(expander) => {
467 // the first one should be a macro_rules
468 let def = MacroDefId {
469 krate: Some(krate),
470 ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(&macro_calls[0]))),
471 kind: MacroDefKind::BuiltInEager(expander),
472 local_inner: false,
473 };
474
475 let args = macro_calls[1].token_tree().unwrap();
476 let parsed_args = mbe::ast_to_token_tree(&args).unwrap().0;
477
478 let arg_id = db.intern_eager_expansion({
479 EagerCallLoc {
480 def,
481 fragment: FragmentKind::Expr,
482 subtree: Arc::new(parsed_args.clone()),
483 krate,
484 file_id: file_id.into(),
485 }
486 });
487
488 let (subtree, fragment) = expander.expand(&db, arg_id, &parsed_args).unwrap();
489 let eager = EagerCallLoc {
490 def,
491 fragment,
492 subtree: Arc::new(subtree),
493 krate,
494 file_id: file_id.into(),
495 };
496
497 let id: MacroCallId = db.intern_eager_expansion(eager).into();
498 id.as_file()
499 }
500 };
501
502 db.parse_or_expand(file_id).unwrap().to_string()
503 }
504
505 #[test]
506 fn test_column_expand() {
507 let expanded = expand_builtin_macro(
508 r#"
509 #[rustc_builtin_macro]
510 macro_rules! column {() => {}}
511 column!()
512 "#,
513 );
514
515 assert_eq!(expanded, "0");
516 }
517
518 #[test]
519 fn test_line_expand() {
520 let expanded = expand_builtin_macro(
521 r#"
522 #[rustc_builtin_macro]
523 macro_rules! line {() => {}}
524 line!()
525 "#,
526 );
527
528 assert_eq!(expanded, "0");
529 }
530
531 #[test]
532 fn test_stringify_expand() {
533 let expanded = expand_builtin_macro(
534 r#"
535 #[rustc_builtin_macro]
536 macro_rules! stringify {() => {}}
537 stringify!(a b c)
538 "#,
539 );
540
541 assert_eq!(expanded, "\"a b c\"");
542 }
543
544 #[test]
545 fn test_env_expand() {
546 let expanded = expand_builtin_macro(
547 r#"
548 #[rustc_builtin_macro]
549 macro_rules! env {() => {}}
550 env!("TEST_ENV_VAR")
551 "#,
552 );
553
554 assert_eq!(expanded, "\"__RA_UNIMPLEMENTED__\"");
555 }
556
557 #[test]
558 fn test_option_env_expand() {
559 let expanded = expand_builtin_macro(
560 r#"
561 #[rustc_builtin_macro]
562 macro_rules! option_env {() => {}}
563 option_env!("TEST_ENV_VAR")
564 "#,
565 );
566
567 assert_eq!(expanded, "std::option::Option::None:: < &str>");
568 }
569
570 #[test]
571 fn test_file_expand() {
572 let expanded = expand_builtin_macro(
573 r#"
574 #[rustc_builtin_macro]
575 macro_rules! file {() => {}}
576 file!()
577 "#,
578 );
579
580 assert_eq!(expanded, "\"\"");
581 }
582
583 #[test]
584 fn test_assert_expand() {
585 let expanded = expand_builtin_macro(
586 r#"
587 #[rustc_builtin_macro]
588 macro_rules! assert {
589 ($cond:expr) => ({ /* compiler built-in */ });
590 ($cond:expr, $($args:tt)*) => ({ /* compiler built-in */ })
591 }
592 assert!(true, "{} {:?}", arg1(a, b, c), arg2);
593 "#,
594 );
595
596 assert_eq!(expanded, "{{(&(true), &(\"{} {:?}\"), &(arg1(a,b,c)), &(arg2),);}}");
597 }
598
599 #[test]
600 fn test_compile_error_expand() {
601 let expanded = expand_builtin_macro(
602 r#"
603 #[rustc_builtin_macro]
604 macro_rules! compile_error {
605 ($msg:expr) => ({ /* compiler built-in */ });
606 ($msg:expr,) => ({ /* compiler built-in */ })
607 }
608 compile_error!("error!");
609 "#,
610 );
611
612 assert_eq!(expanded, r#"loop{"error!"}"#);
613 }
614
615 #[test]
616 fn test_format_args_expand() {
617 let expanded = expand_builtin_macro(
618 r#"
619 #[rustc_builtin_macro]
620 macro_rules! format_args {
621 ($fmt:expr) => ({ /* compiler built-in */ });
622 ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
623 }
624 format_args!("{} {:?}", arg1(a, b, c), arg2);
625 "#,
626 );
627
628 assert_eq!(
629 expanded,
630 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),])"#
631 );
632 }
633
634 #[test]
635 fn test_include_bytes_expand() {
636 let expanded = expand_builtin_macro(
637 r#"
638 #[rustc_builtin_macro]
639 macro_rules! include_bytes {
640 ($file:expr) => {{ /* compiler built-in */ }};
641 ($file:expr,) => {{ /* compiler built-in */ }};
642 }
643 include_bytes("foo");
644 "#,
645 );
646
647 assert_eq!(expanded, r#"b"""#);
648 }
649}