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