Auto merge of #13810 - tfpk:tfpk/macro-inline, r=Veykril

Add action to expand a declarative macro once, inline. Fixes #13598

This commit adds a new r-a method, `expandMacroInline`, which expands the macro that's currently selected. See  #13598 for the most applicable issue; though I suspect it'll resolve part of #5949 and make #11888 significantly easier).

The macro works like this:

![rust-analyser-feature](https://user-images.githubusercontent.com/10906982/208813167-3123e379-8fd5-4206-a4f4-5af1129565f9.gif)

I have 2 questions before this PR can be merged:

1. **Should we rustfmt the output?** The advantage of doing this is neater code. The disadvantages are we'd have to format the whole expr/stmt/block (since there's no point just formatting one part, especially over multiple lines), and maybe it moves the code around more in weird ways. My suggestion here is to start off by not doing any formatting; and if it appears useful we can decide to do formatting in a later release.
2.   **Is it worth solving the `$crate` hygiene issue now?** -- I think this PR is usable as of right now for some use-cases; but it is annoying that many common macros (i.e. `println!()`, `format!()`) can't be expanded further unless the user guesses the correct `$crate` value. The trouble with solving that issue is that I think it's complicated and imperfect. If we do solve it; we'd also need to either change the existing `expandMacro`/`expandMacroInline` commands; provide some option to allow/disallow `$crate` expanding; or come to some other compromise.
This commit is contained in:
bors 2023-01-09 14:24:41 +00:00
commit 336608aa92
3 changed files with 268 additions and 0 deletions

View file

@ -0,0 +1,233 @@
use syntax::ast::{self, AstNode};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: inline_macro
//
// Takes a macro and inlines it one step.
//
// ```
// macro_rules! num {
// (+$($t:tt)+) => (1 + num!($($t )+));
// (-$($t:tt)+) => (-1 + num!($($t )+));
// (+) => (1);
// (-) => (-1);
// }
//
// fn main() {
// let number = num$0!(+ + + - + +);
// println!("{number}");
// }
// ```
// ->
// ```
// macro_rules! num {
// (+$($t:tt)+) => (1 + num!($($t )+));
// (-$($t:tt)+) => (-1 + num!($($t )+));
// (+) => (1);
// (-) => (-1);
// }
//
// fn main() {
// let number = 1+num!(+ + - + +);
// println!("{number}");
// }
// ```
pub(crate) fn inline_macro(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let unexpanded = ctx.find_node_at_offset::<ast::MacroCall>()?;
let expanded = ctx.sema.expand(&unexpanded)?.clone_for_update();
let text_range = unexpanded.syntax().text_range();
acc.add(
AssistId("inline_macro", AssistKind::RefactorRewrite),
format!("Inline macro"),
text_range,
|builder| builder.replace(text_range, expanded.to_string()),
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
macro_rules! simple_macro {
() => {
r#"
macro_rules! foo {
(foo) => (true);
() => (false);
}
"#
};
}
macro_rules! double_macro {
() => {
r#"
macro_rules! bar {
(bar) => (true);
($($tt:tt)?) => (false);
}
macro_rules! foo {
(foo) => (true);
(bar) => (bar!(bar));
($($tt:tt)?) => (bar!($($tt)?));
}
"#
};
}
macro_rules! complex_macro {
() => {
r#"
macro_rules! num {
(+$($t:tt)+) => (1 + num!($($t )+));
(-$($t:tt)+) => (-1 + num!($($t )+));
(+) => (1);
(-) => (-1);
}
"#
};
}
#[test]
fn inline_macro_target() {
check_assist_target(
inline_macro,
concat!(simple_macro!(), r#"fn f() { let a = foo$0!(foo); }"#),
"foo!(foo)",
);
}
#[test]
fn inline_macro_target_start() {
check_assist_target(
inline_macro,
concat!(simple_macro!(), r#"fn f() { let a = $0foo!(foo); }"#),
"foo!(foo)",
);
}
#[test]
fn inline_macro_target_end() {
check_assist_target(
inline_macro,
concat!(simple_macro!(), r#"fn f() { let a = foo!(foo$0); }"#),
"foo!(foo)",
);
}
#[test]
fn inline_macro_simple_case1() {
check_assist(
inline_macro,
concat!(simple_macro!(), r#"fn f() { let result = foo$0!(foo); }"#),
concat!(simple_macro!(), r#"fn f() { let result = true; }"#),
);
}
#[test]
fn inline_macro_simple_case2() {
check_assist(
inline_macro,
concat!(simple_macro!(), r#"fn f() { let result = foo$0!(); }"#),
concat!(simple_macro!(), r#"fn f() { let result = false; }"#),
);
}
#[test]
fn inline_macro_simple_not_applicable() {
check_assist_not_applicable(
inline_macro,
concat!(simple_macro!(), r#"fn f() { let result$0 = foo!(foo); }"#),
);
}
#[test]
fn inline_macro_simple_not_applicable_broken_macro() {
// FIXME: This is a bug. The macro should not expand, but it's
// the same behaviour as the "Expand Macro Recursively" commmand
// so it's presumably OK for the time being.
check_assist(
inline_macro,
concat!(simple_macro!(), r#"fn f() { let result = foo$0!(asdfasdf); }"#),
concat!(simple_macro!(), r#"fn f() { let result = true; }"#),
);
}
#[test]
fn inline_macro_double_case1() {
check_assist(
inline_macro,
concat!(double_macro!(), r#"fn f() { let result = foo$0!(bar); }"#),
concat!(double_macro!(), r#"fn f() { let result = bar!(bar); }"#),
);
}
#[test]
fn inline_macro_double_case2() {
check_assist(
inline_macro,
concat!(double_macro!(), r#"fn f() { let result = foo$0!(asdf); }"#),
concat!(double_macro!(), r#"fn f() { let result = bar!(asdf); }"#),
);
}
#[test]
fn inline_macro_complex_case1() {
check_assist(
inline_macro,
concat!(complex_macro!(), r#"fn f() { let result = num!(+ +$0 + - +); }"#),
concat!(complex_macro!(), r#"fn f() { let result = 1+num!(+ + - +); }"#),
);
}
#[test]
fn inline_macro_complex_case2() {
check_assist(
inline_macro,
concat!(complex_macro!(), r#"fn f() { let result = n$0um!(- + + - +); }"#),
concat!(complex_macro!(), r#"fn f() { let result = -1+num!(+ + - +); }"#),
);
}
#[test]
fn inline_macro_recursive_macro() {
check_assist(
inline_macro,
r#"
macro_rules! foo {
() => {foo!()}
}
fn f() { let result = foo$0!(); }
"#,
r#"
macro_rules! foo {
() => {foo!()}
}
fn f() { let result = foo!(); }
"#,
);
}
#[test]
fn inline_macro_unknown_macro() {
check_assist_not_applicable(
inline_macro,
r#"
fn f() { let result = foo$0!(); }
"#,
);
}
#[test]
fn inline_macro_function_call_not_applicable() {
check_assist_not_applicable(
inline_macro,
r#"
fn f() { let result = foo$0(); }
"#,
);
}
}

View file

@ -159,6 +159,7 @@ mod handlers {
mod add_return_type;
mod inline_call;
mod inline_local_variable;
mod inline_macro;
mod inline_type_alias;
mod introduce_named_lifetime;
mod invert_if;
@ -259,6 +260,7 @@ mod handlers {
inline_local_variable::inline_local_variable,
inline_type_alias::inline_type_alias,
inline_type_alias::inline_type_alias_uses,
inline_macro::inline_macro,
introduce_named_generic::introduce_named_generic,
introduce_named_lifetime::introduce_named_lifetime,
invert_if::invert_if,

View file

@ -1469,6 +1469,39 @@ fn main() {
)
}
#[test]
fn doctest_inline_macro() {
check_doc_test(
"inline_macro",
r#####"
macro_rules! num {
(+$($t:tt)+) => (1 + num!($($t )+));
(-$($t:tt)+) => (-1 + num!($($t )+));
(+) => (1);
(-) => (-1);
}
fn main() {
let number = num$0!(+ + + - + +);
println!("{number}");
}
"#####,
r#####"
macro_rules! num {
(+$($t:tt)+) => (1 + num!($($t )+));
(-$($t:tt)+) => (-1 + num!($($t )+));
(+) => (1);
(-) => (-1);
}
fn main() {
let number = 1+num!(+ + - + +);
println!("{number}");
}
"#####,
)
}
#[test]
fn doctest_inline_type_alias() {
check_doc_test(