1use base_db::AnchoredPath;
4use cfg::CfgExpr;
5use either::Either;
6use intern::{
7 Symbol,
8 sym::{self},
9};
10use itertools::Itertools;
11use mbe::{DelimiterKind, expect_fragment};
12use span::{Edition, FileId, Span};
13use stdx::format_to;
14use syntax::{
15 format_smolstr,
16 unescape::{unescape_byte, unescape_char, unescape_str},
17};
18use syntax_bridge::syntax_node_to_token_tree;
19
20use crate::{
21 EditionedFileId, ExpandError, ExpandResult, Lookup as _, MacroCallId,
22 builtin::quote::{WithDelimiter, dollar_crate, quote},
23 db::ExpandDatabase,
24 hygiene::{span_with_call_site_ctxt, span_with_def_site_ctxt},
25 name,
26 span_map::SpanMap,
27 tt::{self, DelimSpan, TtElement, TtIter},
28};
29
30macro_rules! register_builtin {
31 ( $LAZY:ident: $(($name:ident, $kind: ident) => $expand:ident),* , $EAGER:ident: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => {
32 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33 pub enum $LAZY {
34 $($kind),*
35 }
36
37 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38 pub enum $EAGER {
39 $($e_kind),*
40 }
41
42 impl BuiltinFnLikeExpander {
43 fn expander(&self) -> fn (&dyn ExpandDatabase, MacroCallId, &tt::TopSubtree, Span) -> ExpandResult<tt::TopSubtree> {
44 match *self {
45 $( BuiltinFnLikeExpander::$kind => $expand, )*
46 }
47 }
48 }
49
50 impl EagerExpander {
51 fn expander(&self) -> fn (&dyn ExpandDatabase, MacroCallId, &tt::TopSubtree, Span) -> ExpandResult<tt::TopSubtree> {
52 match *self {
53 $( EagerExpander::$e_kind => $e_expand, )*
54 }
55 }
56 }
57
58 fn find_by_name(ident: &name::Name) -> Option<Either<BuiltinFnLikeExpander, EagerExpander>> {
59 match ident {
60 $( id if id == &sym::$name => Some(Either::Left(BuiltinFnLikeExpander::$kind)), )*
61 $( id if id == &sym::$e_name => Some(Either::Right(EagerExpander::$e_kind)), )*
62 _ => return None,
63 }
64 }
65 };
66}
67
68impl BuiltinFnLikeExpander {
69 pub fn expand(
70 &self,
71 db: &dyn ExpandDatabase,
72 id: MacroCallId,
73 tt: &tt::TopSubtree,
74 span: Span,
75 ) -> ExpandResult<tt::TopSubtree> {
76 let span = span_with_def_site_ctxt(db, span, id.into(), Edition::CURRENT);
77 self.expander()(db, id, tt, span)
78 }
79
80 pub fn is_asm(&self) -> bool {
81 matches!(self, Self::Asm | Self::GlobalAsm | Self::NakedAsm)
82 }
83}
84
85impl EagerExpander {
86 pub fn expand(
87 &self,
88 db: &dyn ExpandDatabase,
89 id: MacroCallId,
90 tt: &tt::TopSubtree,
91 span: Span,
92 ) -> ExpandResult<tt::TopSubtree> {
93 let span = span_with_def_site_ctxt(db, span, id.into(), Edition::CURRENT);
94 self.expander()(db, id, tt, span)
95 }
96
97 pub fn is_include(&self) -> bool {
98 matches!(self, EagerExpander::Include)
99 }
100
101 pub fn is_include_like(&self) -> bool {
102 matches!(
103 self,
104 EagerExpander::Include | EagerExpander::IncludeStr | EagerExpander::IncludeBytes
105 )
106 }
107
108 pub fn is_env_or_option_env(&self) -> bool {
109 matches!(self, EagerExpander::Env | EagerExpander::OptionEnv)
110 }
111}
112
113pub fn find_builtin_macro(
114 ident: &name::Name,
115) -> Option<Either<BuiltinFnLikeExpander, EagerExpander>> {
116 find_by_name(ident)
117}
118
119register_builtin! {
120 BuiltinFnLikeExpander:
121 (column, Column) => line_expand,
122 (file, File) => file_expand,
123 (line, Line) => line_expand,
124 (module_path, ModulePath) => module_path_expand,
125 (assert, Assert) => assert_expand,
126 (stringify, Stringify) => stringify_expand,
127 (asm, Asm) => asm_expand,
128 (global_asm, GlobalAsm) => global_asm_expand,
129 (naked_asm, NakedAsm) => naked_asm_expand,
130 (cfg_select, CfgSelect) => cfg_select_expand,
131 (cfg, Cfg) => cfg_expand,
132 (core_panic, CorePanic) => panic_expand,
133 (std_panic, StdPanic) => panic_expand,
134 (unreachable, Unreachable) => unreachable_expand,
135 (log_syntax, LogSyntax) => log_syntax_expand,
136 (trace_macros, TraceMacros) => trace_macros_expand,
137 (format_args, FormatArgs) => format_args_expand,
138 (const_format_args, ConstFormatArgs) => format_args_expand,
139 (format_args_nl, FormatArgsNl) => format_args_nl_expand,
140 (quote, Quote) => quote_expand,
141
142 EagerExpander:
143 (compile_error, CompileError) => compile_error_expand,
144 (concat, Concat) => concat_expand,
145 (concat_bytes, ConcatBytes) => concat_bytes_expand,
146 (include, Include) => include_expand,
147 (include_bytes, IncludeBytes) => include_bytes_expand,
148 (include_str, IncludeStr) => include_str_expand,
149 (env, Env) => env_expand,
150 (option_env, OptionEnv) => option_env_expand
151}
152
153fn mk_pound(span: Span) -> tt::Leaf {
154 crate::tt::Leaf::Punct(crate::tt::Punct { char: '#', spacing: crate::tt::Spacing::Alone, span })
155}
156
157fn module_path_expand(
158 _db: &dyn ExpandDatabase,
159 _id: MacroCallId,
160 _tt: &tt::TopSubtree,
161 span: Span,
162) -> ExpandResult<tt::TopSubtree> {
163 ExpandResult::ok(quote! {span =>
165 "module::path"
166 })
167}
168
169fn line_expand(
170 _db: &dyn ExpandDatabase,
171 _id: MacroCallId,
172 _tt: &tt::TopSubtree,
173 span: Span,
174) -> ExpandResult<tt::TopSubtree> {
175 ExpandResult::ok(tt::TopSubtree::invisible_from_leaves(
179 span,
180 [tt::Leaf::Literal(tt::Literal {
181 symbol: sym::INTEGER_0,
182 span,
183 kind: tt::LitKind::Integer,
184 suffix: Some(sym::u32),
185 })],
186 ))
187}
188
189fn log_syntax_expand(
190 _db: &dyn ExpandDatabase,
191 _id: MacroCallId,
192 _tt: &tt::TopSubtree,
193 span: Span,
194) -> ExpandResult<tt::TopSubtree> {
195 ExpandResult::ok(quote! {span =>})
196}
197
198fn trace_macros_expand(
199 _db: &dyn ExpandDatabase,
200 _id: MacroCallId,
201 _tt: &tt::TopSubtree,
202 span: Span,
203) -> ExpandResult<tt::TopSubtree> {
204 ExpandResult::ok(quote! {span =>})
205}
206
207fn stringify_expand(
208 _db: &dyn ExpandDatabase,
209 _id: MacroCallId,
210 tt: &tt::TopSubtree,
211 span: Span,
212) -> ExpandResult<tt::TopSubtree> {
213 let pretty = ::tt::pretty(tt.token_trees().flat_tokens());
214
215 let expanded = quote! {span =>
216 #pretty
217 };
218
219 ExpandResult::ok(expanded)
220}
221
222fn assert_expand(
223 db: &dyn ExpandDatabase,
224 id: MacroCallId,
225 tt: &tt::TopSubtree,
226 span: Span,
227) -> ExpandResult<tt::TopSubtree> {
228 let call_site_span = span_with_call_site_ctxt(db, span, id.into(), Edition::CURRENT);
229
230 let mut iter = tt.iter();
231
232 let cond = expect_fragment(
233 &mut iter,
234 parser::PrefixEntryPoint::Expr,
235 id.lookup(db).krate.data(db).edition,
236 tt.top_subtree().delimiter.delim_span(),
237 );
238 _ = iter.expect_char(',');
239 let rest = iter.remaining();
240
241 let dollar_crate = dollar_crate(span);
242 let panic_args = rest.iter();
243 let mac = if use_panic_2021(db, span) {
244 quote! {call_site_span => #dollar_crate::panic::panic_2021!(# #panic_args) }
245 } else {
246 quote! {call_site_span => #dollar_crate::panic!(# #panic_args) }
247 };
248 let value = cond.value;
249 let expanded = quote! {call_site_span =>{
250 if !(#value) {
251 #mac;
252 }
253 }};
254
255 match cond.err {
256 Some(err) => ExpandResult::new(expanded, err.into()),
257 None => ExpandResult::ok(expanded),
258 }
259}
260
261fn file_expand(
262 _db: &dyn ExpandDatabase,
263 _id: MacroCallId,
264 _tt: &tt::TopSubtree,
265 span: Span,
266) -> ExpandResult<tt::TopSubtree> {
267 let file_name = "file";
270
271 let expanded = quote! {span =>
272 #file_name
273 };
274
275 ExpandResult::ok(expanded)
276}
277
278fn format_args_expand(
279 _db: &dyn ExpandDatabase,
280 _id: MacroCallId,
281 tt: &tt::TopSubtree,
282 span: Span,
283) -> ExpandResult<tt::TopSubtree> {
284 let pound = mk_pound(span);
285 let mut tt = tt.clone();
286 tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Parenthesis;
287 ExpandResult::ok(quote! {span =>
288 builtin #pound format_args #tt
289 })
290}
291
292fn format_args_nl_expand(
293 _db: &dyn ExpandDatabase,
294 _id: MacroCallId,
295 tt: &tt::TopSubtree,
296 span: Span,
297) -> ExpandResult<tt::TopSubtree> {
298 let pound = mk_pound(span);
299 let mut tt = tt.clone();
300 tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Parenthesis;
301 if let Some(tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal {
302 symbol: text,
303 kind: tt::LitKind::Str,
304 ..
305 }))) = tt.0.get_mut(1)
306 {
307 *text = Symbol::intern(&format_smolstr!("{}\\n", text.as_str()));
308 }
309 ExpandResult::ok(quote! {span =>
310 builtin #pound format_args #tt
311 })
312}
313
314fn asm_expand(
315 _db: &dyn ExpandDatabase,
316 _id: MacroCallId,
317 tt: &tt::TopSubtree,
318 span: Span,
319) -> ExpandResult<tt::TopSubtree> {
320 let mut tt = tt.clone();
321 tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Parenthesis;
322 let pound = mk_pound(span);
323 let expanded = quote! {span =>
324 builtin #pound asm #tt
325 };
326 ExpandResult::ok(expanded)
327}
328
329fn global_asm_expand(
330 _db: &dyn ExpandDatabase,
331 _id: MacroCallId,
332 tt: &tt::TopSubtree,
333 span: Span,
334) -> ExpandResult<tt::TopSubtree> {
335 let mut tt = tt.clone();
336 tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Parenthesis;
337 let pound = mk_pound(span);
338 let expanded = quote! {span =>
339 builtin #pound global_asm #tt
340 };
341 ExpandResult::ok(expanded)
342}
343
344fn naked_asm_expand(
345 _db: &dyn ExpandDatabase,
346 _id: MacroCallId,
347 tt: &tt::TopSubtree,
348 span: Span,
349) -> ExpandResult<tt::TopSubtree> {
350 let mut tt = tt.clone();
351 tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Parenthesis;
352 let pound = mk_pound(span);
353 let expanded = quote! {span =>
354 builtin #pound naked_asm #tt
355 };
356 ExpandResult::ok(expanded)
357}
358
359fn cfg_select_expand(
360 db: &dyn ExpandDatabase,
361 id: MacroCallId,
362 tt: &tt::TopSubtree,
363 span: Span,
364) -> ExpandResult<tt::TopSubtree> {
365 let loc = db.lookup_intern_macro_call(id);
366 let cfg_options = loc.krate.cfg_options(db);
367
368 let mut iter = tt.iter();
369 let mut expand_to = None;
370 while let Some(next) = iter.peek() {
371 let active = if let tt::TtElement::Leaf(tt::Leaf::Ident(ident)) = next
372 && ident.sym == sym::underscore
373 {
374 iter.next();
375 true
376 } else {
377 cfg_options.check(&CfgExpr::parse_from_iter(&mut iter)) != Some(false)
378 };
379 match iter.expect_glued_punct() {
380 Ok(it) if it.len() == 2 && it[0].char == '=' && it[1].char == '>' => {}
381 _ => {
382 let err_span = iter.peek().map(|it| it.first_span()).unwrap_or(span);
383 return ExpandResult::new(
384 tt::TopSubtree::empty(tt::DelimSpan::from_single(span)),
385 ExpandError::other(err_span, "expected `=>` after cfg expression"),
386 );
387 }
388 }
389 let expand_to_if_active = match iter.next() {
390 Some(tt::TtElement::Subtree(_, tt)) => tt.remaining(),
391 _ => {
392 let err_span = iter.peek().map(|it| it.first_span()).unwrap_or(span);
393 return ExpandResult::new(
394 tt::TopSubtree::empty(tt::DelimSpan::from_single(span)),
395 ExpandError::other(err_span, "expected a token tree after `=>`"),
396 );
397 }
398 };
399
400 if expand_to.is_none() && active {
401 expand_to = Some(expand_to_if_active);
402 }
403 }
404 match expand_to {
405 Some(expand_to) => {
406 let mut builder = tt::TopSubtreeBuilder::new(tt::Delimiter {
407 kind: tt::DelimiterKind::Invisible,
408 open: span,
409 close: span,
410 });
411 builder.extend_with_tt(expand_to);
412 ExpandResult::ok(builder.build())
413 }
414 None => ExpandResult::new(
415 tt::TopSubtree::empty(tt::DelimSpan::from_single(span)),
416 ExpandError::other(
417 span,
418 "none of the predicates in this `cfg_select` evaluated to true",
419 ),
420 ),
421 }
422}
423
424fn cfg_expand(
425 db: &dyn ExpandDatabase,
426 id: MacroCallId,
427 tt: &tt::TopSubtree,
428 span: Span,
429) -> ExpandResult<tt::TopSubtree> {
430 let loc = db.lookup_intern_macro_call(id);
431 let expr = CfgExpr::parse(tt);
432 let enabled = loc.krate.cfg_options(db).check(&expr) != Some(false);
433 let expanded = if enabled { quote!(span=>true) } else { quote!(span=>false) };
434 ExpandResult::ok(expanded)
435}
436
437fn panic_expand(
438 db: &dyn ExpandDatabase,
439 id: MacroCallId,
440 tt: &tt::TopSubtree,
441 span: Span,
442) -> ExpandResult<tt::TopSubtree> {
443 let dollar_crate = dollar_crate(span);
444 let call_site_span = span_with_call_site_ctxt(db, span, id.into(), Edition::CURRENT);
445
446 let mac = if use_panic_2021(db, call_site_span) { sym::panic_2021 } else { sym::panic_2015 };
447
448 let subtree = WithDelimiter {
450 delimiter: tt::Delimiter {
451 open: call_site_span,
452 close: call_site_span,
453 kind: tt::DelimiterKind::Parenthesis,
454 },
455 token_trees: tt.token_trees(),
456 };
457
458 let call = quote!(call_site_span =>#dollar_crate::panic::#mac! #subtree);
460
461 ExpandResult::ok(call)
462}
463
464fn unreachable_expand(
465 db: &dyn ExpandDatabase,
466 id: MacroCallId,
467 tt: &tt::TopSubtree,
468 span: Span,
469) -> ExpandResult<tt::TopSubtree> {
470 let dollar_crate = dollar_crate(span);
471 let call_site_span = span_with_call_site_ctxt(db, span, id.into(), Edition::CURRENT);
472
473 let mac = if use_panic_2021(db, call_site_span) {
474 sym::unreachable_2021
475 } else {
476 sym::unreachable_2015
477 };
478
479 let mut subtree = tt.clone();
481 *subtree.top_subtree_delimiter_mut() = tt::Delimiter {
482 open: call_site_span,
483 close: call_site_span,
484 kind: tt::DelimiterKind::Parenthesis,
485 };
486
487 let call = quote!(call_site_span =>#dollar_crate::panic::#mac! #subtree);
489
490 ExpandResult::ok(call)
491}
492
493#[allow(clippy::never_loop)]
494fn use_panic_2021(db: &dyn ExpandDatabase, span: Span) -> bool {
495 loop {
499 let Some(expn) = span.ctx.outer_expn(db) else {
500 break false;
501 };
502 let expn = db.lookup_intern_macro_call(expn.into());
503 break expn.def.edition >= Edition::Edition2021;
512 }
513}
514
515fn compile_error_expand(
516 _db: &dyn ExpandDatabase,
517 _id: MacroCallId,
518 tt: &tt::TopSubtree,
519 span: Span,
520) -> ExpandResult<tt::TopSubtree> {
521 let err = match &*tt.0 {
522 [
523 _,
524 tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal {
525 symbol: text,
526 span: _,
527 kind: tt::LitKind::Str | tt::LitKind::StrRaw(_),
528 suffix: _,
529 })),
530 ] => ExpandError::other(span, Box::from(unescape_symbol(text).as_str())),
531 _ => ExpandError::other(span, "`compile_error!` argument must be a string"),
532 };
533
534 ExpandResult { value: quote! {span =>}, err: Some(err) }
535}
536
537fn concat_expand(
538 _db: &dyn ExpandDatabase,
539 _arg_id: MacroCallId,
540 tt: &tt::TopSubtree,
541 call_site: Span,
542) -> ExpandResult<tt::TopSubtree> {
543 let mut err = None;
544 let mut text = String::new();
545 let mut span: Option<Span> = None;
546 let mut record_span = |s: Span| match &mut span {
547 Some(span) if span.anchor == s.anchor => span.range = span.range.cover(s.range),
548 Some(_) => (),
549 None => span = Some(s),
550 };
551
552 let mut i = 0;
553 let mut iter = tt.iter();
554 while let Some(mut t) = iter.next() {
555 if let TtElement::Subtree(subtree, subtree_iter) = &t
559 && let [tt::TokenTree::Leaf(tt)] = subtree_iter.remaining().flat_tokens()
560 && subtree.delimiter.kind == tt::DelimiterKind::Parenthesis
561 {
562 t = TtElement::Leaf(tt);
563 }
564 match t {
565 TtElement::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => {
566 match it.kind {
570 tt::LitKind::Char => {
571 if let Ok(c) = unescape_char(it.symbol.as_str()) {
572 text.push(c);
573 }
574 record_span(it.span);
575 }
576 tt::LitKind::Integer | tt::LitKind::Float => {
577 format_to!(text, "{}", it.symbol.as_str())
578 }
579 tt::LitKind::Str => {
580 text.push_str(unescape_symbol(&it.symbol).as_str());
581 record_span(it.span);
582 }
583 tt::LitKind::StrRaw(_) => {
584 format_to!(text, "{}", it.symbol.as_str());
585 record_span(it.span);
586 }
587 tt::LitKind::Byte
588 | tt::LitKind::ByteStr
589 | tt::LitKind::ByteStrRaw(_)
590 | tt::LitKind::CStr
591 | tt::LitKind::CStrRaw(_)
592 | tt::LitKind::Err(_) => {
593 err = Some(ExpandError::other(it.span, "unexpected literal"))
594 }
595 }
596 }
597 TtElement::Leaf(tt::Leaf::Ident(id))
599 if i % 2 == 0 && (id.sym == sym::true_ || id.sym == sym::false_) =>
600 {
601 text.push_str(id.sym.as_str());
602 record_span(id.span);
603 }
604 TtElement::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
605 TtElement::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 0 && punct.char == '-' => {
607 let t = match iter.next() {
608 Some(t) => t,
609 None => {
610 err.get_or_insert(ExpandError::other(
611 call_site,
612 "unexpected end of input after '-'",
613 ));
614 break;
615 }
616 };
617
618 match t {
619 TtElement::Leaf(tt::Leaf::Literal(it))
620 if matches!(it.kind, tt::LitKind::Integer | tt::LitKind::Float) =>
621 {
622 format_to!(text, "-{}", it.symbol.as_str());
623 record_span(punct.span.cover(it.span));
624 }
625 _ => {
626 err.get_or_insert(ExpandError::other(
627 call_site,
628 "expected integer or floating pointer number after '-'",
629 ));
630 break;
631 }
632 }
633 }
634 _ => {
635 err.get_or_insert(ExpandError::other(call_site, "unexpected token"));
636 }
637 }
638 i += 1;
639 }
640 let span = span.unwrap_or_else(|| tt.top_subtree().delimiter.open);
641 ExpandResult { value: quote!(span =>#text), err }
642}
643
644fn concat_bytes_expand(
645 _db: &dyn ExpandDatabase,
646 _arg_id: MacroCallId,
647 tt: &tt::TopSubtree,
648 call_site: Span,
649) -> ExpandResult<tt::TopSubtree> {
650 let mut bytes = String::new();
651 let mut err = None;
652 let mut span: Option<Span> = None;
653 let mut record_span = |s: Span| match &mut span {
654 Some(span) if span.anchor == s.anchor => span.range = span.range.cover(s.range),
655 Some(_) => (),
656 None => span = Some(s),
657 };
658 for (i, t) in tt.iter().enumerate() {
659 match t {
660 TtElement::Leaf(tt::Leaf::Literal(tt::Literal {
661 symbol: text,
662 span,
663 kind,
664 suffix: _,
665 })) => {
666 record_span(*span);
667 match kind {
668 tt::LitKind::Byte => {
669 if let Ok(b) = unescape_byte(text.as_str()) {
670 bytes.extend(
671 b.escape_ascii().filter_map(|it| char::from_u32(it as u32)),
672 );
673 }
674 }
675 tt::LitKind::ByteStr => {
676 bytes.push_str(text.as_str());
677 }
678 tt::LitKind::ByteStrRaw(_) => {
679 bytes.extend(text.as_str().escape_debug());
680 }
681 _ => {
682 err.get_or_insert(ExpandError::other(*span, "unexpected token"));
683 break;
684 }
685 }
686 }
687 TtElement::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
688 TtElement::Subtree(tree, tree_iter)
689 if tree.delimiter.kind == tt::DelimiterKind::Bracket =>
690 {
691 if let Err(e) =
692 concat_bytes_expand_subtree(tree_iter, &mut bytes, &mut record_span, call_site)
693 {
694 err.get_or_insert(e);
695 break;
696 }
697 }
698 _ => {
699 err.get_or_insert(ExpandError::other(call_site, "unexpected token"));
700 break;
701 }
702 }
703 }
704 let span = span.unwrap_or(tt.top_subtree().delimiter.open);
705 ExpandResult {
706 value: tt::TopSubtree::invisible_from_leaves(
707 span,
708 [tt::Leaf::Literal(tt::Literal {
709 symbol: Symbol::intern(&bytes),
710 span,
711 kind: tt::LitKind::ByteStr,
712 suffix: None,
713 })],
714 ),
715 err,
716 }
717}
718
719fn concat_bytes_expand_subtree(
720 tree_iter: TtIter<'_>,
721 bytes: &mut String,
722 mut record_span: impl FnMut(Span),
723 err_span: Span,
724) -> Result<(), ExpandError> {
725 for (ti, tt) in tree_iter.enumerate() {
726 match tt {
727 TtElement::Leaf(tt::Leaf::Literal(tt::Literal {
728 symbol: text,
729 span,
730 kind: tt::LitKind::Byte,
731 suffix: _,
732 })) => {
733 if let Ok(b) = unescape_byte(text.as_str()) {
734 bytes.extend(b.escape_ascii().filter_map(|it| char::from_u32(it as u32)));
735 }
736 record_span(*span);
737 }
738 TtElement::Leaf(tt::Leaf::Literal(tt::Literal {
739 symbol: text,
740 span,
741 kind: tt::LitKind::Integer,
742 suffix: _,
743 })) => {
744 record_span(*span);
745 if let Ok(b) = text.as_str().parse::<u8>() {
746 bytes.extend(b.escape_ascii().filter_map(|it| char::from_u32(it as u32)));
747 }
748 }
749 TtElement::Leaf(tt::Leaf::Punct(punct)) if ti % 2 == 1 && punct.char == ',' => (),
750 _ => {
751 return Err(ExpandError::other(err_span, "unexpected token"));
752 }
753 }
754 }
755 Ok(())
756}
757
758fn relative_file(
759 db: &dyn ExpandDatabase,
760 call_id: MacroCallId,
761 path_str: &str,
762 allow_recursion: bool,
763 err_span: Span,
764) -> Result<EditionedFileId, ExpandError> {
765 let lookup = db.lookup_intern_macro_call(call_id);
766 let call_site = lookup.kind.file_id().original_file_respecting_includes(db).file_id(db);
767 let path = AnchoredPath { anchor: call_site, path: path_str };
768 let res: FileId = db
769 .resolve_path(path)
770 .ok_or_else(|| ExpandError::other(err_span, format!("failed to load file `{path_str}`")))?;
771 if res == call_site && !allow_recursion {
773 Err(ExpandError::other(err_span, format!("recursive inclusion of `{path_str}`")))
774 } else {
775 Ok(EditionedFileId::new(db, res, lookup.krate.data(db).edition))
776 }
777}
778
779fn parse_string(tt: &tt::TopSubtree) -> Result<(Symbol, Span), ExpandError> {
780 let mut tt = TtElement::Subtree(tt.top_subtree(), tt.iter());
781 (|| {
782 while let TtElement::Subtree(sub, tt_iter) = &mut tt
786 && let DelimiterKind::Parenthesis | DelimiterKind::Invisible = sub.delimiter.kind
787 {
788 tt =
789 tt_iter.exactly_one().map_err(|_| sub.delimiter.open.cover(sub.delimiter.close))?;
790 }
791
792 match tt {
793 TtElement::Leaf(tt::Leaf::Literal(tt::Literal {
794 symbol: text,
795 span,
796 kind: tt::LitKind::Str,
797 suffix: _,
798 })) => Ok((unescape_symbol(text), *span)),
799 TtElement::Leaf(tt::Leaf::Literal(tt::Literal {
800 symbol: text,
801 span,
802 kind: tt::LitKind::StrRaw(_),
803 suffix: _,
804 })) => Ok((text.clone(), *span)),
805 TtElement::Leaf(l) => Err(*l.span()),
806 TtElement::Subtree(tt, _) => Err(tt.delimiter.open.cover(tt.delimiter.close)),
807 }
808 })()
809 .map_err(|span| ExpandError::other(span, "expected string literal"))
810}
811
812fn include_expand(
813 db: &dyn ExpandDatabase,
814 arg_id: MacroCallId,
815 tt: &tt::TopSubtree,
816 span: Span,
817) -> ExpandResult<tt::TopSubtree> {
818 let editioned_file_id = match include_input_to_file_id(db, arg_id, tt) {
819 Ok(editioned_file_id) => editioned_file_id,
820 Err(e) => {
821 return ExpandResult::new(
822 tt::TopSubtree::empty(DelimSpan { open: span, close: span }),
823 e,
824 );
825 }
826 };
827 let span_map = db.real_span_map(editioned_file_id);
828 ExpandResult::ok(syntax_node_to_token_tree(
830 &db.parse(editioned_file_id).syntax_node(),
831 SpanMap::RealSpanMap(span_map),
832 span,
833 syntax_bridge::DocCommentDesugarMode::ProcMacro,
834 ))
835}
836
837pub fn include_input_to_file_id(
838 db: &dyn ExpandDatabase,
839 arg_id: MacroCallId,
840 arg: &tt::TopSubtree,
841) -> Result<EditionedFileId, ExpandError> {
842 let (s, span) = parse_string(arg)?;
843 relative_file(db, arg_id, s.as_str(), false, span)
844}
845
846fn include_bytes_expand(
847 _db: &dyn ExpandDatabase,
848 _arg_id: MacroCallId,
849 _tt: &tt::TopSubtree,
850 span: Span,
851) -> ExpandResult<tt::TopSubtree> {
852 let res = tt::TopSubtree::invisible_from_leaves(
854 span,
855 [tt::Leaf::Literal(tt::Literal {
856 symbol: Symbol::empty(),
857 span,
858 kind: tt::LitKind::ByteStrRaw(1),
859 suffix: None,
860 })],
861 );
862 ExpandResult::ok(res)
863}
864
865fn include_str_expand(
866 db: &dyn ExpandDatabase,
867 arg_id: MacroCallId,
868 tt: &tt::TopSubtree,
869 call_site: Span,
870) -> ExpandResult<tt::TopSubtree> {
871 let (path, input_span) = match parse_string(tt) {
872 Ok(it) => it,
873 Err(e) => {
874 return ExpandResult::new(
875 tt::TopSubtree::empty(DelimSpan { open: call_site, close: call_site }),
876 e,
877 );
878 }
879 };
880
881 let file_id = match relative_file(db, arg_id, path.as_str(), true, input_span) {
886 Ok(file_id) => file_id,
887 Err(_) => {
888 return ExpandResult::ok(quote!(call_site =>""));
889 }
890 };
891
892 let text = db.file_text(file_id.file_id(db));
893 let text = &**text.text(db);
894
895 ExpandResult::ok(quote!(call_site =>#text))
896}
897
898fn get_env_inner(db: &dyn ExpandDatabase, arg_id: MacroCallId, key: &Symbol) -> Option<String> {
899 let krate = db.lookup_intern_macro_call(arg_id).krate;
900 krate.env(db).get(key.as_str())
901}
902
903fn env_expand(
904 db: &dyn ExpandDatabase,
905 arg_id: MacroCallId,
906 tt: &tt::TopSubtree,
907 span: Span,
908) -> ExpandResult<tt::TopSubtree> {
909 let (key, span) = match parse_string(tt) {
910 Ok(it) => it,
911 Err(e) => {
912 return ExpandResult::new(
913 tt::TopSubtree::empty(DelimSpan { open: span, close: span }),
914 e,
915 );
916 }
917 };
918
919 let mut err = None;
920 let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| {
921 if key.as_str() == "OUT_DIR" {
924 err = Some(ExpandError::other(
925 span,
926 r#"`OUT_DIR` not set, build scripts may have failed to run"#,
927 ));
928 }
929
930 "UNRESOLVED_ENV_VAR".to_owned()
935 });
936 let expanded = quote! {span => #s };
937
938 ExpandResult { value: expanded, err }
939}
940
941fn option_env_expand(
942 db: &dyn ExpandDatabase,
943 arg_id: MacroCallId,
944 tt: &tt::TopSubtree,
945 call_site: Span,
946) -> ExpandResult<tt::TopSubtree> {
947 let (key, span) = match parse_string(tt) {
948 Ok(it) => it,
949 Err(e) => {
950 return ExpandResult::new(
951 tt::TopSubtree::empty(DelimSpan { open: call_site, close: call_site }),
952 e,
953 );
954 }
955 };
956 let dollar_crate = dollar_crate(call_site);
957 let expanded = match get_env_inner(db, arg_id, &key) {
958 None => quote! {call_site => #dollar_crate::option::Option::None::<&str> },
959 Some(s) => {
960 let s = quote! (span => #s);
961 quote! {call_site => #dollar_crate::option::Option::Some(#s) }
962 }
963 };
964
965 ExpandResult::ok(expanded)
966}
967
968fn quote_expand(
969 _db: &dyn ExpandDatabase,
970 _arg_id: MacroCallId,
971 _tt: &tt::TopSubtree,
972 span: Span,
973) -> ExpandResult<tt::TopSubtree> {
974 ExpandResult::new(
975 tt::TopSubtree::empty(tt::DelimSpan { open: span, close: span }),
976 ExpandError::other(span, "quote! is not implemented"),
977 )
978}
979
980fn unescape_symbol(s: &Symbol) -> Symbol {
981 if s.as_str().contains('\\') {
982 let s = s.as_str();
983 let mut buf = String::with_capacity(s.len());
984 unescape_str(s, |_, c| {
985 if let Ok(c) = c {
986 buf.push(c)
987 }
988 });
989 Symbol::intern(&buf)
990 } else {
991 s.clone()
992 }
993}