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