]> git.lizzy.rs Git - rust.git/blob - crates/hir_expand/src/builtin_fn_macro.rs
Merge #11182
[rust.git] / crates / hir_expand / src / builtin_fn_macro.rs
1 //! Builtin macro
2 use crate::{
3     db::AstDatabase, name, quote, AstId, CrateId, MacroCallId, MacroCallLoc, MacroDefId,
4     MacroDefKind,
5 };
6
7 use base_db::{AnchoredPath, Edition, FileId};
8 use cfg::CfgExpr;
9 use either::Either;
10 use mbe::{parse_exprs_with_sep, parse_to_token_tree, ExpandResult};
11 use syntax::ast::{self, AstToken};
12
13 macro_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: MacroCallId,
30                 tt: &tt::Subtree,
31             ) -> ExpandResult<tt::Subtree> {
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: MacroCallId,
44                 tt: &tt::Subtree,
45             ) -> ExpandResult<ExpandedEager> {
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
63 #[derive(Debug, Default)]
64 pub struct ExpandedEager {
65     pub(crate) subtree: tt::Subtree,
66     /// The included file ID of the include macro.
67     pub(crate) included_file: Option<FileId>,
68 }
69
70 impl ExpandedEager {
71     fn new(subtree: tt::Subtree) -> Self {
72         ExpandedEager { subtree, included_file: None }
73     }
74 }
75
76 pub fn find_builtin_macro(
77     ident: &name::Name,
78     krate: CrateId,
79     ast_id: AstId<ast::Macro>,
80 ) -> Option<MacroDefId> {
81     let kind = find_by_name(ident)?;
82
83     match kind {
84         Either::Left(kind) => Some(MacroDefId {
85             krate,
86             kind: MacroDefKind::BuiltIn(kind, ast_id),
87             local_inner: false,
88         }),
89         Either::Right(kind) => Some(MacroDefId {
90             krate,
91             kind: MacroDefKind::BuiltInEager(kind, ast_id),
92             local_inner: false,
93         }),
94     }
95 }
96
97 register_builtin! {
98     LAZY:
99     (column, Column) => column_expand,
100     (file, File) => file_expand,
101     (line, Line) => line_expand,
102     (module_path, ModulePath) => module_path_expand,
103     (assert, Assert) => assert_expand,
104     (stringify, Stringify) => stringify_expand,
105     (format_args, FormatArgs) => format_args_expand,
106     (const_format_args, ConstFormatArgs) => format_args_expand,
107     // format_args_nl only differs in that it adds a newline in the end,
108     // so we use the same stub expansion for now
109     (format_args_nl, FormatArgsNl) => format_args_expand,
110     (llvm_asm, LlvmAsm) => asm_expand,
111     (asm, Asm) => asm_expand,
112     (global_asm, GlobalAsm) => global_asm_expand,
113     (cfg, Cfg) => cfg_expand,
114     (core_panic, CorePanic) => panic_expand,
115     (std_panic, StdPanic) => panic_expand,
116     (log_syntax, LogSyntax) => log_syntax_expand,
117     (trace_macros, TraceMacros) => trace_macros_expand,
118
119     EAGER:
120     (compile_error, CompileError) => compile_error_expand,
121     (concat, Concat) => concat_expand,
122     (concat_idents, ConcatIdents) => concat_idents_expand,
123     (include, Include) => include_expand,
124     (include_bytes, IncludeBytes) => include_bytes_expand,
125     (include_str, IncludeStr) => include_str_expand,
126     (env, Env) => env_expand,
127     (option_env, OptionEnv) => option_env_expand
128 }
129
130 fn module_path_expand(
131     _db: &dyn AstDatabase,
132     _id: MacroCallId,
133     _tt: &tt::Subtree,
134 ) -> ExpandResult<tt::Subtree> {
135     // Just return a dummy result.
136     ExpandResult::ok(quote! { "module::path" })
137 }
138
139 fn line_expand(
140     _db: &dyn AstDatabase,
141     _id: MacroCallId,
142     _tt: &tt::Subtree,
143 ) -> ExpandResult<tt::Subtree> {
144     // dummy implementation for type-checking purposes
145     let line_num = 0;
146     let expanded = quote! {
147         #line_num
148     };
149
150     ExpandResult::ok(expanded)
151 }
152
153 fn log_syntax_expand(
154     _db: &dyn AstDatabase,
155     _id: MacroCallId,
156     _tt: &tt::Subtree,
157 ) -> ExpandResult<tt::Subtree> {
158     ExpandResult::ok(quote! {})
159 }
160
161 fn trace_macros_expand(
162     _db: &dyn AstDatabase,
163     _id: MacroCallId,
164     _tt: &tt::Subtree,
165 ) -> ExpandResult<tt::Subtree> {
166     ExpandResult::ok(quote! {})
167 }
168
169 fn stringify_expand(
170     _db: &dyn AstDatabase,
171     _id: MacroCallId,
172     tt: &tt::Subtree,
173 ) -> ExpandResult<tt::Subtree> {
174     let pretty = tt::pretty(&tt.token_trees);
175
176     let expanded = quote! {
177         #pretty
178     };
179
180     ExpandResult::ok(expanded)
181 }
182
183 fn column_expand(
184     _db: &dyn AstDatabase,
185     _id: MacroCallId,
186     _tt: &tt::Subtree,
187 ) -> ExpandResult<tt::Subtree> {
188     // dummy implementation for type-checking purposes
189     let col_num = 0;
190     let expanded = quote! {
191         #col_num
192     };
193
194     ExpandResult::ok(expanded)
195 }
196
197 fn assert_expand(
198     _db: &dyn AstDatabase,
199     _id: MacroCallId,
200     tt: &tt::Subtree,
201 ) -> ExpandResult<tt::Subtree> {
202     let krate = tt::Ident { text: "$crate".into(), id: tt::TokenId::unspecified() };
203     let args = parse_exprs_with_sep(tt, ',');
204     let expanded = match &*args {
205         [cond, panic_args @ ..] => {
206             let comma = tt::Subtree {
207                 delimiter: None,
208                 token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct {
209                     char: ',',
210                     spacing: tt::Spacing::Alone,
211                     id: tt::TokenId::unspecified(),
212                 }))],
213             };
214             let cond = cond.clone();
215             let panic_args = itertools::Itertools::intersperse(panic_args.iter().cloned(), comma);
216             quote! {{
217                 if !#cond {
218                     #krate::panic!(##panic_args);
219                 }
220             }}
221         }
222         [] => quote! {{}},
223     };
224
225     ExpandResult::ok(expanded)
226 }
227
228 fn file_expand(
229     _db: &dyn AstDatabase,
230     _id: MacroCallId,
231     _tt: &tt::Subtree,
232 ) -> ExpandResult<tt::Subtree> {
233     // FIXME: RA purposefully lacks knowledge of absolute file names
234     // so just return "".
235     let file_name = "";
236
237     let expanded = quote! {
238         #file_name
239     };
240
241     ExpandResult::ok(expanded)
242 }
243
244 fn format_args_expand(
245     _db: &dyn AstDatabase,
246     _id: MacroCallId,
247     tt: &tt::Subtree,
248 ) -> ExpandResult<tt::Subtree> {
249     // We expand `format_args!("", a1, a2)` to
250     // ```
251     // std::fmt::Arguments::new_v1(&[], &[
252     //   std::fmt::ArgumentV1::new(&arg1,std::fmt::Display::fmt),
253     //   std::fmt::ArgumentV1::new(&arg2,std::fmt::Display::fmt),
254     // ])
255     // ```,
256     // which is still not really correct, but close enough for now
257     let mut args = parse_exprs_with_sep(tt, ',');
258
259     if args.is_empty() {
260         return ExpandResult::only_err(mbe::ExpandError::NoMatchingRule);
261     }
262     for arg in &mut args {
263         // Remove `key =`.
264         if matches!(arg.token_trees.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=' && p.spacing != tt::Spacing::Joint)
265         {
266             arg.token_trees.drain(..2);
267         }
268     }
269     let _format_string = args.remove(0);
270     let arg_tts = args.into_iter().flat_map(|arg| {
271         quote! { std::fmt::ArgumentV1::new(&(#arg), std::fmt::Display::fmt), }
272     }.token_trees);
273     let expanded = quote! {
274         // It's unsafe since https://github.com/rust-lang/rust/pull/83302
275         // Wrap an unsafe block to avoid false-positive `missing-unsafe` lint.
276         // FIXME: Currently we don't have `unused_unsafe` lint so an extra unsafe block won't cause issues on early
277         // stable rust-src.
278         unsafe {
279             std::fmt::Arguments::new_v1(&[], &[##arg_tts])
280         }
281     };
282     ExpandResult::ok(expanded)
283 }
284
285 fn asm_expand(
286     _db: &dyn AstDatabase,
287     _id: MacroCallId,
288     tt: &tt::Subtree,
289 ) -> ExpandResult<tt::Subtree> {
290     // We expand all assembly snippets to `format_args!` invocations to get format syntax
291     // highlighting for them.
292
293     let krate = tt::Ident { text: "$crate".into(), id: tt::TokenId::unspecified() };
294
295     let mut literals = Vec::new();
296     for tt in tt.token_trees.chunks(2) {
297         match tt {
298             [tt::TokenTree::Leaf(tt::Leaf::Literal(lit))]
299             | [tt::TokenTree::Leaf(tt::Leaf::Literal(lit)), tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', id: _, spacing: _ }))] =>
300             {
301                 let krate = krate.clone();
302                 literals.push(quote!(#krate::format_args!(#lit);));
303             }
304             _ => break,
305         }
306     }
307
308     let expanded = quote! {{
309         ##literals
310         ()
311     }};
312     ExpandResult::ok(expanded)
313 }
314
315 fn global_asm_expand(
316     _db: &dyn AstDatabase,
317     _id: MacroCallId,
318     _tt: &tt::Subtree,
319 ) -> ExpandResult<tt::Subtree> {
320     // Expand to nothing (at item-level)
321     ExpandResult::ok(quote! {})
322 }
323
324 fn cfg_expand(
325     db: &dyn AstDatabase,
326     id: MacroCallId,
327     tt: &tt::Subtree,
328 ) -> ExpandResult<tt::Subtree> {
329     let loc = db.lookup_intern_macro_call(id);
330     let expr = CfgExpr::parse(tt);
331     let enabled = db.crate_graph()[loc.krate].cfg_options.check(&expr) != Some(false);
332     let expanded = if enabled { quote!(true) } else { quote!(false) };
333     ExpandResult::ok(expanded)
334 }
335
336 fn panic_expand(
337     db: &dyn AstDatabase,
338     id: MacroCallId,
339     tt: &tt::Subtree,
340 ) -> ExpandResult<tt::Subtree> {
341     let loc: MacroCallLoc = db.lookup_intern_macro_call(id);
342     // Expand to a macro call `$crate::panic::panic_{edition}`
343     let krate = tt::Ident { text: "$crate".into(), id: tt::TokenId::unspecified() };
344     let mut call = if db.crate_graph()[loc.krate].edition == Edition::Edition2021 {
345         quote!(#krate::panic::panic_2021!)
346     } else {
347         quote!(#krate::panic::panic_2015!)
348     };
349
350     // Pass the original arguments
351     call.token_trees.push(tt::TokenTree::Subtree(tt.clone()));
352     ExpandResult::ok(call)
353 }
354
355 fn unquote_str(lit: &tt::Literal) -> Option<String> {
356     let lit = ast::make::tokens::literal(&lit.to_string());
357     let token = ast::String::cast(lit)?;
358     token.value().map(|it| it.into_owned())
359 }
360
361 fn compile_error_expand(
362     _db: &dyn AstDatabase,
363     _id: MacroCallId,
364     tt: &tt::Subtree,
365 ) -> ExpandResult<ExpandedEager> {
366     let err = match &*tt.token_trees {
367         [tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => {
368             let text = it.text.as_str();
369             if text.starts_with('"') && text.ends_with('"') {
370                 // FIXME: does not handle raw strings
371                 mbe::ExpandError::Other(text[1..text.len() - 1].to_string())
372             } else {
373                 mbe::ExpandError::BindingError("`compile_error!` argument must be a string".into())
374             }
375         }
376         _ => mbe::ExpandError::BindingError("`compile_error!` argument must be a string".into()),
377     };
378
379     ExpandResult { value: ExpandedEager::new(quote! {}), err: Some(err) }
380 }
381
382 fn concat_expand(
383     _db: &dyn AstDatabase,
384     _arg_id: MacroCallId,
385     tt: &tt::Subtree,
386 ) -> ExpandResult<ExpandedEager> {
387     let mut err = None;
388     let mut text = String::new();
389     for (i, mut t) in tt.token_trees.iter().enumerate() {
390         // FIXME: hack on top of a hack: `$e:expr` captures get surrounded in parentheses
391         // to ensure the right parsing order, so skip the parentheses here. Ideally we'd
392         // implement rustc's model. cc https://github.com/rust-analyzer/rust-analyzer/pull/10623
393         if let tt::TokenTree::Subtree(tt::Subtree { delimiter: Some(delim), token_trees }) = t {
394             if let [tt] = &**token_trees {
395                 if delim.kind == tt::DelimiterKind::Parenthesis {
396                     t = tt;
397                 }
398             }
399         }
400
401         match t {
402             tt::TokenTree::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => {
403                 // concat works with string and char literals, so remove any quotes.
404                 // It also works with integer, float and boolean literals, so just use the rest
405                 // as-is.
406                 let component = unquote_str(it).unwrap_or_else(|| it.text.to_string());
407                 text.push_str(&component);
408             }
409             // handle boolean literals
410             tt::TokenTree::Leaf(tt::Leaf::Ident(id))
411                 if i % 2 == 0 && (id.text == "true" || id.text == "false") =>
412             {
413                 text.push_str(id.text.as_str());
414             }
415             tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
416             _ => {
417                 err.get_or_insert(mbe::ExpandError::UnexpectedToken);
418             }
419         }
420     }
421     ExpandResult { value: ExpandedEager::new(quote!(#text)), err }
422 }
423
424 fn concat_idents_expand(
425     _db: &dyn AstDatabase,
426     _arg_id: MacroCallId,
427     tt: &tt::Subtree,
428 ) -> ExpandResult<ExpandedEager> {
429     let mut err = None;
430     let mut ident = String::new();
431     for (i, t) in tt.token_trees.iter().enumerate() {
432         match t {
433             tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => {
434                 ident.push_str(id.text.as_str());
435             }
436             tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
437             _ => {
438                 err.get_or_insert(mbe::ExpandError::UnexpectedToken);
439             }
440         }
441     }
442     let ident = tt::Ident { text: ident.into(), id: tt::TokenId::unspecified() };
443     ExpandResult { value: ExpandedEager::new(quote!(#ident)), err }
444 }
445
446 fn relative_file(
447     db: &dyn AstDatabase,
448     call_id: MacroCallId,
449     path_str: &str,
450     allow_recursion: bool,
451 ) -> Result<FileId, mbe::ExpandError> {
452     let call_site = call_id.as_file().original_file(db);
453     let path = AnchoredPath { anchor: call_site, path: path_str };
454     let res = db
455         .resolve_path(path)
456         .ok_or_else(|| mbe::ExpandError::Other(format!("failed to load file `{}`", path_str)))?;
457     // Prevent include itself
458     if res == call_site && !allow_recursion {
459         Err(mbe::ExpandError::Other(format!("recursive inclusion of `{}`", path_str)))
460     } else {
461         Ok(res)
462     }
463 }
464
465 fn parse_string(tt: &tt::Subtree) -> Result<String, mbe::ExpandError> {
466     tt.token_trees
467         .get(0)
468         .and_then(|tt| match tt {
469             tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(it),
470             _ => None,
471         })
472         .ok_or(mbe::ExpandError::ConversionError)
473 }
474
475 fn include_expand(
476     db: &dyn AstDatabase,
477     arg_id: MacroCallId,
478     tt: &tt::Subtree,
479 ) -> ExpandResult<ExpandedEager> {
480     let res = (|| {
481         let path = parse_string(tt)?;
482         let file_id = relative_file(db, arg_id, &path, false)?;
483
484         let subtree =
485             parse_to_token_tree(&db.file_text(file_id)).ok_or(mbe::ExpandError::ConversionError)?.0;
486         Ok((subtree, file_id))
487     })();
488
489     match res {
490         Ok((subtree, file_id)) => {
491             ExpandResult::ok(ExpandedEager { subtree, included_file: Some(file_id) })
492         }
493         Err(e) => ExpandResult::only_err(e),
494     }
495 }
496
497 fn include_bytes_expand(
498     _db: &dyn AstDatabase,
499     _arg_id: MacroCallId,
500     tt: &tt::Subtree,
501 ) -> ExpandResult<ExpandedEager> {
502     if let Err(e) = parse_string(tt) {
503         return ExpandResult::only_err(e);
504     }
505
506     // FIXME: actually read the file here if the user asked for macro expansion
507     let res = tt::Subtree {
508         delimiter: None,
509         token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal {
510             text: r#"b"""#.into(),
511             id: tt::TokenId::unspecified(),
512         }))],
513     };
514     ExpandResult::ok(ExpandedEager::new(res))
515 }
516
517 fn include_str_expand(
518     db: &dyn AstDatabase,
519     arg_id: MacroCallId,
520     tt: &tt::Subtree,
521 ) -> ExpandResult<ExpandedEager> {
522     let path = match parse_string(tt) {
523         Ok(it) => it,
524         Err(e) => return ExpandResult::only_err(e),
525     };
526
527     // FIXME: we're not able to read excluded files (which is most of them because
528     // it's unusual to `include_str!` a Rust file), but we can return an empty string.
529     // Ideally, we'd be able to offer a precise expansion if the user asks for macro
530     // expansion.
531     let file_id = match relative_file(db, arg_id, &path, true) {
532         Ok(file_id) => file_id,
533         Err(_) => {
534             return ExpandResult::ok(ExpandedEager::new(quote!("")));
535         }
536     };
537
538     let text = db.file_text(file_id);
539     let text = &*text;
540
541     ExpandResult::ok(ExpandedEager::new(quote!(#text)))
542 }
543
544 fn get_env_inner(db: &dyn AstDatabase, arg_id: MacroCallId, key: &str) -> Option<String> {
545     let krate = db.lookup_intern_macro_call(arg_id).krate;
546     db.crate_graph()[krate].env.get(key)
547 }
548
549 fn env_expand(
550     db: &dyn AstDatabase,
551     arg_id: MacroCallId,
552     tt: &tt::Subtree,
553 ) -> ExpandResult<ExpandedEager> {
554     let key = match parse_string(tt) {
555         Ok(it) => it,
556         Err(e) => return ExpandResult::only_err(e),
557     };
558
559     let mut err = None;
560     let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| {
561         // The only variable rust-analyzer ever sets is `OUT_DIR`, so only diagnose that to avoid
562         // unnecessary diagnostics for eg. `CARGO_PKG_NAME`.
563         if key == "OUT_DIR" {
564             err = Some(mbe::ExpandError::Other(
565                 r#"`OUT_DIR` not set, enable "run build scripts" to fix"#.into(),
566             ));
567         }
568
569         // If the variable is unset, still return a dummy string to help type inference along.
570         // We cannot use an empty string here, because for
571         // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become
572         // `include!("foo.rs"), which might go to infinite loop
573         "__RA_UNIMPLEMENTED__".to_string()
574     });
575     let expanded = quote! { #s };
576
577     ExpandResult { value: ExpandedEager::new(expanded), err }
578 }
579
580 fn option_env_expand(
581     db: &dyn AstDatabase,
582     arg_id: MacroCallId,
583     tt: &tt::Subtree,
584 ) -> ExpandResult<ExpandedEager> {
585     let key = match parse_string(tt) {
586         Ok(it) => it,
587         Err(e) => return ExpandResult::only_err(e),
588     };
589
590     let expanded = match get_env_inner(db, arg_id, &key) {
591         None => quote! { std::option::Option::None::<&str> },
592         Some(s) => quote! { std::option::Some(#s) },
593     };
594
595     ExpandResult::ok(ExpandedEager::new(expanded))
596 }