3 use base_db
::{AnchoredPath, FileId}
;
6 use itertools
::Itertools
;
7 use mbe
::{parse_exprs_with_sep, parse_to_token_tree}
;
8 use span
::{Edition, Span, SpanAnchor, SyntaxContextId, ROOT_ERASED_FILE_AST_ID}
;
9 use syntax
::ast
::{self, AstToken}
;
13 hygiene
::{span_with_call_site_ctxt, span_with_def_site_ctxt}
,
17 tt
::{self, DelimSpan}
,
18 ExpandError
, ExpandResult
, HirFileIdExt
, MacroCallId
, MacroFileIdExt
,
21 macro_rules
! register_builtin
{
22 ( $LAZY
:ident
: $
(($name
:ident
, $kind
: ident
) => $expand
:ident
),* , $EAGER
:ident
: $
(($e_name
:ident
, $e_kind
: ident
) => $e_expand
:ident
),* ) => {
23 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33 impl BuiltinFnLikeExpander
{
34 pub fn expander(&self) -> fn (&dyn ExpandDatabase
, MacroCallId
, &tt
::Subtree
, Span
) -> ExpandResult
<tt
::Subtree
> {
36 $
( BuiltinFnLikeExpander
::$kind
=> $expand
, )*
42 pub fn expander(&self) -> fn (&dyn ExpandDatabase
, MacroCallId
, &tt
::Subtree
, Span
) -> ExpandResult
<tt
::Subtree
> {
44 $
( EagerExpander
::$e_kind
=> $e_expand
, )*
49 fn find_by_name(ident
: &name
::Name
) -> Option
<Either
<BuiltinFnLikeExpander
, EagerExpander
>> {
51 $
( id
if id
== &name
::name
![$name
] => Some(Either
::Left(BuiltinFnLikeExpander
::$kind
)), )*
52 $
( id
if id
== &name
::name
![$e_name
] => Some(Either
::Right(EagerExpander
::$e_kind
)), )*
59 impl BuiltinFnLikeExpander
{
62 db
: &dyn ExpandDatabase
,
66 ) -> ExpandResult
<tt
::Subtree
> {
67 let span
= span_with_def_site_ctxt(db
, span
, id
);
68 self.expander()(db
, id
, tt
, span
)
75 db
: &dyn ExpandDatabase
,
79 ) -> ExpandResult
<tt
::Subtree
> {
80 let span
= span_with_def_site_ctxt(db
, span
, id
);
81 self.expander()(db
, id
, tt
, span
)
84 pub fn is_include(&self) -> bool
{
85 matches
!(self, EagerExpander
::Include
)
88 pub fn is_include_like(&self) -> bool
{
91 EagerExpander
::Include
| EagerExpander
::IncludeStr
| EagerExpander
::IncludeBytes
95 pub fn is_env_or_option_env(&self) -> bool
{
96 matches
!(self, EagerExpander
::Env
| EagerExpander
::OptionEnv
)
100 pub fn find_builtin_macro(
102 ) -> Option
<Either
<BuiltinFnLikeExpander
, EagerExpander
>> {
107 BuiltinFnLikeExpander
:
108 (column
, Column
) => line_expand
,
109 (file
, File
) => file_expand
,
110 (line
, Line
) => line_expand
,
111 (module_path
, ModulePath
) => module_path_expand
,
112 (assert
, Assert
) => assert_expand
,
113 (stringify
, Stringify
) => stringify_expand
,
114 (llvm_asm
, LlvmAsm
) => asm_expand
,
115 (asm
, Asm
) => asm_expand
,
116 (global_asm
, GlobalAsm
) => global_asm_expand
,
117 (cfg
, Cfg
) => cfg_expand
,
118 (core_panic
, CorePanic
) => panic_expand
,
119 (std_panic
, StdPanic
) => panic_expand
,
120 (unreachable
, Unreachable
) => unreachable_expand
,
121 (log_syntax
, LogSyntax
) => log_syntax_expand
,
122 (trace_macros
, TraceMacros
) => trace_macros_expand
,
123 (format_args
, FormatArgs
) => format_args_expand
,
124 (const_format_args
, ConstFormatArgs
) => format_args_expand
,
125 (format_args_nl
, FormatArgsNl
) => format_args_nl_expand
,
126 (quote
, Quote
) => quote_expand
,
129 (compile_error
, CompileError
) => compile_error_expand
,
130 (concat
, Concat
) => concat_expand
,
131 (concat_idents
, ConcatIdents
) => concat_idents_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
140 fn mk_pound(span
: Span
) -> tt
::Subtree
{
141 crate::quote
::IntoTt
::to_subtree(
142 vec
![crate::tt
::Leaf
::Punct(crate::tt
::Punct
{
144 spacing
: crate::tt
::Spacing
::Alone
,
152 fn module_path_expand(
153 _db
: &dyn ExpandDatabase
,
157 ) -> ExpandResult
<tt
::Subtree
> {
158 // Just return a dummy result.
159 ExpandResult
::ok(quote
! {span
=>
165 _db
: &dyn ExpandDatabase
,
169 ) -> ExpandResult
<tt
::Subtree
> {
170 // dummy implementation for type-checking purposes
171 // Note that `line!` and `column!` will never be implemented properly, as they are by definition
173 ExpandResult
::ok(tt
::Subtree
{
174 delimiter
: tt
::Delimiter
::invisible_spanned(span
),
175 token_trees
: Box
::new([tt
::TokenTree
::Leaf(tt
::Leaf
::Literal(tt
::Literal
{
182 fn log_syntax_expand(
183 _db
: &dyn ExpandDatabase
,
187 ) -> ExpandResult
<tt
::Subtree
> {
188 ExpandResult
::ok(quote
! {span =>}
)
191 fn trace_macros_expand(
192 _db
: &dyn ExpandDatabase
,
196 ) -> ExpandResult
<tt
::Subtree
> {
197 ExpandResult
::ok(quote
! {span =>}
)
201 _db
: &dyn ExpandDatabase
,
205 ) -> ExpandResult
<tt
::Subtree
> {
206 let pretty
= ::tt
::pretty(&tt
.token_trees
);
208 let expanded
= quote
! {span
=>
212 ExpandResult
::ok(expanded
)
216 db
: &dyn ExpandDatabase
,
220 ) -> ExpandResult
<tt
::Subtree
> {
221 let call_site_span
= span_with_call_site_ctxt(db
, span
, id
);
222 let args
= parse_exprs_with_sep(tt
, '
,'
, call_site_span
, Edition
::CURRENT
);
223 let dollar_crate
= dollar_crate(span
);
224 let expanded
= match &*args
{
225 [cond
, panic_args @
..] => {
226 let comma
= tt
::Subtree
{
227 delimiter
: tt
::Delimiter
::invisible_spanned(call_site_span
),
228 token_trees
: Box
::new([tt
::TokenTree
::Leaf(tt
::Leaf
::Punct(tt
::Punct
{
230 spacing
: tt
::Spacing
::Alone
,
231 span
: call_site_span
,
234 let cond
= cond
.clone();
235 let panic_args
= itertools
::Itertools
::intersperse(panic_args
.iter().cloned(), comma
);
236 let mac
= if use_panic_2021(db
, span
) {
237 quote
! {call_site_span => #dollar_crate::panic::panic_2021!(##panic_args) }
239 quote
! {call_site_span => #dollar_crate::panic!(##panic_args) }
241 quote
! {call_site_span
=>{
247 [] => quote
! {call_site_span =>{}
},
250 ExpandResult
::ok(expanded
)
254 _db
: &dyn ExpandDatabase
,
258 ) -> ExpandResult
<tt
::Subtree
> {
259 // FIXME: RA purposefully lacks knowledge of absolute file names
260 // so just return "".
263 let expanded
= quote
! {span
=>
267 ExpandResult
::ok(expanded
)
270 fn format_args_expand(
271 db
: &dyn ExpandDatabase
,
275 ) -> ExpandResult
<tt
::Subtree
> {
276 format_args_expand_general(db
, id
, tt
, "", span
)
279 fn format_args_nl_expand(
280 db
: &dyn ExpandDatabase
,
284 ) -> ExpandResult
<tt
::Subtree
> {
285 format_args_expand_general(db
, id
, tt
, "\\n", span
)
288 fn format_args_expand_general(
289 _db
: &dyn ExpandDatabase
,
292 // FIXME: Make use of this so that mir interpretation works properly
295 ) -> ExpandResult
<tt
::Subtree
> {
296 let pound
= mk_pound(span
);
297 let mut tt
= tt
.clone();
298 tt
.delimiter
.kind
= tt
::DelimiterKind
::Parenthesis
;
299 ExpandResult
::ok(quote
! {span
=>
300 builtin
#pound format_args #tt
305 _db
: &dyn ExpandDatabase
,
309 ) -> ExpandResult
<tt
::Subtree
> {
310 // We expand all assembly snippets to `format_args!` invocations to get format syntax
311 // highlighting for them.
312 let mut literals
= Vec
::new();
313 for tt
in tt
.token_trees
.chunks(2) {
315 [tt
::TokenTree
::Leaf(tt
::Leaf
::Literal(lit
))]
316 | [tt
::TokenTree
::Leaf(tt
::Leaf
::Literal(lit
)), tt
::TokenTree
::Leaf(tt
::Leaf
::Punct(tt
::Punct { char: ',', span: _, spacing: _ }
))] =>
318 let dollar_krate
= dollar_crate(span
);
319 literals
.push(quote
!(span
=>#dollar_krate::format_args!(#lit);));
325 let pound
= mk_pound(span
);
326 let expanded
= quote
! {span
=>
331 ExpandResult
::ok(expanded
)
334 fn global_asm_expand(
335 _db
: &dyn ExpandDatabase
,
339 ) -> ExpandResult
<tt
::Subtree
> {
340 // Expand to nothing (at item-level)
341 ExpandResult
::ok(quote
! {span =>}
)
345 db
: &dyn ExpandDatabase
,
349 ) -> ExpandResult
<tt
::Subtree
> {
350 let loc
= db
.lookup_intern_macro_call(id
);
351 let expr
= CfgExpr
::parse(tt
);
352 let enabled
= db
.crate_graph()[loc
.krate
].cfg_options
.check(&expr
) != Some(false);
353 let expanded
= if enabled { quote!(span=>true) }
else { quote!(span=>false) }
;
354 ExpandResult
::ok(expanded
)
358 db
: &dyn ExpandDatabase
,
362 ) -> ExpandResult
<tt
::Subtree
> {
363 let dollar_crate
= dollar_crate(span
);
364 let call_site_span
= span_with_call_site_ctxt(db
, span
, id
);
367 if use_panic_2021(db
, call_site_span
) { known::panic_2021 }
else { known::panic_2015 }
;
369 // Expand to a macro call `$crate::panic::panic_{edition}`
370 let mut call
= quote
!(call_site_span
=>#dollar_crate::panic::#mac!);
372 // Pass the original arguments
373 let mut subtree
= tt
.clone();
374 subtree
.delimiter
= tt
::Delimiter
{
375 open
: call_site_span
,
376 close
: call_site_span
,
377 kind
: tt
::DelimiterKind
::Parenthesis
,
380 // FIXME(slow): quote! have a way to expand to builder to make this a vec!
381 call
.push(tt
::TokenTree
::Subtree(subtree
));
383 ExpandResult
::ok(call
)
386 fn unreachable_expand(
387 db
: &dyn ExpandDatabase
,
391 ) -> ExpandResult
<tt
::Subtree
> {
392 let dollar_crate
= dollar_crate(span
);
393 let call_site_span
= span_with_call_site_ctxt(db
, span
, id
);
395 let mac
= if use_panic_2021(db
, call_site_span
) {
396 known
::unreachable_2021
398 known
::unreachable_2015
401 // Expand to a macro call `$crate::panic::panic_{edition}`
402 let mut call
= quote
!(call_site_span
=>#dollar_crate::panic::#mac!);
404 // Pass the original arguments
405 let mut subtree
= tt
.clone();
406 subtree
.delimiter
= tt
::Delimiter
{
407 open
: call_site_span
,
408 close
: call_site_span
,
409 kind
: tt
::DelimiterKind
::Parenthesis
,
412 // FIXME(slow): quote! have a way to expand to builder to make this a vec!
413 call
.push(tt
::TokenTree
::Subtree(subtree
));
415 ExpandResult
::ok(call
)
418 #[allow(clippy::never_loop)]
419 fn use_panic_2021(db
: &dyn ExpandDatabase
, span
: Span
) -> bool
{
420 // To determine the edition, we check the first span up the expansion
421 // stack that does not have #[allow_internal_unstable(edition_panic)].
422 // (To avoid using the edition of e.g. the assert!() or debug_assert!() definition.)
424 let Some(expn
) = db
.lookup_intern_syntax_context(span
.ctx
).outer_expn
else {
427 let expn
= db
.lookup_intern_macro_call(expn
);
428 // FIXME: Record allow_internal_unstable in the macro def (not been done yet because it
429 // would consume quite a bit extra memory for all call locs...)
430 // if let Some(features) = expn.def.allow_internal_unstable {
431 // if features.iter().any(|&f| f == sym::edition_panic) {
432 // span = expn.call_site;
436 break expn
.def
.edition
>= Edition
::Edition2021
;
440 fn unquote_str(lit
: &tt
::Literal
) -> Option
<(String
, Span
)> {
442 let lit
= ast
::make
::tokens
::literal(&lit
.to_string());
443 let token
= ast
::String
::cast(lit
)?
;
444 token
.value().ok().map(|it
| (it
.into_owned(), span
))
447 fn unquote_char(lit
: &tt
::Literal
) -> Option
<(char, Span
)> {
449 let lit
= ast
::make
::tokens
::literal(&lit
.to_string());
450 let token
= ast
::Char
::cast(lit
)?
;
451 token
.value().ok().zip(Some(span
))
454 fn unquote_byte_string(lit
: &tt
::Literal
) -> Option
<(Vec
<u8>, Span
)> {
456 let lit
= ast
::make
::tokens
::literal(&lit
.to_string());
457 let token
= ast
::ByteString
::cast(lit
)?
;
458 token
.value().ok().map(|it
| (it
.into_owned(), span
))
461 fn compile_error_expand(
462 _db
: &dyn ExpandDatabase
,
466 ) -> ExpandResult
<tt
::Subtree
> {
467 let err
= match &*tt
.token_trees
{
468 [tt
::TokenTree
::Leaf(tt
::Leaf
::Literal(it
))] => match unquote_str(it
) {
469 Some((unquoted
, _
)) => ExpandError
::other(unquoted
.into_boxed_str()),
470 None
=> ExpandError
::other("`compile_error!` argument must be a string"),
472 _
=> ExpandError
::other("`compile_error!` argument must be a string"),
475 ExpandResult { value: quote! {span =>}
, err
: Some(err
) }
479 _db
: &dyn ExpandDatabase
,
480 _arg_id
: MacroCallId
,
483 ) -> ExpandResult
<tt
::Subtree
> {
485 let mut text
= String
::new();
486 let mut span
: Option
<Span
> = None
;
487 let mut record_span
= |s
: Span
| match &mut span
{
488 Some(span
) if span
.anchor
== s
.anchor
=> span
.range
= span
.range
.cover(s
.range
),
490 None
=> span
= Some(s
),
492 for (i
, mut t
) in tt
.token_trees
.iter().enumerate() {
493 // FIXME: hack on top of a hack: `$e:expr` captures get surrounded in parentheses
494 // to ensure the right parsing order, so skip the parentheses here. Ideally we'd
495 // implement rustc's model. cc https://github.com/rust-lang/rust-analyzer/pull/10623
496 if let tt
::TokenTree
::Subtree(tt
::Subtree { delimiter: delim, token_trees }
) = t
{
497 if let [tt
] = &**token_trees
{
498 if delim
.kind
== tt
::DelimiterKind
::Parenthesis
{
505 tt
::TokenTree
::Leaf(tt
::Leaf
::Literal(it
)) if i
% 2 == 0 => {
506 // concat works with string and char literals, so remove any quotes.
507 // It also works with integer, float and boolean literals, so just use the rest
509 if let Some((c
, span
)) = unquote_char(it
) {
513 let (component
, span
) =
514 unquote_str(it
).unwrap_or_else(|| (it
.text
.to_string(), it
.span
));
515 text
.push_str(&component
);
519 // handle boolean literals
520 tt
::TokenTree
::Leaf(tt
::Leaf
::Ident(id
))
521 if i
% 2 == 0 && (id
.text
== "true" || id
.text
== "false") =>
523 text
.push_str(id
.text
.as_str());
524 record_span(id
.span
);
526 tt
::TokenTree
::Leaf(tt
::Leaf
::Punct(punct
)) if i
% 2 == 1 && punct
.char == '
,'
=> (),
528 err
.get_or_insert(mbe
::ExpandError
::UnexpectedToken
.into());
532 let span
= span
.unwrap_or(tt
.delimiter
.open
);
533 ExpandResult { value: quote!(span =>#text), err }
536 fn concat_bytes_expand(
537 _db
: &dyn ExpandDatabase
,
538 _arg_id
: MacroCallId
,
541 ) -> ExpandResult
<tt
::Subtree
> {
542 let mut bytes
= Vec
::new();
544 let mut span
: Option
<Span
> = None
;
545 let mut record_span
= |s
: Span
| match &mut span
{
546 Some(span
) if span
.anchor
== s
.anchor
=> span
.range
= span
.range
.cover(s
.range
),
548 None
=> span
= Some(s
),
550 for (i
, t
) in tt
.token_trees
.iter().enumerate() {
552 tt
::TokenTree
::Leaf(tt
::Leaf
::Literal(lit
)) => {
553 let token
= ast
::make
::tokens
::literal(&lit
.to_string());
554 record_span(lit
.span
);
556 syntax
::SyntaxKind
::BYTE
=> bytes
.push(token
.text().to_owned()),
557 syntax
::SyntaxKind
::BYTE_STRING
=> {
558 let components
= unquote_byte_string(lit
).map_or(vec
![], |(it
, _
)| it
);
559 components
.into_iter().for_each(|it
| bytes
.push(it
.to_string()));
562 err
.get_or_insert(mbe
::ExpandError
::UnexpectedToken
.into());
567 tt
::TokenTree
::Leaf(tt
::Leaf
::Punct(punct
)) if i
% 2 == 1 && punct
.char == '
,'
=> (),
568 tt
::TokenTree
::Subtree(tree
) if tree
.delimiter
.kind
== tt
::DelimiterKind
::Bracket
=> {
569 if let Err(e
) = concat_bytes_expand_subtree(tree
, &mut bytes
, &mut record_span
) {
570 err
.get_or_insert(e
);
575 err
.get_or_insert(mbe
::ExpandError
::UnexpectedToken
.into());
580 let value
= tt
::Subtree
{
581 delimiter
: tt
::Delimiter
{
584 kind
: tt
::DelimiterKind
::Bracket
,
587 Itertools
::intersperse_with(
588 bytes
.into_iter().map(|it
| {
589 tt
::TokenTree
::Leaf(tt
::Leaf
::Literal(tt
::Literal
{
591 span
: span
.unwrap_or(call_site
),
595 tt
::TokenTree
::Leaf(tt
::Leaf
::Punct(tt
::Punct
{
597 spacing
: tt
::Spacing
::Alone
,
605 ExpandResult { value, err }
608 fn concat_bytes_expand_subtree(
610 bytes
: &mut Vec
<String
>,
611 mut record_span
: impl FnMut(Span
),
612 ) -> Result
<(), ExpandError
> {
613 for (ti
, tt
) in tree
.token_trees
.iter().enumerate() {
615 tt
::TokenTree
::Leaf(tt
::Leaf
::Literal(it
)) => {
616 let lit
= ast
::make
::tokens
::literal(&it
.to_string());
618 syntax
::SyntaxKind
::BYTE
| syntax
::SyntaxKind
::INT_NUMBER
=> {
619 record_span(it
.span
);
620 bytes
.push(lit
.text().to_owned())
623 return Err(mbe
::ExpandError
::UnexpectedToken
.into());
627 tt
::TokenTree
::Leaf(tt
::Leaf
::Punct(punct
)) if ti
% 2 == 1 && punct
.char == '
,'
=> (),
629 return Err(mbe
::ExpandError
::UnexpectedToken
.into());
636 fn concat_idents_expand(
637 _db
: &dyn ExpandDatabase
,
638 _arg_id
: MacroCallId
,
641 ) -> ExpandResult
<tt
::Subtree
> {
643 let mut ident
= String
::new();
644 for (i
, t
) in tt
.token_trees
.iter().enumerate() {
646 tt
::TokenTree
::Leaf(tt
::Leaf
::Ident(id
)) => {
647 ident
.push_str(id
.text
.as_str());
649 tt
::TokenTree
::Leaf(tt
::Leaf
::Punct(punct
)) if i
% 2 == 1 && punct
.char == '
,'
=> (),
651 err
.get_or_insert(mbe
::ExpandError
::UnexpectedToken
.into());
656 let ident
= tt
::Ident { text: ident.into(), span }
;
657 ExpandResult { value: quote!(span =>#ident), err }
661 db
: &dyn ExpandDatabase
,
662 call_id
: MacroCallId
,
664 allow_recursion
: bool
,
665 ) -> Result
<FileId
, ExpandError
> {
666 let call_site
= call_id
.as_macro_file().parent(db
).original_file_respecting_includes(db
);
667 let path
= AnchoredPath { anchor: call_site, path: path_str }
;
670 .ok_or_else(|| ExpandError
::other(format
!("failed to load file `{path_str}`")))?
;
671 // Prevent include itself
672 if res
== call_site
&& !allow_recursion
{
673 Err(ExpandError
::other(format
!("recursive inclusion of `{path_str}`")))
679 fn parse_string(tt
: &tt
::Subtree
) -> Result
<(String
, Span
), ExpandError
> {
682 .and_then(|tt
| match tt
{
683 tt
::TokenTree
::Leaf(tt
::Leaf
::Literal(it
)) => unquote_str(it
),
686 .ok_or(mbe
::ExpandError
::ConversionError
.into())
690 db
: &dyn ExpandDatabase
,
694 ) -> ExpandResult
<tt
::Subtree
> {
695 let file_id
= match include_input_to_file_id(db
, arg_id
, tt
) {
698 return ExpandResult
::new(tt
::Subtree
::empty(DelimSpan { open: span, close: span }
), e
)
701 match parse_to_token_tree(
702 SpanAnchor { file_id, ast_id: ROOT_ERASED_FILE_AST_ID }
,
703 SyntaxContextId
::ROOT
,
704 &db
.file_text(file_id
),
706 Some(it
) => ExpandResult
::ok(it
),
707 None
=> ExpandResult
::new(
708 tt
::Subtree
::empty(DelimSpan { open: span, close: span }
),
709 ExpandError
::other("failed to parse included file"),
714 pub fn include_input_to_file_id(
715 db
: &dyn ExpandDatabase
,
718 ) -> Result
<FileId
, ExpandError
> {
719 relative_file(db
, arg_id
, &parse_string(arg
)?
.0, false)
722 fn include_bytes_expand(
723 _db
: &dyn ExpandDatabase
,
724 _arg_id
: MacroCallId
,
727 ) -> ExpandResult
<tt
::Subtree
> {
728 // FIXME: actually read the file here if the user asked for macro expansion
729 let res
= tt
::Subtree
{
730 delimiter
: tt
::Delimiter
::invisible_spanned(span
),
731 token_trees
: Box
::new([tt
::TokenTree
::Leaf(tt
::Leaf
::Literal(tt
::Literal
{
732 text
: r
#"b"""#.into(),
736 ExpandResult
::ok(res
)
739 fn include_str_expand(
740 db
: &dyn ExpandDatabase
,
744 ) -> ExpandResult
<tt
::Subtree
> {
745 let (path
, span
) = match parse_string(tt
) {
748 return ExpandResult
::new(tt
::Subtree
::empty(DelimSpan { open: span, close: span }
), e
)
752 // FIXME: we're not able to read excluded files (which is most of them because
753 // it's unusual to `include_str!` a Rust file), but we can return an empty string.
754 // Ideally, we'd be able to offer a precise expansion if the user asks for macro
756 let file_id
= match relative_file(db
, arg_id
, &path
, true) {
757 Ok(file_id
) => file_id
,
759 return ExpandResult
::ok(quote
!(span
=>""));
763 let text
= db
.file_text(file_id
);
766 ExpandResult
::ok(quote
!(span
=>#text))
769 fn get_env_inner(db
: &dyn ExpandDatabase
, arg_id
: MacroCallId
, key
: &str) -> Option
<String
> {
770 let krate
= db
.lookup_intern_macro_call(arg_id
).krate
;
771 db
.crate_graph()[krate
].env
.get(key
)
775 db
: &dyn ExpandDatabase
,
779 ) -> ExpandResult
<tt
::Subtree
> {
780 let (key
, span
) = match parse_string(tt
) {
783 return ExpandResult
::new(tt
::Subtree
::empty(DelimSpan { open: span, close: span }
), e
)
788 let s
= get_env_inner(db
, arg_id
, &key
).unwrap_or_else(|| {
789 // The only variable rust-analyzer ever sets is `OUT_DIR`, so only diagnose that to avoid
790 // unnecessary diagnostics for eg. `CARGO_PKG_NAME`.
791 if key
== "OUT_DIR" {
792 err
= Some(ExpandError
::other(r
#"`OUT_DIR` not set, enable "build scripts" to fix"#));
795 // If the variable is unset, still return a dummy string to help type inference along.
796 // We cannot use an empty string here, because for
797 // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become
798 // `include!("foo.rs"), which might go to infinite loop
799 "UNRESOLVED_ENV_VAR".to_owned()
801 let expanded
= quote
! {span => #s }
;
803 ExpandResult { value: expanded, err }
806 fn option_env_expand(
807 db
: &dyn ExpandDatabase
,
811 ) -> ExpandResult
<tt
::Subtree
> {
812 let (key
, span
) = match parse_string(tt
) {
815 return ExpandResult
::new(
816 tt
::Subtree
::empty(DelimSpan { open: call_site, close: call_site }
),
821 let dollar_crate
= dollar_crate(call_site
);
822 let expanded
= match get_env_inner(db
, arg_id
, &key
) {
823 None
=> quote
! {call_site => #dollar_crate::option::Option::None::<&str> }
,
825 let s
= quote
! (span
=> #s);
826 quote
! {call_site => #dollar_crate::option::Option::Some(#s) }
830 ExpandResult
::ok(expanded
)
834 _db
: &dyn ExpandDatabase
,
835 _arg_id
: MacroCallId
,
838 ) -> ExpandResult
<tt
::Subtree
> {
840 tt
::Subtree
::empty(tt
::DelimSpan { open: span, close: span }
),
841 ExpandError
::other("quote! is not implemented"),