Skip to content

Commit 336608a

Browse files
committed
Auto merge of rust-lang#13810 - tfpk:tfpk/macro-inline, r=Veykril
Add action to expand a declarative macro once, inline. Fixes rust-lang#13598 This commit adds a new r-a method, `expandMacroInline`, which expands the macro that's currently selected. See rust-lang#13598 for the most applicable issue; though I suspect it'll resolve part of rust-lang#5949 and make rust-lang#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.
2 parents 938a39a + 769273c commit 336608a

File tree

3 files changed

+268
-0
lines changed

3 files changed

+268
-0
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
use syntax::ast::{self, AstNode};
2+
3+
use crate::{AssistContext, AssistId, AssistKind, Assists};
4+
5+
// Assist: inline_macro
6+
//
7+
// Takes a macro and inlines it one step.
8+
//
9+
// ```
10+
// macro_rules! num {
11+
// (+$($t:tt)+) => (1 + num!($($t )+));
12+
// (-$($t:tt)+) => (-1 + num!($($t )+));
13+
// (+) => (1);
14+
// (-) => (-1);
15+
// }
16+
//
17+
// fn main() {
18+
// let number = num$0!(+ + + - + +);
19+
// println!("{number}");
20+
// }
21+
// ```
22+
// ->
23+
// ```
24+
// macro_rules! num {
25+
// (+$($t:tt)+) => (1 + num!($($t )+));
26+
// (-$($t:tt)+) => (-1 + num!($($t )+));
27+
// (+) => (1);
28+
// (-) => (-1);
29+
// }
30+
//
31+
// fn main() {
32+
// let number = 1+num!(+ + - + +);
33+
// println!("{number}");
34+
// }
35+
// ```
36+
pub(crate) fn inline_macro(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
37+
let unexpanded = ctx.find_node_at_offset::<ast::MacroCall>()?;
38+
let expanded = ctx.sema.expand(&unexpanded)?.clone_for_update();
39+
40+
let text_range = unexpanded.syntax().text_range();
41+
42+
acc.add(
43+
AssistId("inline_macro", AssistKind::RefactorRewrite),
44+
format!("Inline macro"),
45+
text_range,
46+
|builder| builder.replace(text_range, expanded.to_string()),
47+
)
48+
}
49+
50+
#[cfg(test)]
51+
mod tests {
52+
use super::*;
53+
54+
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
55+
56+
macro_rules! simple_macro {
57+
() => {
58+
r#"
59+
macro_rules! foo {
60+
(foo) => (true);
61+
() => (false);
62+
}
63+
"#
64+
};
65+
}
66+
macro_rules! double_macro {
67+
() => {
68+
r#"
69+
macro_rules! bar {
70+
(bar) => (true);
71+
($($tt:tt)?) => (false);
72+
}
73+
macro_rules! foo {
74+
(foo) => (true);
75+
(bar) => (bar!(bar));
76+
($($tt:tt)?) => (bar!($($tt)?));
77+
}
78+
"#
79+
};
80+
}
81+
82+
macro_rules! complex_macro {
83+
() => {
84+
r#"
85+
macro_rules! num {
86+
(+$($t:tt)+) => (1 + num!($($t )+));
87+
(-$($t:tt)+) => (-1 + num!($($t )+));
88+
(+) => (1);
89+
(-) => (-1);
90+
}
91+
"#
92+
};
93+
}
94+
#[test]
95+
fn inline_macro_target() {
96+
check_assist_target(
97+
inline_macro,
98+
concat!(simple_macro!(), r#"fn f() { let a = foo$0!(foo); }"#),
99+
"foo!(foo)",
100+
);
101+
}
102+
103+
#[test]
104+
fn inline_macro_target_start() {
105+
check_assist_target(
106+
inline_macro,
107+
concat!(simple_macro!(), r#"fn f() { let a = $0foo!(foo); }"#),
108+
"foo!(foo)",
109+
);
110+
}
111+
112+
#[test]
113+
fn inline_macro_target_end() {
114+
check_assist_target(
115+
inline_macro,
116+
concat!(simple_macro!(), r#"fn f() { let a = foo!(foo$0); }"#),
117+
"foo!(foo)",
118+
);
119+
}
120+
121+
#[test]
122+
fn inline_macro_simple_case1() {
123+
check_assist(
124+
inline_macro,
125+
concat!(simple_macro!(), r#"fn f() { let result = foo$0!(foo); }"#),
126+
concat!(simple_macro!(), r#"fn f() { let result = true; }"#),
127+
);
128+
}
129+
130+
#[test]
131+
fn inline_macro_simple_case2() {
132+
check_assist(
133+
inline_macro,
134+
concat!(simple_macro!(), r#"fn f() { let result = foo$0!(); }"#),
135+
concat!(simple_macro!(), r#"fn f() { let result = false; }"#),
136+
);
137+
}
138+
139+
#[test]
140+
fn inline_macro_simple_not_applicable() {
141+
check_assist_not_applicable(
142+
inline_macro,
143+
concat!(simple_macro!(), r#"fn f() { let result$0 = foo!(foo); }"#),
144+
);
145+
}
146+
147+
#[test]
148+
fn inline_macro_simple_not_applicable_broken_macro() {
149+
// FIXME: This is a bug. The macro should not expand, but it's
150+
// the same behaviour as the "Expand Macro Recursively" commmand
151+
// so it's presumably OK for the time being.
152+
check_assist(
153+
inline_macro,
154+
concat!(simple_macro!(), r#"fn f() { let result = foo$0!(asdfasdf); }"#),
155+
concat!(simple_macro!(), r#"fn f() { let result = true; }"#),
156+
);
157+
}
158+
159+
#[test]
160+
fn inline_macro_double_case1() {
161+
check_assist(
162+
inline_macro,
163+
concat!(double_macro!(), r#"fn f() { let result = foo$0!(bar); }"#),
164+
concat!(double_macro!(), r#"fn f() { let result = bar!(bar); }"#),
165+
);
166+
}
167+
168+
#[test]
169+
fn inline_macro_double_case2() {
170+
check_assist(
171+
inline_macro,
172+
concat!(double_macro!(), r#"fn f() { let result = foo$0!(asdf); }"#),
173+
concat!(double_macro!(), r#"fn f() { let result = bar!(asdf); }"#),
174+
);
175+
}
176+
177+
#[test]
178+
fn inline_macro_complex_case1() {
179+
check_assist(
180+
inline_macro,
181+
concat!(complex_macro!(), r#"fn f() { let result = num!(+ +$0 + - +); }"#),
182+
concat!(complex_macro!(), r#"fn f() { let result = 1+num!(+ + - +); }"#),
183+
);
184+
}
185+
186+
#[test]
187+
fn inline_macro_complex_case2() {
188+
check_assist(
189+
inline_macro,
190+
concat!(complex_macro!(), r#"fn f() { let result = n$0um!(- + + - +); }"#),
191+
concat!(complex_macro!(), r#"fn f() { let result = -1+num!(+ + - +); }"#),
192+
);
193+
}
194+
195+
#[test]
196+
fn inline_macro_recursive_macro() {
197+
check_assist(
198+
inline_macro,
199+
r#"
200+
macro_rules! foo {
201+
() => {foo!()}
202+
}
203+
fn f() { let result = foo$0!(); }
204+
"#,
205+
r#"
206+
macro_rules! foo {
207+
() => {foo!()}
208+
}
209+
fn f() { let result = foo!(); }
210+
"#,
211+
);
212+
}
213+
214+
#[test]
215+
fn inline_macro_unknown_macro() {
216+
check_assist_not_applicable(
217+
inline_macro,
218+
r#"
219+
fn f() { let result = foo$0!(); }
220+
"#,
221+
);
222+
}
223+
224+
#[test]
225+
fn inline_macro_function_call_not_applicable() {
226+
check_assist_not_applicable(
227+
inline_macro,
228+
r#"
229+
fn f() { let result = foo$0(); }
230+
"#,
231+
);
232+
}
233+
}

crates/ide-assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ mod handlers {
159159
mod add_return_type;
160160
mod inline_call;
161161
mod inline_local_variable;
162+
mod inline_macro;
162163
mod inline_type_alias;
163164
mod introduce_named_lifetime;
164165
mod invert_if;
@@ -259,6 +260,7 @@ mod handlers {
259260
inline_local_variable::inline_local_variable,
260261
inline_type_alias::inline_type_alias,
261262
inline_type_alias::inline_type_alias_uses,
263+
inline_macro::inline_macro,
262264
introduce_named_generic::introduce_named_generic,
263265
introduce_named_lifetime::introduce_named_lifetime,
264266
invert_if::invert_if,

crates/ide-assists/src/tests/generated.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,6 +1469,39 @@ fn main() {
14691469
)
14701470
}
14711471

1472+
#[test]
1473+
fn doctest_inline_macro() {
1474+
check_doc_test(
1475+
"inline_macro",
1476+
r#####"
1477+
macro_rules! num {
1478+
(+$($t:tt)+) => (1 + num!($($t )+));
1479+
(-$($t:tt)+) => (-1 + num!($($t )+));
1480+
(+) => (1);
1481+
(-) => (-1);
1482+
}
1483+
1484+
fn main() {
1485+
let number = num$0!(+ + + - + +);
1486+
println!("{number}");
1487+
}
1488+
"#####,
1489+
r#####"
1490+
macro_rules! num {
1491+
(+$($t:tt)+) => (1 + num!($($t )+));
1492+
(-$($t:tt)+) => (-1 + num!($($t )+));
1493+
(+) => (1);
1494+
(-) => (-1);
1495+
}
1496+
1497+
fn main() {
1498+
let number = 1+num!(+ + - + +);
1499+
println!("{number}");
1500+
}
1501+
"#####,
1502+
)
1503+
}
1504+
14721505
#[test]
14731506
fn doctest_inline_type_alias() {
14741507
check_doc_test(

0 commit comments

Comments
 (0)