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