Skip to content

Commit d1fd635

Browse files
committed
Auto merge of rust-lang#14225 - lowr:patch/remove-nested-dbg, r=Veykril
Support removing nested `dbg!()`s in `remove_dbg` Closes rust-lang#13901
2 parents 7f01ae8 + de4a896 commit d1fd635

File tree

2 files changed

+116
-22
lines changed

2 files changed

+116
-22
lines changed

crates/ide-assists/src/handlers/remove_dbg.rs

Lines changed: 114 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use itertools::Itertools;
22
use syntax::{
3-
ast::{self, AstNode, AstToken},
4-
match_ast, NodeOrToken, SyntaxElement, TextRange, TextSize, T,
3+
ast::{self, make, AstNode, AstToken},
4+
match_ast, ted, NodeOrToken, SyntaxElement, TextRange, TextSize, T,
55
};
66

77
use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -12,24 +12,28 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
1212
//
1313
// ```
1414
// fn main() {
15-
// $0dbg!(92);
15+
// let x = $0dbg!(42 * dbg!(4 + 2));$0
1616
// }
1717
// ```
1818
// ->
1919
// ```
2020
// fn main() {
21-
// 92;
21+
// let x = 42 * (4 + 2);
2222
// }
2323
// ```
2424
pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
2525
let macro_calls = if ctx.has_empty_selection() {
26-
vec![ctx.find_node_at_offset::<ast::MacroCall>()?]
26+
vec![ctx.find_node_at_offset::<ast::MacroExpr>()?]
2727
} else {
2828
ctx.covering_element()
2929
.as_node()?
3030
.descendants()
3131
.filter(|node| ctx.selection_trimmed().contains_range(node.text_range()))
32+
// When the selection exactly covers the macro call to be removed, `covering_element()`
33+
// returns `ast::MacroCall` instead of its parent `ast::MacroExpr` that we want. So
34+
// first try finding `ast::MacroCall`s and then retrieve their parent.
3235
.filter_map(ast::MacroCall::cast)
36+
.filter_map(|it| it.syntax().parent().and_then(ast::MacroExpr::cast))
3337
.collect()
3438
};
3539

@@ -44,14 +48,25 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
4448
"Remove dbg!()",
4549
ctx.selection_trimmed(),
4650
|builder| {
47-
for (range, text) in replacements {
48-
builder.replace(range, text);
51+
for (range, expr) in replacements {
52+
if let Some(expr) = expr {
53+
builder.replace(range, expr.to_string());
54+
} else {
55+
builder.delete(range);
56+
}
4957
}
5058
},
5159
)
5260
}
5361

54-
fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, String)> {
62+
/// Returns `None` when either
63+
/// - macro call is not `dbg!()`
64+
/// - any node inside `dbg!()` could not be parsed as an expression
65+
/// - (`macro_expr` has no parent - is that possible?)
66+
///
67+
/// Returns `Some(_, None)` when the macro call should just be removed.
68+
fn compute_dbg_replacement(macro_expr: ast::MacroExpr) -> Option<(TextRange, Option<ast::Expr>)> {
69+
let macro_call = macro_expr.macro_call()?;
5570
let tt = macro_call.token_tree()?;
5671
let r_delim = NodeOrToken::Token(tt.right_delimiter_token()?);
5772
if macro_call.path()?.segment()?.name_ref()?.text() != "dbg"
@@ -68,40 +83,43 @@ fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, Str
6883
.map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join("")))
6984
.collect::<Option<Vec<ast::Expr>>>()?;
7085

71-
let macro_expr = ast::MacroExpr::cast(macro_call.syntax().parent()?)?;
7286
let parent = macro_expr.syntax().parent()?;
7387
Some(match &*input_expressions {
7488
// dbg!()
7589
[] => {
7690
match_ast! {
7791
match parent {
78-
ast::StmtList(__) => {
92+
ast::StmtList(_) => {
7993
let range = macro_expr.syntax().text_range();
8094
let range = match whitespace_start(macro_expr.syntax().prev_sibling_or_token()) {
8195
Some(start) => range.cover_offset(start),
8296
None => range,
8397
};
84-
(range, String::new())
98+
(range, None)
8599
},
86100
ast::ExprStmt(it) => {
87101
let range = it.syntax().text_range();
88102
let range = match whitespace_start(it.syntax().prev_sibling_or_token()) {
89103
Some(start) => range.cover_offset(start),
90104
None => range,
91105
};
92-
(range, String::new())
106+
(range, None)
93107
},
94-
_ => (macro_call.syntax().text_range(), "()".to_owned())
108+
_ => (macro_call.syntax().text_range(), Some(make::expr_unit())),
95109
}
96110
}
97111
}
98112
// dbg!(expr0)
99113
[expr] => {
114+
// dbg!(expr, &parent);
100115
let wrap = match ast::Expr::cast(parent) {
101116
Some(parent) => match (expr, parent) {
102117
(ast::Expr::CastExpr(_), ast::Expr::CastExpr(_)) => false,
103118
(
104-
ast::Expr::BoxExpr(_) | ast::Expr::PrefixExpr(_) | ast::Expr::RefExpr(_),
119+
ast::Expr::BoxExpr(_)
120+
| ast::Expr::PrefixExpr(_)
121+
| ast::Expr::RefExpr(_)
122+
| ast::Expr::MacroExpr(_),
105123
ast::Expr::AwaitExpr(_)
106124
| ast::Expr::CallExpr(_)
107125
| ast::Expr::CastExpr(_)
@@ -112,7 +130,10 @@ fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, Str
112130
| ast::Expr::TryExpr(_),
113131
) => true,
114132
(
115-
ast::Expr::BinExpr(_) | ast::Expr::CastExpr(_) | ast::Expr::RangeExpr(_),
133+
ast::Expr::BinExpr(_)
134+
| ast::Expr::CastExpr(_)
135+
| ast::Expr::RangeExpr(_)
136+
| ast::Expr::MacroExpr(_),
116137
ast::Expr::AwaitExpr(_)
117138
| ast::Expr::BinExpr(_)
118139
| ast::Expr::CallExpr(_)
@@ -129,16 +150,61 @@ fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, Str
129150
},
130151
None => false,
131152
};
132-
(
133-
macro_call.syntax().text_range(),
134-
if wrap { format!("({expr})") } else { expr.to_string() },
135-
)
153+
let expr = replace_nested_dbgs(expr.clone());
154+
let expr = if wrap { make::expr_paren(expr) } else { expr.clone_subtree() };
155+
(macro_call.syntax().text_range(), Some(expr))
136156
}
137157
// dbg!(expr0, expr1, ...)
138-
exprs => (macro_call.syntax().text_range(), format!("({})", exprs.iter().format(", "))),
158+
exprs => {
159+
let exprs = exprs.iter().cloned().map(replace_nested_dbgs);
160+
let expr = make::expr_tuple(exprs);
161+
(macro_call.syntax().text_range(), Some(expr))
162+
}
139163
})
140164
}
141165

166+
fn replace_nested_dbgs(expanded: ast::Expr) -> ast::Expr {
167+
if let ast::Expr::MacroExpr(mac) = &expanded {
168+
// Special-case when `expanded` itself is `dbg!()` since we cannot replace the whole tree
169+
// with `ted`. It should be fairly rare as it means the user wrote `dbg!(dbg!(..))` but you
170+
// never know how code ends up being!
171+
let replaced = if let Some((_, expr_opt)) = compute_dbg_replacement(mac.clone()) {
172+
match expr_opt {
173+
Some(expr) => expr,
174+
None => {
175+
stdx::never!("dbg! inside dbg! should not be just removed");
176+
expanded
177+
}
178+
}
179+
} else {
180+
expanded
181+
};
182+
183+
return replaced;
184+
}
185+
186+
let expanded = expanded.clone_for_update();
187+
188+
// We need to collect to avoid mutation during traversal.
189+
let macro_exprs: Vec<_> =
190+
expanded.syntax().descendants().filter_map(ast::MacroExpr::cast).collect();
191+
192+
for mac in macro_exprs {
193+
let expr_opt = match compute_dbg_replacement(mac.clone()) {
194+
Some((_, expr)) => expr,
195+
None => continue,
196+
};
197+
198+
if let Some(expr) = expr_opt {
199+
ted::replace(mac.syntax(), expr.syntax().clone_for_update());
200+
} else {
201+
ted::remove(mac.syntax());
202+
}
203+
}
204+
205+
expanded
206+
}
207+
142208
fn whitespace_start(it: Option<SyntaxElement>) -> Option<TextSize> {
143209
Some(it?.into_token().and_then(ast::Whitespace::cast)?.syntax().text_range().start())
144210
}
@@ -287,4 +353,32 @@ fn f() {
287353
check_assist_not_applicable(remove_dbg, r#"$0dbg$0!(0)"#);
288354
check_assist_not_applicable(remove_dbg, r#"$0dbg!(0$0)"#);
289355
}
356+
357+
#[test]
358+
fn test_nested_dbg() {
359+
check(
360+
r#"$0let x = dbg!(dbg!(dbg!(dbg!(0 + 1)) * 2) + dbg!(3));$0"#,
361+
r#"let x = ((0 + 1) * 2) + 3;"#,
362+
);
363+
check(r#"$0dbg!(10, dbg!(), dbg!(20, 30))$0"#, r#"(10, (), (20, 30))"#);
364+
}
365+
366+
#[test]
367+
fn test_multiple_nested_dbg() {
368+
check(
369+
r#"
370+
fn f() {
371+
$0dbg!();
372+
let x = dbg!(dbg!(dbg!(0 + 1)) + 2) + dbg!(3);
373+
dbg!(10, dbg!(), dbg!(20, 30));$0
374+
}
375+
"#,
376+
r#"
377+
fn f() {
378+
let x = ((0 + 1) + 2) + 3;
379+
(10, (), (20, 30));
380+
}
381+
"#,
382+
);
383+
}
290384
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2006,12 +2006,12 @@ fn doctest_remove_dbg() {
20062006
"remove_dbg",
20072007
r#####"
20082008
fn main() {
2009-
$0dbg!(92);
2009+
let x = $0dbg!(42 * dbg!(4 + 2));$0
20102010
}
20112011
"#####,
20122012
r#####"
20132013
fn main() {
2014-
92;
2014+
let x = 42 * (4 + 2);
20152015
}
20162016
"#####,
20172017
)

0 commit comments

Comments
 (0)