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