3 use base_db
::{AnchoredPath, Edition, FileId}
;
6 use mbe
::{parse_exprs_with_sep, parse_to_token_tree}
;
13 db
::ExpandDatabase
, name
, quote
, tt
, ExpandError
, ExpandResult
, MacroCallId
, MacroCallLoc
,
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
{
23 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24 pub enum EagerExpander
{
28 impl BuiltinFnLikeExpander
{
31 db
: &dyn ExpandDatabase
,
34 ) -> ExpandResult
<tt
::Subtree
> {
35 let expander
= match *self {
36 $
( BuiltinFnLikeExpander
::$kind
=> $expand
, )*
45 db
: &dyn ExpandDatabase
,
48 ) -> ExpandResult
<ExpandedEager
> {
49 let expander
= match *self {
50 $
( EagerExpander
::$e_kind
=> $e_expand
, )*
52 expander(db
, arg_id
, tt
)
56 fn find_by_name(ident
: &name
::Name
) -> Option
<Either
<BuiltinFnLikeExpander
, EagerExpander
>> {
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
)), )*
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
>,
74 fn new(subtree
: tt
::Subtree
) -> Self {
75 ExpandedEager { subtree, included_file: None }
79 pub fn find_builtin_macro(
81 ) -> Option
<Either
<BuiltinFnLikeExpander
, EagerExpander
>> {
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
,
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
120 const DOLLAR_CRATE
: tt
::Ident
=
121 tt
::Ident { text: SmolStr::new_inline("$crate"), span: tt::TokenId::unspecified() }
;
123 fn module_path_expand(
124 _db
: &dyn ExpandDatabase
,
127 ) -> ExpandResult
<tt
::Subtree
> {
128 // Just return a dummy result.
129 ExpandResult
::ok(quote
! { "module::path" }
)
133 _db
: &dyn ExpandDatabase
,
136 ) -> ExpandResult
<tt
::Subtree
> {
137 // dummy implementation for type-checking purposes
139 let expanded
= quote
! {
143 ExpandResult
::ok(expanded
)
146 fn log_syntax_expand(
147 _db
: &dyn ExpandDatabase
,
150 ) -> ExpandResult
<tt
::Subtree
> {
151 ExpandResult
::ok(quote
! {}
)
154 fn trace_macros_expand(
155 _db
: &dyn ExpandDatabase
,
158 ) -> ExpandResult
<tt
::Subtree
> {
159 ExpandResult
::ok(quote
! {}
)
163 _db
: &dyn ExpandDatabase
,
166 ) -> ExpandResult
<tt
::Subtree
> {
167 let pretty
= ::tt
::pretty(&tt
.token_trees
);
169 let expanded
= quote
! {
173 ExpandResult
::ok(expanded
)
177 _db
: &dyn ExpandDatabase
,
180 ) -> ExpandResult
<tt
::Subtree
> {
181 // dummy implementation for type-checking purposes
183 let expanded
= quote
! {
187 ExpandResult
::ok(expanded
)
191 _db
: &dyn ExpandDatabase
,
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
{
202 spacing
: tt
::Spacing
::Alone
,
203 span
: tt
::TokenId
::unspecified(),
206 let cond
= cond
.clone();
207 let panic_args
= itertools
::Itertools
::intersperse(panic_args
.iter().cloned(), comma
);
210 #DOLLAR_CRATE::panic!(##panic_args);
217 ExpandResult
::ok(expanded
)
221 _db
: &dyn ExpandDatabase
,
224 ) -> ExpandResult
<tt
::Subtree
> {
225 // FIXME: RA purposefully lacks knowledge of absolute file names
226 // so just return "".
229 let expanded
= quote
! {
233 ExpandResult
::ok(expanded
)
236 fn format_args_expand(
237 _db
: &dyn ExpandDatabase
,
240 ) -> ExpandResult
<tt
::Subtree
> {
241 // We expand `format_args!("", a1, a2)` to
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),
248 // which is still not really correct, but close enough for now
249 let mut args
= parse_exprs_with_sep(tt
, '
,'
);
252 return ExpandResult
::with_err(
253 tt
::Subtree
::empty(),
254 mbe
::ExpandError
::NoMatchingRule
.into(),
257 for arg
in &mut args
{
259 if matches
!(arg
.token_trees
.get(1), Some(tt
::TokenTree
::Leaf(tt
::Leaf
::Punct(p
))) if p
.char == '
='
)
262 if !matches
!(arg
.token_trees
.get(2), Some(tt
::TokenTree
::Leaf(tt
::Leaf
::Punct(p
))) if p
.char == '
='
)
264 arg
.token_trees
.drain(..2);
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), }
272 let expanded
= quote
! {
273 #DOLLAR_CRATE::fmt::Arguments::new_v1(&[], &[##arg_tts])
275 ExpandResult
::ok(expanded
)
279 _db
: &dyn ExpandDatabase
,
282 ) -> ExpandResult
<tt
::Subtree
> {
283 // We expand all assembly snippets to `format_args!` invocations to get format syntax
284 // highlighting for them.
286 let mut literals
= Vec
::new();
287 for tt
in tt
.token_trees
.chunks(2) {
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: _ }
))] =>
292 let krate
= DOLLAR_CRATE
.clone();
293 literals
.push(quote
!(#krate::format_args!(#lit);));
299 let expanded
= quote
! {{
303 ExpandResult
::ok(expanded
)
306 fn global_asm_expand(
307 _db
: &dyn ExpandDatabase
,
310 ) -> ExpandResult
<tt
::Subtree
> {
311 // Expand to nothing (at item-level)
312 ExpandResult
::ok(quote
! {}
)
316 db
: &dyn ExpandDatabase
,
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
)
328 db
: &dyn ExpandDatabase
,
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!)
337 quote
!(#DOLLAR_CRATE::panic::panic_2015!)
340 // Pass the original arguments
341 call
.token_trees
.push(tt
::TokenTree
::Subtree(tt
.clone()));
342 ExpandResult
::ok(call
)
345 fn unreachable_expand(
346 db
: &dyn ExpandDatabase
,
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!)
355 quote
!(#DOLLAR_CRATE::panic::unreachable_2015!)
358 // Pass the original arguments
359 call
.token_trees
.push(tt
::TokenTree
::Subtree(tt
.clone()));
360 ExpandResult
::ok(call
)
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())
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
)?
;
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())
381 fn compile_error_expand(
382 _db
: &dyn ExpandDatabase
,
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()),
391 _
=> ExpandError
::Other("`compile_error!` argument must be a string".into()),
394 ExpandResult { value: ExpandedEager::new(quote! {}
), err
: Some(err
) }
398 _db
: &dyn ExpandDatabase
,
399 _arg_id
: MacroCallId
,
401 ) -> ExpandResult
<ExpandedEager
> {
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
{
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
421 if let Some(c
) = unquote_char(it
) {
424 let component
= unquote_str(it
).unwrap_or_else(|| it
.text
.to_string());
425 text
.push_str(&component
);
428 // handle boolean literals
429 tt
::TokenTree
::Leaf(tt
::Leaf
::Ident(id
))
430 if i
% 2 == 0 && (id
.text
== "true" || id
.text
== "false") =>
432 text
.push_str(id
.text
.as_str());
434 tt
::TokenTree
::Leaf(tt
::Leaf
::Punct(punct
)) if i
% 2 == 1 && punct
.char == '
,'
=> (),
436 err
.get_or_insert(mbe
::ExpandError
::UnexpectedToken
.into());
440 ExpandResult { value: ExpandedEager::new(quote!(#text)), err }
443 fn concat_bytes_expand(
444 _db
: &dyn ExpandDatabase
,
445 _arg_id
: MacroCallId
,
447 ) -> ExpandResult
<ExpandedEager
> {
448 let mut bytes
= Vec
::new();
450 for (i
, t
) in tt
.token_trees
.iter().enumerate() {
452 tt
::TokenTree
::Leaf(tt
::Leaf
::Literal(lit
)) => {
453 let token
= ast
::make
::tokens
::literal(&lit
.to_string());
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()));
461 err
.get_or_insert(mbe
::ExpandError
::UnexpectedToken
.into());
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
);
474 err
.get_or_insert(mbe
::ExpandError
::UnexpectedToken
.into());
479 let ident
= tt
::Ident { text: bytes.join(", ").into(), span: tt::TokenId::unspecified() }
;
480 ExpandResult { value: ExpandedEager::new(quote!([#ident])), err }
483 fn concat_bytes_expand_subtree(
485 bytes
: &mut Vec
<String
>,
486 ) -> Result
<(), ExpandError
> {
487 for (ti
, tt
) in tree
.token_trees
.iter().enumerate() {
489 tt
::TokenTree
::Leaf(tt
::Leaf
::Literal(lit
)) => {
490 let lit
= ast
::make
::tokens
::literal(&lit
.to_string());
492 syntax
::SyntaxKind
::BYTE
| syntax
::SyntaxKind
::INT_NUMBER
=> {
493 bytes
.push(lit
.text().to_string())
496 return Err(mbe
::ExpandError
::UnexpectedToken
.into());
500 tt
::TokenTree
::Leaf(tt
::Leaf
::Punct(punct
)) if ti
% 2 == 1 && punct
.char == '
,'
=> (),
502 return Err(mbe
::ExpandError
::UnexpectedToken
.into());
509 fn concat_idents_expand(
510 _db
: &dyn ExpandDatabase
,
511 _arg_id
: MacroCallId
,
513 ) -> ExpandResult
<ExpandedEager
> {
515 let mut ident
= String
::new();
516 for (i
, t
) in tt
.token_trees
.iter().enumerate() {
518 tt
::TokenTree
::Leaf(tt
::Leaf
::Ident(id
)) => {
519 ident
.push_str(id
.text
.as_str());
521 tt
::TokenTree
::Leaf(tt
::Leaf
::Punct(punct
)) if i
% 2 == 1 && punct
.char == '
,'
=> (),
523 err
.get_or_insert(mbe
::ExpandError
::UnexpectedToken
.into());
527 let ident
= tt
::Ident { text: ident.into(), span: tt::TokenId::unspecified() }
;
528 ExpandResult { value: ExpandedEager::new(quote!(#ident)), err }
532 db
: &dyn ExpandDatabase
,
533 call_id
: MacroCallId
,
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 }
;
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()))
550 fn parse_string(tt
: &tt
::Subtree
) -> Result
<String
, ExpandError
> {
553 .and_then(|tt
| match tt
{
554 tt
::TokenTree
::Leaf(tt
::Leaf
::Literal(it
)) => unquote_str(it
),
557 .ok_or(mbe
::ExpandError
::ConversionError
.into())
561 db
: &dyn ExpandDatabase
,
564 ) -> ExpandResult
<ExpandedEager
> {
566 let path
= parse_string(tt
)?
;
567 let file_id
= relative_file(db
, arg_id
, &path
, false)?
;
570 parse_to_token_tree(&db
.file_text(file_id
)).ok_or(mbe
::ExpandError
::ConversionError
)?
.0;
571 Ok((subtree
, file_id
))
575 Ok((subtree
, file_id
)) => {
576 ExpandResult
::ok(ExpandedEager { subtree, included_file: Some(file_id) }
)
578 Err(e
) => ExpandResult
::with_err(
579 ExpandedEager { subtree: tt::Subtree::empty(), included_file: None }
,
585 fn include_bytes_expand(
586 _db
: &dyn ExpandDatabase
,
587 _arg_id
: MacroCallId
,
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 }
,
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(),
605 ExpandResult
::ok(ExpandedEager
::new(res
))
608 fn include_str_expand(
609 db
: &dyn ExpandDatabase
,
612 ) -> ExpandResult
<ExpandedEager
> {
613 let path
= match parse_string(tt
) {
616 return ExpandResult
::with_err(
617 ExpandedEager { subtree: tt::Subtree::empty(), included_file: None }
,
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
627 let file_id
= match relative_file(db
, arg_id
, &path
, true) {
628 Ok(file_id
) => file_id
,
630 return ExpandResult
::ok(ExpandedEager
::new(quote
!("")));
634 let text
= db
.file_text(file_id
);
637 ExpandResult
::ok(ExpandedEager
::new(quote
!(#text)))
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
)
646 db
: &dyn ExpandDatabase
,
649 ) -> ExpandResult
<ExpandedEager
> {
650 let key
= match parse_string(tt
) {
653 return ExpandResult
::with_err(
654 ExpandedEager { subtree: tt::Subtree::empty(), included_file: 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(),
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()
676 let expanded
= quote
! { #s }
;
678 ExpandResult { value: ExpandedEager::new(expanded), err }
681 fn option_env_expand(
682 db
: &dyn ExpandDatabase
,
685 ) -> ExpandResult
<ExpandedEager
> {
686 let key
= match parse_string(tt
) {
689 return ExpandResult
::with_err(
690 ExpandedEager { subtree: tt::Subtree::empty(), included_file: None }
,
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) }
,
701 ExpandResult
::ok(ExpandedEager
::new(expanded
))