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 pound = mk_pound(span);
438 let expanded = quote! {span =>
441 builtin #pound cfg #tt
442 };
443 ExpandResult::ok(expanded)
444}
445
446fn panic_expand(
447 db: &dyn SourceDatabase,
448 id: MacroCallId,
449 tt: &tt::TopSubtree,
450 span: Span,
451) -> ExpandResult<tt::TopSubtree> {
452 let dollar_crate = dollar_crate(span);
453 let call_site_span = span_with_call_site_ctxt(db, span, id.into(), Edition::CURRENT);
454
455 let mac = if use_panic_2021(db, call_site_span) { sym::panic_2021 } else { sym::panic_2015 };
456
457 let subtree = WithDelimiter {
459 delimiter: tt::Delimiter {
460 open: call_site_span,
461 close: call_site_span,
462 kind: tt::DelimiterKind::Parenthesis,
463 },
464 token_trees: tt.token_trees(),
465 };
466
467 let call = quote!(call_site_span =>#dollar_crate::panic::#mac! #subtree);
469
470 ExpandResult::ok(call)
471}
472
473fn unreachable_expand(
474 db: &dyn SourceDatabase,
475 id: MacroCallId,
476 tt: &tt::TopSubtree,
477 span: Span,
478) -> ExpandResult<tt::TopSubtree> {
479 let dollar_crate = dollar_crate(span);
480 let call_site_span = span_with_call_site_ctxt(db, span, id.into(), Edition::CURRENT);
481
482 let mac = if use_panic_2021(db, call_site_span) {
483 sym::unreachable_2021
484 } else {
485 sym::unreachable_2015
486 };
487
488 let mut subtree = tt.clone();
490 subtree.set_top_subtree_delimiter_kind(tt::DelimiterKind::Parenthesis);
491 subtree.set_top_subtree_delimiter_span(tt::DelimSpan {
492 open: call_site_span,
493 close: call_site_span,
494 });
495
496 let call = quote!(call_site_span =>#dollar_crate::panic::#mac! #subtree);
498
499 ExpandResult::ok(call)
500}
501
502#[allow(clippy::never_loop)]
503fn use_panic_2021(db: &dyn SourceDatabase, span: Span) -> bool {
504 loop {
508 let Some(expn) = span.ctx.outer_expn(db) else {
509 break false;
510 };
511 let expn = crate::MacroCallId::from(expn).loc(db);
512 break expn.def.edition >= Edition::Edition2021;
521 }
522}
523
524fn compile_error_expand(
525 _db: &dyn SourceDatabase,
526 _id: MacroCallId,
527 tt: &tt::TopSubtree,
528 span: Span,
529) -> ExpandResult<tt::TopSubtree> {
530 let err = match tt.iter().collect_array() {
531 Some(
532 [
533 tt::TtElement::Leaf(tt::Leaf::Literal(
534 lit @ tt::Literal { kind: tt::LitKind::Str | tt::LitKind::StrRaw(_), .. },
535 )),
536 ],
537 ) => ExpandError::other(span, Box::from(unescape_str(lit.text()))),
538 _ => ExpandError::other(span, "`compile_error!` argument must be a string"),
539 };
540
541 ExpandResult { value: quote! {span =>}, err: Some(err) }
542}
543
544fn concat_expand(
545 _db: &dyn SourceDatabase,
546 _arg_id: MacroCallId,
547 tt: &tt::TopSubtree,
548 call_site: Span,
549) -> ExpandResult<tt::TopSubtree> {
550 let mut err = None;
551 let mut text = String::new();
552 let mut span: Option<Span> = None;
553 let mut record_span = |s: Span| match &mut span {
554 Some(span) if span.anchor == s.anchor => span.range = span.range.cover(s.range),
555 Some(_) => (),
556 None => span = Some(s),
557 };
558
559 let mut i = 0;
560 let mut iter = tt.iter();
561 while let Some(mut t) = iter.next() {
562 if let TtElement::Subtree(subtree, subtree_iter) = &t
566 && let Some([tt::TtElement::Leaf(tt)]) = subtree_iter.clone().collect_array()
567 && subtree.delimiter.kind == tt::DelimiterKind::Parenthesis
568 {
569 t = TtElement::Leaf(tt);
570 }
571 match t {
572 TtElement::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => {
573 match it.kind {
577 tt::LitKind::Char => {
578 if let Ok(c) = unescape_char(it.text()) {
579 text.push(c);
580 }
581 record_span(it.span);
582 }
583 tt::LitKind::Integer | tt::LitKind::Float => {
584 format_to!(text, "{}", it.text())
585 }
586 tt::LitKind::Str => {
587 text.push_str(&unescape_str(it.text()));
588 record_span(it.span);
589 }
590 tt::LitKind::StrRaw(_) => {
591 format_to!(text, "{}", it.text());
592 record_span(it.span);
593 }
594 tt::LitKind::Byte
595 | tt::LitKind::ByteStr
596 | tt::LitKind::ByteStrRaw(_)
597 | tt::LitKind::CStr
598 | tt::LitKind::CStrRaw(_)
599 | tt::LitKind::Err(_) => {
600 err = Some(ExpandError::other(it.span, "unexpected literal"))
601 }
602 }
603 }
604 TtElement::Leaf(tt::Leaf::Ident(id))
606 if i % 2 == 0 && (id.sym == sym::true_ || id.sym == sym::false_) =>
607 {
608 text.push_str(id.sym.as_str());
609 record_span(id.span);
610 }
611 TtElement::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
612 TtElement::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 0 && punct.char == '-' => {
614 let t = match iter.next() {
615 Some(t) => t,
616 None => {
617 err.get_or_insert(ExpandError::other(
618 call_site,
619 "unexpected end of input after '-'",
620 ));
621 break;
622 }
623 };
624
625 match t {
626 TtElement::Leaf(tt::Leaf::Literal(it))
627 if matches!(it.kind, tt::LitKind::Integer | tt::LitKind::Float) =>
628 {
629 format_to!(text, "-{}", it.text());
630 record_span(punct.span.cover(it.span));
631 }
632 _ => {
633 err.get_or_insert(ExpandError::other(
634 call_site,
635 "expected integer or floating pointer number after '-'",
636 ));
637 break;
638 }
639 }
640 }
641 _ => {
642 err.get_or_insert(ExpandError::other(call_site, "unexpected token"));
643 }
644 }
645 i += 1;
646 }
647 let span = span.unwrap_or_else(|| tt.top_subtree().delimiter.open);
648 ExpandResult { value: quote!(span =>#text), err }
649}
650
651fn concat_bytes_expand(
652 _db: &dyn SourceDatabase,
653 _arg_id: MacroCallId,
654 tt: &tt::TopSubtree,
655 call_site: Span,
656) -> ExpandResult<tt::TopSubtree> {
657 let mut bytes = String::new();
658 let mut err = None;
659 let mut span: Option<Span> = None;
660 let mut record_span = |s: Span| match &mut span {
661 Some(span) if span.anchor == s.anchor => span.range = span.range.cover(s.range),
662 Some(_) => (),
663 None => span = Some(s),
664 };
665 for (i, t) in tt.iter().enumerate() {
666 match t {
667 TtElement::Leaf(tt::Leaf::Literal(lit @ tt::Literal { span, kind, .. })) => {
668 let text = lit.text();
669 record_span(span);
670 match kind {
671 tt::LitKind::Byte => {
672 if let Ok(b) = unescape_byte(text) {
673 bytes.extend(
674 b.escape_ascii().filter_map(|it| char::from_u32(it as u32)),
675 );
676 }
677 }
678 tt::LitKind::ByteStr => {
679 bytes.push_str(text);
680 }
681 tt::LitKind::ByteStrRaw(_) => {
682 bytes.extend(text.escape_debug());
683 }
684 _ => {
685 err.get_or_insert(ExpandError::other(span, "unexpected token"));
686 break;
687 }
688 }
689 }
690 TtElement::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
691 TtElement::Subtree(tree, tree_iter)
692 if tree.delimiter.kind == tt::DelimiterKind::Bracket =>
693 {
694 if let Err(e) =
695 concat_bytes_expand_subtree(tree_iter, &mut bytes, &mut record_span, call_site)
696 {
697 err.get_or_insert(e);
698 break;
699 }
700 }
701 _ => {
702 err.get_or_insert(ExpandError::other(call_site, "unexpected token"));
703 break;
704 }
705 }
706 }
707 let span = span.unwrap_or(tt.top_subtree().delimiter.open);
708 ExpandResult {
709 value: tt::TopSubtree::invisible_from_leaves(
710 span,
711 [tt::Leaf::Literal(tt::Literal::new_no_suffix(&bytes, span, tt::LitKind::ByteStr))],
712 ),
713 err,
714 }
715}
716
717fn concat_bytes_expand_subtree(
718 tree_iter: TtIter<'_>,
719 bytes: &mut String,
720 mut record_span: impl FnMut(Span),
721 err_span: Span,
722) -> Result<(), ExpandError> {
723 for (ti, tt) in tree_iter.enumerate() {
724 match tt {
725 TtElement::Leaf(tt::Leaf::Literal(
726 lit @ tt::Literal { span, kind: tt::LitKind::Byte, .. },
727 )) => {
728 if let Ok(b) = unescape_byte(lit.text()) {
729 bytes.extend(b.escape_ascii().filter_map(|it| char::from_u32(it as u32)));
730 }
731 record_span(span);
732 }
733 TtElement::Leaf(tt::Leaf::Literal(
734 lit @ tt::Literal { span, kind: tt::LitKind::Integer, .. },
735 )) => {
736 record_span(span);
737 if let Ok(b) = lit.text().parse::<u8>() {
738 bytes.extend(b.escape_ascii().filter_map(|it| char::from_u32(it as u32)));
739 }
740 }
741 TtElement::Leaf(tt::Leaf::Punct(punct)) if ti % 2 == 1 && punct.char == ',' => (),
742 _ => {
743 return Err(ExpandError::other(err_span, "unexpected token"));
744 }
745 }
746 }
747 Ok(())
748}
749
750fn relative_file(
751 db: &dyn SourceDatabase,
752 call_id: MacroCallId,
753 path_str: &str,
754 allow_recursion: bool,
755 err_span: Span,
756) -> Result<EditionedFileId, ExpandError> {
757 let lookup = call_id.loc(db);
758 let call_site = lookup.kind.file_id().original_file_respecting_includes(db).file_id(db);
759 let path = AnchoredPath { anchor: call_site, path: path_str };
760 let res: FileId = db
761 .resolve_path(path)
762 .ok_or_else(|| ExpandError::other(err_span, format!("failed to load file `{path_str}`")))?;
763 if res == call_site && !allow_recursion {
765 Err(ExpandError::other(err_span, format!("recursive inclusion of `{path_str}`")))
766 } else {
767 Ok(EditionedFileId::new(db, res, lookup.krate.data(db).edition))
768 }
769}
770
771fn parse_string(tt: &tt::TopSubtree, allow_rest_args: bool) -> Result<(Symbol, Span), ExpandError> {
772 let expect_literal = |span| ExpandError::other(span, "expected string literal");
773 let mut tt = {
774 let mut tt_iter = tt.iter();
775 let extracted =
776 tt_iter.next().ok_or_else(|| expect_literal(tt.top_subtree().delimiter.close))?;
777
778 match tt_iter.next() {
779 None => {}
780 Some(TtElement::Leaf(tt::Leaf::Punct(it))) if it.char == ',' => {
781 if let Some(tt) = tt_iter.next()
784 && !allow_rest_args
785 {
786 return Err(ExpandError::other(tt.first_span(), "unexpected input"));
787 }
788 }
789 Some(tt) => {
790 return Err(ExpandError::other(tt.first_span(), "unexpected input"));
791 }
792 }
793
794 extracted
795 };
796
797 (|| {
798 while let TtElement::Subtree(sub, tt_iter) = &mut tt
802 && let DelimiterKind::Parenthesis | DelimiterKind::Invisible = sub.delimiter.kind
803 {
804 tt =
805 Itertools::exactly_one(tt_iter).map_err(|_| sub.delimiter.open.cover(sub.delimiter.close))?;
807 }
808
809 match tt {
810 TtElement::Leaf(tt::Leaf::Literal(lit @ tt::Literal {
811 span,
812 kind: tt::LitKind::Str,
813 ..
814 })) => Ok((Symbol::intern(&unescape_str(lit.text())), span)),
815 TtElement::Leaf(tt::Leaf::Literal(lit @ tt::Literal {
816 span,
817 kind: tt::LitKind::StrRaw(_),
818 ..
819 })) => Ok((Symbol::intern(lit.text()), span)),
820 TtElement::Leaf(l) => Err(*l.span()),
821 TtElement::Subtree(tt, _) => Err(tt.delimiter.open.cover(tt.delimiter.close)),
822 }
823 })()
824 .map_err(expect_literal)
825}
826
827fn include_expand(
828 db: &dyn SourceDatabase,
829 arg_id: MacroCallId,
830 tt: &tt::TopSubtree,
831 span: Span,
832) -> ExpandResult<tt::TopSubtree> {
833 let editioned_file_id = match include_input_to_file_id(db, arg_id, tt) {
834 Ok(editioned_file_id) => editioned_file_id,
835 Err(e) => {
836 return ExpandResult::new(
837 tt::TopSubtree::empty(DelimSpan { open: span, close: span }),
838 e,
839 );
840 }
841 };
842 ExpandResult::ok(syntax_node_to_token_tree(
844 &editioned_file_id.parse(db).syntax_node(),
845 crate::HirFileId::from(editioned_file_id).span_map(db),
846 span,
847 syntax_bridge::DocCommentDesugarMode::ProcMacro,
848 ))
849}
850
851pub fn include_input_to_file_id(
852 db: &dyn SourceDatabase,
853 arg_id: MacroCallId,
854 arg: &tt::TopSubtree,
855) -> Result<EditionedFileId, ExpandError> {
856 let (s, span) = parse_string(arg, false)?;
857 relative_file(db, arg_id, s.as_str(), false, span)
858}
859
860fn include_bytes_expand(
861 _db: &dyn SourceDatabase,
862 _arg_id: MacroCallId,
863 _tt: &tt::TopSubtree,
864 span: Span,
865) -> ExpandResult<tt::TopSubtree> {
866 let pound = mk_pound(span);
868 let res = quote! {span =>
869 builtin #pound include_bytes
870 };
871 ExpandResult::ok(res)
872}
873
874fn include_str_expand(
875 db: &dyn SourceDatabase,
876 arg_id: MacroCallId,
877 tt: &tt::TopSubtree,
878 call_site: Span,
879) -> ExpandResult<tt::TopSubtree> {
880 let (path, input_span) = match parse_string(tt, false) {
881 Ok(it) => it,
882 Err(e) => {
883 return ExpandResult::new(
884 tt::TopSubtree::empty(DelimSpan { open: call_site, close: call_site }),
885 e,
886 );
887 }
888 };
889
890 let file_id = match relative_file(db, arg_id, path.as_str(), true, input_span) {
895 Ok(file_id) => file_id,
896 Err(_) => {
897 return ExpandResult::ok(quote!(call_site =>""));
898 }
899 };
900
901 let text = db.file_text(file_id.file_id(db));
902 let text = &**text.text(db);
903
904 ExpandResult::ok(quote!(call_site =>#text))
905}
906
907fn get_env_inner(db: &dyn SourceDatabase, arg_id: MacroCallId, key: &Symbol) -> Option<String> {
908 let krate = arg_id.loc(db).krate;
909 krate.env(db).get(key.as_str())
910}
911
912fn env_expand(
913 db: &dyn SourceDatabase,
914 arg_id: MacroCallId,
915 tt: &tt::TopSubtree,
916 span: Span,
917) -> ExpandResult<tt::TopSubtree> {
918 let (key, span) = match parse_string(tt, true) {
919 Ok(it) => it,
920 Err(e) => {
921 return ExpandResult::new(
922 tt::TopSubtree::empty(DelimSpan { open: span, close: span }),
923 e,
924 );
925 }
926 };
927
928 let mut err = None;
929 let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| {
930 if key.as_str() == "OUT_DIR" {
933 err = Some(ExpandError::other(
934 span,
935 r#"`OUT_DIR` not set, build scripts may have failed to run"#,
936 ));
937 }
938
939 "UNRESOLVED_ENV_VAR".to_owned()
944 });
945 let expanded = quote! {span => #s };
946
947 ExpandResult { value: expanded, err }
948}
949
950fn option_env_expand(
951 db: &dyn SourceDatabase,
952 arg_id: MacroCallId,
953 tt: &tt::TopSubtree,
954 call_site: Span,
955) -> ExpandResult<tt::TopSubtree> {
956 let (key, span) = match parse_string(tt, false) {
957 Ok(it) => it,
958 Err(e) => {
959 return ExpandResult::new(
960 tt::TopSubtree::empty(DelimSpan { open: call_site, close: call_site }),
961 e,
962 );
963 }
964 };
965 let dollar_crate = dollar_crate(call_site);
966 let expanded = match get_env_inner(db, arg_id, &key) {
967 None => quote! {call_site => #dollar_crate::option::Option::None::<&str> },
968 Some(s) => {
969 let s = quote! (span => #s);
970 quote! {call_site => #dollar_crate::option::Option::Some(#s) }
971 }
972 };
973
974 ExpandResult::ok(expanded)
975}
976
977fn quote_expand(
978 _db: &dyn SourceDatabase,
979 _arg_id: MacroCallId,
980 _tt: &tt::TopSubtree,
981 span: Span,
982) -> ExpandResult<tt::TopSubtree> {
983 ExpandResult::new(
984 tt::TopSubtree::empty(tt::DelimSpan { open: span, close: span }),
985 ExpandError::other(span, "quote! is not implemented"),
986 )
987}
988
989fn unescape_str(s: &str) -> Cow<'_, str> {
990 if s.contains('\\') {
991 let mut buf = String::with_capacity(s.len());
992 syntax::unescape::unescape_str(s, |_, c| {
993 if let Ok(c) = c {
994 buf.push(c)
995 }
996 });
997 Cow::Owned(buf)
998 } else {
999 Cow::Borrowed(s)
1000 }
1001}
1002
1003fn pattern_type_expand(
1004 _db: &dyn SourceDatabase,
1005 _arg_id: MacroCallId,
1006 tt: &tt::TopSubtree,
1007 call_site: Span,
1008) -> ExpandResult<tt::TopSubtree> {
1009 let mut tt = tt.clone();
1010 tt.set_top_subtree_delimiter_kind(tt::DelimiterKind::Invisible);
1011 let pound = mk_pound(call_site);
1012 ExpandResult::ok(quote! {call_site => builtin #pound pattern_type ( #tt ) })
1013}