• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Builtin macro
2 
3 use std::mem;
4 
5 use ::tt::Ident;
6 use base_db::{AnchoredPath, Edition, FileId};
7 use cfg::CfgExpr;
8 use either::Either;
9 use mbe::{parse_exprs_with_sep, parse_to_token_tree, TokenMap};
10 use rustc_hash::FxHashMap;
11 use syntax::{
12     ast::{self, AstToken},
13     SmolStr,
14 };
15 
16 use crate::{
17     db::ExpandDatabase, name, quote, tt, EagerCallInfo, ExpandError, ExpandResult, MacroCallId,
18     MacroCallLoc,
19 };
20 
21 macro_rules! register_builtin {
22     ( LAZY: $(($name:ident, $kind: ident) => $expand:ident),* , EAGER: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),*  ) => {
23         #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24         pub enum BuiltinFnLikeExpander {
25             $($kind),*
26         }
27 
28         #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29         pub enum EagerExpander {
30             $($e_kind),*
31         }
32 
33         impl BuiltinFnLikeExpander {
34             pub fn expand(
35                 &self,
36                 db: &dyn ExpandDatabase,
37                 id: MacroCallId,
38                 tt: &tt::Subtree,
39             ) -> ExpandResult<tt::Subtree> {
40                 let expander = match *self {
41                     $( BuiltinFnLikeExpander::$kind => $expand, )*
42                 };
43                 expander(db, id, tt)
44             }
45         }
46 
47         impl EagerExpander {
48             pub fn expand(
49                 &self,
50                 db: &dyn ExpandDatabase,
51                 arg_id: MacroCallId,
52                 tt: &tt::Subtree,
53             ) -> ExpandResult<tt::Subtree> {
54                 let expander = match *self {
55                     $( EagerExpander::$e_kind => $e_expand, )*
56                 };
57                 expander(db, arg_id, tt)
58             }
59         }
60 
61         fn find_by_name(ident: &name::Name) -> Option<Either<BuiltinFnLikeExpander, EagerExpander>> {
62             match ident {
63                 $( id if id == &name::name![$name] => Some(Either::Left(BuiltinFnLikeExpander::$kind)), )*
64                 $( id if id == &name::name![$e_name] => Some(Either::Right(EagerExpander::$e_kind)), )*
65                 _ => return None,
66             }
67         }
68     };
69 }
70 
71 impl EagerExpander {
is_include(&self) -> bool72     pub fn is_include(&self) -> bool {
73         matches!(self, EagerExpander::Include)
74     }
75 }
76 
find_builtin_macro( ident: &name::Name, ) -> Option<Either<BuiltinFnLikeExpander, EagerExpander>>77 pub fn find_builtin_macro(
78     ident: &name::Name,
79 ) -> Option<Either<BuiltinFnLikeExpander, EagerExpander>> {
80     find_by_name(ident)
81 }
82 
83 register_builtin! {
84     LAZY:
85     (column, Column) => column_expand,
86     (file, File) => file_expand,
87     (line, Line) => line_expand,
88     (module_path, ModulePath) => module_path_expand,
89     (assert, Assert) => assert_expand,
90     (stringify, Stringify) => stringify_expand,
91     (llvm_asm, LlvmAsm) => asm_expand,
92     (asm, Asm) => asm_expand,
93     (global_asm, GlobalAsm) => global_asm_expand,
94     (cfg, Cfg) => cfg_expand,
95     (core_panic, CorePanic) => panic_expand,
96     (std_panic, StdPanic) => panic_expand,
97     (unreachable, Unreachable) => unreachable_expand,
98     (log_syntax, LogSyntax) => log_syntax_expand,
99     (trace_macros, TraceMacros) => trace_macros_expand,
100 
101     EAGER:
102     (format_args, FormatArgs) => format_args_expand,
103     (const_format_args, ConstFormatArgs) => format_args_expand,
104     (format_args_nl, FormatArgsNl) => format_args_nl_expand,
105     (compile_error, CompileError) => compile_error_expand,
106     (concat, Concat) => concat_expand,
107     (concat_idents, ConcatIdents) => concat_idents_expand,
108     (concat_bytes, ConcatBytes) => concat_bytes_expand,
109     (include, Include) => include_expand,
110     (include_bytes, IncludeBytes) => include_bytes_expand,
111     (include_str, IncludeStr) => include_str_expand,
112     (env, Env) => env_expand,
113     (option_env, OptionEnv) => option_env_expand
114 }
115 
116 const DOLLAR_CRATE: tt::Ident =
117     tt::Ident { text: SmolStr::new_inline("$crate"), span: tt::TokenId::unspecified() };
118 
module_path_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>119 fn module_path_expand(
120     _db: &dyn ExpandDatabase,
121     _id: MacroCallId,
122     _tt: &tt::Subtree,
123 ) -> ExpandResult<tt::Subtree> {
124     // Just return a dummy result.
125     ExpandResult::ok(quote! { "module::path" })
126 }
127 
line_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>128 fn line_expand(
129     _db: &dyn ExpandDatabase,
130     _id: MacroCallId,
131     _tt: &tt::Subtree,
132 ) -> ExpandResult<tt::Subtree> {
133     // dummy implementation for type-checking purposes
134     let expanded = quote! {
135         0 as u32
136     };
137 
138     ExpandResult::ok(expanded)
139 }
140 
log_syntax_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>141 fn log_syntax_expand(
142     _db: &dyn ExpandDatabase,
143     _id: MacroCallId,
144     _tt: &tt::Subtree,
145 ) -> ExpandResult<tt::Subtree> {
146     ExpandResult::ok(quote! {})
147 }
148 
trace_macros_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>149 fn trace_macros_expand(
150     _db: &dyn ExpandDatabase,
151     _id: MacroCallId,
152     _tt: &tt::Subtree,
153 ) -> ExpandResult<tt::Subtree> {
154     ExpandResult::ok(quote! {})
155 }
156 
stringify_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>157 fn stringify_expand(
158     _db: &dyn ExpandDatabase,
159     _id: MacroCallId,
160     tt: &tt::Subtree,
161 ) -> ExpandResult<tt::Subtree> {
162     let pretty = ::tt::pretty(&tt.token_trees);
163 
164     let expanded = quote! {
165         #pretty
166     };
167 
168     ExpandResult::ok(expanded)
169 }
170 
column_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>171 fn column_expand(
172     _db: &dyn ExpandDatabase,
173     _id: MacroCallId,
174     _tt: &tt::Subtree,
175 ) -> ExpandResult<tt::Subtree> {
176     // dummy implementation for type-checking purposes
177     let expanded = quote! {
178         0 as u32
179     };
180 
181     ExpandResult::ok(expanded)
182 }
183 
assert_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>184 fn assert_expand(
185     _db: &dyn ExpandDatabase,
186     _id: MacroCallId,
187     tt: &tt::Subtree,
188 ) -> ExpandResult<tt::Subtree> {
189     let args = parse_exprs_with_sep(tt, ',');
190     let expanded = match &*args {
191         [cond, panic_args @ ..] => {
192             let comma = tt::Subtree {
193                 delimiter: tt::Delimiter::unspecified(),
194                 token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct {
195                     char: ',',
196                     spacing: tt::Spacing::Alone,
197                     span: tt::TokenId::unspecified(),
198                 }))],
199             };
200             let cond = cond.clone();
201             let panic_args = itertools::Itertools::intersperse(panic_args.iter().cloned(), comma);
202             quote! {{
203                 if !(#cond) {
204                     #DOLLAR_CRATE::panic!(##panic_args);
205                 }
206             }}
207         }
208         [] => quote! {{}},
209     };
210 
211     ExpandResult::ok(expanded)
212 }
213 
file_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>214 fn file_expand(
215     _db: &dyn ExpandDatabase,
216     _id: MacroCallId,
217     _tt: &tt::Subtree,
218 ) -> ExpandResult<tt::Subtree> {
219     // FIXME: RA purposefully lacks knowledge of absolute file names
220     // so just return "".
221     let file_name = "";
222 
223     let expanded = quote! {
224         #file_name
225     };
226 
227     ExpandResult::ok(expanded)
228 }
229 
format_args_expand( db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>230 fn format_args_expand(
231     db: &dyn ExpandDatabase,
232     id: MacroCallId,
233     tt: &tt::Subtree,
234 ) -> ExpandResult<tt::Subtree> {
235     format_args_expand_general(db, id, tt, "")
236 }
237 
format_args_nl_expand( db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>238 fn format_args_nl_expand(
239     db: &dyn ExpandDatabase,
240     id: MacroCallId,
241     tt: &tt::Subtree,
242 ) -> ExpandResult<tt::Subtree> {
243     format_args_expand_general(db, id, tt, "\\n")
244 }
245 
format_args_expand_general( _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, end_string: &str, ) -> ExpandResult<tt::Subtree>246 fn format_args_expand_general(
247     _db: &dyn ExpandDatabase,
248     _id: MacroCallId,
249     tt: &tt::Subtree,
250     end_string: &str,
251 ) -> ExpandResult<tt::Subtree> {
252     let args = parse_exprs_with_sep(tt, ',');
253 
254     let expand_error =
255         ExpandResult::new(tt::Subtree::empty(), mbe::ExpandError::NoMatchingRule.into());
256 
257     let mut key_args = FxHashMap::default();
258     let mut args = args.into_iter().filter_map(|mut arg| {
259         // Remove `key =`.
260         if matches!(arg.token_trees.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=')
261         {
262             // but not with `==`
263             if !matches!(arg.token_trees.get(2), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=')
264             {
265                 let key = arg.token_trees.drain(..2).next().unwrap();
266                 key_args.insert(key.to_string(), arg);
267                 return None;
268             }
269         }
270         Some(arg)
271     }).collect::<Vec<_>>().into_iter();
272     // ^^^^^^^ we need this collect, to enforce the side effect of the filter_map closure (building the `key_args`)
273     let Some(format_subtree) = args.next() else {
274         return expand_error;
275     };
276     let format_string = (|| {
277         let token_tree = format_subtree.token_trees.get(0)?;
278         match token_tree {
279             tt::TokenTree::Leaf(l) => match l {
280                 tt::Leaf::Literal(l) => {
281                     if let Some(mut text) = l.text.strip_prefix('r') {
282                         let mut raw_sharps = String::new();
283                         while let Some(t) = text.strip_prefix('#') {
284                             text = t;
285                             raw_sharps.push('#');
286                         }
287                         text =
288                             text.strip_suffix(&raw_sharps)?.strip_prefix('"')?.strip_suffix('"')?;
289                         Some((text, l.span, Some(raw_sharps)))
290                     } else {
291                         let text = l.text.strip_prefix('"')?.strip_suffix('"')?;
292                         let span = l.span;
293                         Some((text, span, None))
294                     }
295                 }
296                 _ => None,
297             },
298             tt::TokenTree::Subtree(_) => None,
299         }
300     })();
301     let Some((format_string, _format_string_span, raw_sharps)) = format_string else {
302         return expand_error;
303     };
304     let mut format_iter = format_string.chars().peekable();
305     let mut parts = vec![];
306     let mut last_part = String::new();
307     let mut arg_tts = vec![];
308     let mut err = None;
309     while let Some(c) = format_iter.next() {
310         // Parsing the format string. See https://doc.rust-lang.org/std/fmt/index.html#syntax for the grammar and more info
311         match c {
312             '{' => {
313                 if format_iter.peek() == Some(&'{') {
314                     format_iter.next();
315                     last_part.push('{');
316                     continue;
317                 }
318                 let mut argument = String::new();
319                 while ![Some(&'}'), Some(&':')].contains(&format_iter.peek()) {
320                     argument.push(match format_iter.next() {
321                         Some(c) => c,
322                         None => return expand_error,
323                     });
324                 }
325                 let format_spec = match format_iter.next().unwrap() {
326                     '}' => "".to_owned(),
327                     ':' => {
328                         let mut s = String::new();
329                         while let Some(c) = format_iter.next() {
330                             if c == '}' {
331                                 break;
332                             }
333                             s.push(c);
334                         }
335                         s
336                     }
337                     _ => unreachable!(),
338                 };
339                 parts.push(mem::take(&mut last_part));
340                 let arg_tree = if argument.is_empty() {
341                     match args.next() {
342                         Some(x) => x,
343                         None => {
344                             err = Some(mbe::ExpandError::NoMatchingRule.into());
345                             tt::Subtree::empty()
346                         }
347                     }
348                 } else if let Some(tree) = key_args.get(&argument) {
349                     tree.clone()
350                 } else {
351                     // FIXME: we should pick the related substring of the `_format_string_span` as the span. You
352                     // can use `.char_indices()` instead of `.char()` for `format_iter` to find the substring interval.
353                     let ident = Ident::new(argument, tt::TokenId::unspecified());
354                     quote!(#ident)
355                 };
356                 let formatter = match &*format_spec {
357                     "?" => quote!(::core::fmt::Debug::fmt),
358                     "" => quote!(::core::fmt::Display::fmt),
359                     _ => {
360                         // FIXME: implement the rest and return expand error here
361                         quote!(::core::fmt::Display::fmt)
362                     }
363                 };
364                 arg_tts.push(quote! { ::core::fmt::Argument::new(&(#arg_tree), #formatter), });
365             }
366             '}' => {
367                 if format_iter.peek() == Some(&'}') {
368                     format_iter.next();
369                     last_part.push('}');
370                 } else {
371                     return expand_error;
372                 }
373             }
374             _ => last_part.push(c),
375         }
376     }
377     last_part += end_string;
378     if !last_part.is_empty() {
379         parts.push(last_part);
380     }
381     let part_tts = parts.into_iter().map(|x| {
382         let text = if let Some(raw) = &raw_sharps {
383             format!("r{raw}\"{}\"{raw}", x).into()
384         } else {
385             format!("\"{}\"", x).into()
386         };
387         let l = tt::Literal { span: tt::TokenId::unspecified(), text };
388         quote!(#l ,)
389     });
390     let arg_tts = arg_tts.into_iter().flat_map(|arg| arg.token_trees);
391     let expanded = quote! {
392         ::core::fmt::Arguments::new_v1(&[##part_tts], &[##arg_tts])
393     };
394     ExpandResult { value: expanded, err }
395 }
396 
asm_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>397 fn asm_expand(
398     _db: &dyn ExpandDatabase,
399     _id: MacroCallId,
400     tt: &tt::Subtree,
401 ) -> ExpandResult<tt::Subtree> {
402     // We expand all assembly snippets to `format_args!` invocations to get format syntax
403     // highlighting for them.
404 
405     let mut literals = Vec::new();
406     for tt in tt.token_trees.chunks(2) {
407         match tt {
408             [tt::TokenTree::Leaf(tt::Leaf::Literal(lit))]
409             | [tt::TokenTree::Leaf(tt::Leaf::Literal(lit)), tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', span: _, spacing: _ }))] =>
410             {
411                 let krate = DOLLAR_CRATE.clone();
412                 literals.push(quote!(#krate::format_args!(#lit);));
413             }
414             _ => break,
415         }
416     }
417 
418     let expanded = quote! {{
419         ##literals
420         loop {}
421     }};
422     ExpandResult::ok(expanded)
423 }
424 
global_asm_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>425 fn global_asm_expand(
426     _db: &dyn ExpandDatabase,
427     _id: MacroCallId,
428     _tt: &tt::Subtree,
429 ) -> ExpandResult<tt::Subtree> {
430     // Expand to nothing (at item-level)
431     ExpandResult::ok(quote! {})
432 }
433 
cfg_expand( db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>434 fn cfg_expand(
435     db: &dyn ExpandDatabase,
436     id: MacroCallId,
437     tt: &tt::Subtree,
438 ) -> ExpandResult<tt::Subtree> {
439     let loc = db.lookup_intern_macro_call(id);
440     let expr = CfgExpr::parse(tt);
441     let enabled = db.crate_graph()[loc.krate].cfg_options.check(&expr) != Some(false);
442     let expanded = if enabled { quote!(true) } else { quote!(false) };
443     ExpandResult::ok(expanded)
444 }
445 
panic_expand( db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>446 fn panic_expand(
447     db: &dyn ExpandDatabase,
448     id: MacroCallId,
449     tt: &tt::Subtree,
450 ) -> ExpandResult<tt::Subtree> {
451     let loc: MacroCallLoc = db.lookup_intern_macro_call(id);
452     // Expand to a macro call `$crate::panic::panic_{edition}`
453     let mut call = if db.crate_graph()[loc.krate].edition >= Edition::Edition2021 {
454         quote!(#DOLLAR_CRATE::panic::panic_2021!)
455     } else {
456         quote!(#DOLLAR_CRATE::panic::panic_2015!)
457     };
458 
459     // Pass the original arguments
460     call.token_trees.push(tt::TokenTree::Subtree(tt.clone()));
461     ExpandResult::ok(call)
462 }
463 
unreachable_expand( db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>464 fn unreachable_expand(
465     db: &dyn ExpandDatabase,
466     id: MacroCallId,
467     tt: &tt::Subtree,
468 ) -> ExpandResult<tt::Subtree> {
469     let loc: MacroCallLoc = db.lookup_intern_macro_call(id);
470     // Expand to a macro call `$crate::panic::unreachable_{edition}`
471     let mut call = if db.crate_graph()[loc.krate].edition >= Edition::Edition2021 {
472         quote!(#DOLLAR_CRATE::panic::unreachable_2021!)
473     } else {
474         quote!(#DOLLAR_CRATE::panic::unreachable_2015!)
475     };
476 
477     // Pass the original arguments
478     call.token_trees.push(tt::TokenTree::Subtree(tt.clone()));
479     ExpandResult::ok(call)
480 }
481 
unquote_str(lit: &tt::Literal) -> Option<String>482 fn unquote_str(lit: &tt::Literal) -> Option<String> {
483     let lit = ast::make::tokens::literal(&lit.to_string());
484     let token = ast::String::cast(lit)?;
485     token.value().map(|it| it.into_owned())
486 }
487 
unquote_char(lit: &tt::Literal) -> Option<char>488 fn unquote_char(lit: &tt::Literal) -> Option<char> {
489     let lit = ast::make::tokens::literal(&lit.to_string());
490     let token = ast::Char::cast(lit)?;
491     token.value()
492 }
493 
unquote_byte_string(lit: &tt::Literal) -> Option<Vec<u8>>494 fn unquote_byte_string(lit: &tt::Literal) -> Option<Vec<u8>> {
495     let lit = ast::make::tokens::literal(&lit.to_string());
496     let token = ast::ByteString::cast(lit)?;
497     token.value().map(|it| it.into_owned())
498 }
499 
compile_error_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>500 fn compile_error_expand(
501     _db: &dyn ExpandDatabase,
502     _id: MacroCallId,
503     tt: &tt::Subtree,
504 ) -> ExpandResult<tt::Subtree> {
505     let err = match &*tt.token_trees {
506         [tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => match unquote_str(it) {
507             Some(unquoted) => ExpandError::other(unquoted),
508             None => ExpandError::other("`compile_error!` argument must be a string"),
509         },
510         _ => ExpandError::other("`compile_error!` argument must be a string"),
511     };
512 
513     ExpandResult { value: quote! {}, err: Some(err) }
514 }
515 
concat_expand( _db: &dyn ExpandDatabase, _arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>516 fn concat_expand(
517     _db: &dyn ExpandDatabase,
518     _arg_id: MacroCallId,
519     tt: &tt::Subtree,
520 ) -> ExpandResult<tt::Subtree> {
521     let mut err = None;
522     let mut text = String::new();
523     for (i, mut t) in tt.token_trees.iter().enumerate() {
524         // FIXME: hack on top of a hack: `$e:expr` captures get surrounded in parentheses
525         // to ensure the right parsing order, so skip the parentheses here. Ideally we'd
526         // implement rustc's model. cc https://github.com/rust-lang/rust-analyzer/pull/10623
527         if let tt::TokenTree::Subtree(tt::Subtree { delimiter: delim, token_trees }) = t {
528             if let [tt] = &**token_trees {
529                 if delim.kind == tt::DelimiterKind::Parenthesis {
530                     t = tt;
531                 }
532             }
533         }
534 
535         match t {
536             tt::TokenTree::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => {
537                 // concat works with string and char literals, so remove any quotes.
538                 // It also works with integer, float and boolean literals, so just use the rest
539                 // as-is.
540                 if let Some(c) = unquote_char(it) {
541                     text.push(c);
542                 } else {
543                     let component = unquote_str(it).unwrap_or_else(|| it.text.to_string());
544                     text.push_str(&component);
545                 }
546             }
547             // handle boolean literals
548             tt::TokenTree::Leaf(tt::Leaf::Ident(id))
549                 if i % 2 == 0 && (id.text == "true" || id.text == "false") =>
550             {
551                 text.push_str(id.text.as_str());
552             }
553             tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
554             _ => {
555                 err.get_or_insert(mbe::ExpandError::UnexpectedToken.into());
556             }
557         }
558     }
559     ExpandResult { value: quote!(#text), err }
560 }
561 
concat_bytes_expand( _db: &dyn ExpandDatabase, _arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>562 fn concat_bytes_expand(
563     _db: &dyn ExpandDatabase,
564     _arg_id: MacroCallId,
565     tt: &tt::Subtree,
566 ) -> ExpandResult<tt::Subtree> {
567     let mut bytes = Vec::new();
568     let mut err = None;
569     for (i, t) in tt.token_trees.iter().enumerate() {
570         match t {
571             tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => {
572                 let token = ast::make::tokens::literal(&lit.to_string());
573                 match token.kind() {
574                     syntax::SyntaxKind::BYTE => bytes.push(token.text().to_string()),
575                     syntax::SyntaxKind::BYTE_STRING => {
576                         let components = unquote_byte_string(lit).unwrap_or_default();
577                         components.into_iter().for_each(|x| bytes.push(x.to_string()));
578                     }
579                     _ => {
580                         err.get_or_insert(mbe::ExpandError::UnexpectedToken.into());
581                         break;
582                     }
583                 }
584             }
585             tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
586             tt::TokenTree::Subtree(tree) if tree.delimiter.kind == tt::DelimiterKind::Bracket => {
587                 if let Err(e) = concat_bytes_expand_subtree(tree, &mut bytes) {
588                     err.get_or_insert(e);
589                     break;
590                 }
591             }
592             _ => {
593                 err.get_or_insert(mbe::ExpandError::UnexpectedToken.into());
594                 break;
595             }
596         }
597     }
598     let ident = tt::Ident { text: bytes.join(", ").into(), span: tt::TokenId::unspecified() };
599     ExpandResult { value: quote!([#ident]), err }
600 }
601 
concat_bytes_expand_subtree( tree: &tt::Subtree, bytes: &mut Vec<String>, ) -> Result<(), ExpandError>602 fn concat_bytes_expand_subtree(
603     tree: &tt::Subtree,
604     bytes: &mut Vec<String>,
605 ) -> Result<(), ExpandError> {
606     for (ti, tt) in tree.token_trees.iter().enumerate() {
607         match tt {
608             tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => {
609                 let lit = ast::make::tokens::literal(&lit.to_string());
610                 match lit.kind() {
611                     syntax::SyntaxKind::BYTE | syntax::SyntaxKind::INT_NUMBER => {
612                         bytes.push(lit.text().to_string())
613                     }
614                     _ => {
615                         return Err(mbe::ExpandError::UnexpectedToken.into());
616                     }
617                 }
618             }
619             tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if ti % 2 == 1 && punct.char == ',' => (),
620             _ => {
621                 return Err(mbe::ExpandError::UnexpectedToken.into());
622             }
623         }
624     }
625     Ok(())
626 }
627 
concat_idents_expand( _db: &dyn ExpandDatabase, _arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>628 fn concat_idents_expand(
629     _db: &dyn ExpandDatabase,
630     _arg_id: MacroCallId,
631     tt: &tt::Subtree,
632 ) -> ExpandResult<tt::Subtree> {
633     let mut err = None;
634     let mut ident = String::new();
635     for (i, t) in tt.token_trees.iter().enumerate() {
636         match t {
637             tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => {
638                 ident.push_str(id.text.as_str());
639             }
640             tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
641             _ => {
642                 err.get_or_insert(mbe::ExpandError::UnexpectedToken.into());
643             }
644         }
645     }
646     let ident = tt::Ident { text: ident.into(), span: tt::TokenId::unspecified() };
647     ExpandResult { value: quote!(#ident), err }
648 }
649 
relative_file( db: &dyn ExpandDatabase, call_id: MacroCallId, path_str: &str, allow_recursion: bool, ) -> Result<FileId, ExpandError>650 fn relative_file(
651     db: &dyn ExpandDatabase,
652     call_id: MacroCallId,
653     path_str: &str,
654     allow_recursion: bool,
655 ) -> Result<FileId, ExpandError> {
656     let call_site = call_id.as_file().original_file(db);
657     let path = AnchoredPath { anchor: call_site, path: path_str };
658     let res = db
659         .resolve_path(path)
660         .ok_or_else(|| ExpandError::other(format!("failed to load file `{path_str}`")))?;
661     // Prevent include itself
662     if res == call_site && !allow_recursion {
663         Err(ExpandError::other(format!("recursive inclusion of `{path_str}`")))
664     } else {
665         Ok(res)
666     }
667 }
668 
parse_string(tt: &tt::Subtree) -> Result<String, ExpandError>669 fn parse_string(tt: &tt::Subtree) -> Result<String, ExpandError> {
670     tt.token_trees
671         .get(0)
672         .and_then(|tt| match tt {
673             tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(it),
674             _ => None,
675         })
676         .ok_or(mbe::ExpandError::ConversionError.into())
677 }
678 
include_expand( db: &dyn ExpandDatabase, arg_id: MacroCallId, _tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>679 fn include_expand(
680     db: &dyn ExpandDatabase,
681     arg_id: MacroCallId,
682     _tt: &tt::Subtree,
683 ) -> ExpandResult<tt::Subtree> {
684     match db.include_expand(arg_id) {
685         Ok((res, _)) => ExpandResult::ok(res.0.clone()),
686         Err(e) => ExpandResult::new(tt::Subtree::empty(), e),
687     }
688 }
689 
include_arg_to_tt( db: &dyn ExpandDatabase, arg_id: MacroCallId, ) -> Result<(triomphe::Arc<(::tt::Subtree<::tt::TokenId>, TokenMap)>, FileId), ExpandError>690 pub(crate) fn include_arg_to_tt(
691     db: &dyn ExpandDatabase,
692     arg_id: MacroCallId,
693 ) -> Result<(triomphe::Arc<(::tt::Subtree<::tt::TokenId>, TokenMap)>, FileId), ExpandError> {
694     let loc = db.lookup_intern_macro_call(arg_id);
695     let Some(EagerCallInfo {arg, arg_id: Some(arg_id), .. }) = loc.eager.as_deref() else {
696         panic!("include_arg_to_tt called on non include macro call: {:?}", &loc.eager);
697     };
698     let path = parse_string(&arg.0)?;
699     let file_id = relative_file(db, *arg_id, &path, false)?;
700 
701     let (subtree, map) =
702         parse_to_token_tree(&db.file_text(file_id)).ok_or(mbe::ExpandError::ConversionError)?;
703     Ok((triomphe::Arc::new((subtree, map)), file_id))
704 }
705 
include_bytes_expand( _db: &dyn ExpandDatabase, _arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>706 fn include_bytes_expand(
707     _db: &dyn ExpandDatabase,
708     _arg_id: MacroCallId,
709     tt: &tt::Subtree,
710 ) -> ExpandResult<tt::Subtree> {
711     if let Err(e) = parse_string(tt) {
712         return ExpandResult::new(tt::Subtree::empty(), e);
713     }
714 
715     // FIXME: actually read the file here if the user asked for macro expansion
716     let res = tt::Subtree {
717         delimiter: tt::Delimiter::unspecified(),
718         token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal {
719             text: r#"b"""#.into(),
720             span: tt::TokenId::unspecified(),
721         }))],
722     };
723     ExpandResult::ok(res)
724 }
725 
include_str_expand( db: &dyn ExpandDatabase, arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>726 fn include_str_expand(
727     db: &dyn ExpandDatabase,
728     arg_id: MacroCallId,
729     tt: &tt::Subtree,
730 ) -> ExpandResult<tt::Subtree> {
731     let path = match parse_string(tt) {
732         Ok(it) => it,
733         Err(e) => return ExpandResult::new(tt::Subtree::empty(), e),
734     };
735 
736     // FIXME: we're not able to read excluded files (which is most of them because
737     // it's unusual to `include_str!` a Rust file), but we can return an empty string.
738     // Ideally, we'd be able to offer a precise expansion if the user asks for macro
739     // expansion.
740     let file_id = match relative_file(db, arg_id, &path, true) {
741         Ok(file_id) => file_id,
742         Err(_) => {
743             return ExpandResult::ok(quote!(""));
744         }
745     };
746 
747     let text = db.file_text(file_id);
748     let text = &*text;
749 
750     ExpandResult::ok(quote!(#text))
751 }
752 
get_env_inner(db: &dyn ExpandDatabase, arg_id: MacroCallId, key: &str) -> Option<String>753 fn get_env_inner(db: &dyn ExpandDatabase, arg_id: MacroCallId, key: &str) -> Option<String> {
754     let krate = db.lookup_intern_macro_call(arg_id).krate;
755     db.crate_graph()[krate].env.get(key)
756 }
757 
env_expand( db: &dyn ExpandDatabase, arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>758 fn env_expand(
759     db: &dyn ExpandDatabase,
760     arg_id: MacroCallId,
761     tt: &tt::Subtree,
762 ) -> ExpandResult<tt::Subtree> {
763     let key = match parse_string(tt) {
764         Ok(it) => it,
765         Err(e) => return ExpandResult::new(tt::Subtree::empty(), e),
766     };
767 
768     let mut err = None;
769     let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| {
770         // The only variable rust-analyzer ever sets is `OUT_DIR`, so only diagnose that to avoid
771         // unnecessary diagnostics for eg. `CARGO_PKG_NAME`.
772         if key == "OUT_DIR" {
773             err = Some(ExpandError::other(r#"`OUT_DIR` not set, enable "build scripts" to fix"#));
774         }
775 
776         // If the variable is unset, still return a dummy string to help type inference along.
777         // We cannot use an empty string here, because for
778         // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become
779         // `include!("foo.rs"), which might go to infinite loop
780         "UNRESOLVED_ENV_VAR".to_string()
781     });
782     let expanded = quote! { #s };
783 
784     ExpandResult { value: expanded, err }
785 }
786 
option_env_expand( db: &dyn ExpandDatabase, arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree>787 fn option_env_expand(
788     db: &dyn ExpandDatabase,
789     arg_id: MacroCallId,
790     tt: &tt::Subtree,
791 ) -> ExpandResult<tt::Subtree> {
792     let key = match parse_string(tt) {
793         Ok(it) => it,
794         Err(e) => return ExpandResult::new(tt::Subtree::empty(), e),
795     };
796     // FIXME: Use `DOLLAR_CRATE` when that works in eager macros.
797     let expanded = match get_env_inner(db, arg_id, &key) {
798         None => quote! { ::core::option::Option::None::<&str> },
799         Some(s) => quote! { ::core::option::Option::Some(#s) },
800     };
801 
802     ExpandResult::ok(expanded)
803 }
804