1
1
use itertools:: Itertools ;
2
2
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 ,
5
5
} ;
6
6
7
7
use crate :: { AssistContext , AssistId , AssistKind , Assists } ;
@@ -12,24 +12,28 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
12
12
//
13
13
// ```
14
14
// fn main() {
15
- // $0dbg!(92);
15
+ // let x = $0dbg!(42 * dbg!(4 + 2));$0
16
16
// }
17
17
// ```
18
18
// ->
19
19
// ```
20
20
// fn main() {
21
- // 92 ;
21
+ // let x = 42 * (4 + 2) ;
22
22
// }
23
23
// ```
24
24
pub ( crate ) fn remove_dbg ( acc : & mut Assists , ctx : & AssistContext < ' _ > ) -> Option < ( ) > {
25
25
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 >( ) ?]
27
27
} else {
28
28
ctx. covering_element ( )
29
29
. as_node ( ) ?
30
30
. descendants ( )
31
31
. 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.
32
35
. filter_map ( ast:: MacroCall :: cast)
36
+ . filter_map ( |it| it. syntax ( ) . parent ( ) . and_then ( ast:: MacroExpr :: cast) )
33
37
. collect ( )
34
38
} ;
35
39
@@ -44,14 +48,25 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
44
48
"Remove dbg!()" ,
45
49
ctx. selection_trimmed ( ) ,
46
50
|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
+ }
49
57
}
50
58
} ,
51
59
)
52
60
}
53
61
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 ( ) ?;
55
70
let tt = macro_call. token_tree ( ) ?;
56
71
let r_delim = NodeOrToken :: Token ( tt. right_delimiter_token ( ) ?) ;
57
72
if macro_call. path ( ) ?. segment ( ) ?. name_ref ( ) ?. text ( ) != "dbg"
@@ -68,40 +83,43 @@ fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, Str
68
83
. map ( |mut tokens| syntax:: hacks:: parse_expr_from_str ( & tokens. join ( "" ) ) )
69
84
. collect :: < Option < Vec < ast:: Expr > > > ( ) ?;
70
85
71
- let macro_expr = ast:: MacroExpr :: cast ( macro_call. syntax ( ) . parent ( ) ?) ?;
72
86
let parent = macro_expr. syntax ( ) . parent ( ) ?;
73
87
Some ( match & * input_expressions {
74
88
// dbg!()
75
89
[ ] => {
76
90
match_ast ! {
77
91
match parent {
78
- ast:: StmtList ( __ ) => {
92
+ ast:: StmtList ( _ ) => {
79
93
let range = macro_expr. syntax( ) . text_range( ) ;
80
94
let range = match whitespace_start( macro_expr. syntax( ) . prev_sibling_or_token( ) ) {
81
95
Some ( start) => range. cover_offset( start) ,
82
96
None => range,
83
97
} ;
84
- ( range, String :: new ( ) )
98
+ ( range, None )
85
99
} ,
86
100
ast:: ExprStmt ( it) => {
87
101
let range = it. syntax( ) . text_range( ) ;
88
102
let range = match whitespace_start( it. syntax( ) . prev_sibling_or_token( ) ) {
89
103
Some ( start) => range. cover_offset( start) ,
90
104
None => range,
91
105
} ;
92
- ( range, String :: new ( ) )
106
+ ( range, None )
93
107
} ,
94
- _ => ( macro_call. syntax( ) . text_range( ) , "()" . to_owned ( ) )
108
+ _ => ( macro_call. syntax( ) . text_range( ) , Some ( make :: expr_unit ( ) ) ) ,
95
109
}
96
110
}
97
111
}
98
112
// dbg!(expr0)
99
113
[ expr] => {
114
+ // dbg!(expr, &parent);
100
115
let wrap = match ast:: Expr :: cast ( parent) {
101
116
Some ( parent) => match ( expr, parent) {
102
117
( ast:: Expr :: CastExpr ( _) , ast:: Expr :: CastExpr ( _) ) => false ,
103
118
(
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 ( _) ,
105
123
ast:: Expr :: AwaitExpr ( _)
106
124
| ast:: Expr :: CallExpr ( _)
107
125
| ast:: Expr :: CastExpr ( _)
@@ -112,7 +130,10 @@ fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, Str
112
130
| ast:: Expr :: TryExpr ( _) ,
113
131
) => true ,
114
132
(
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 ( _) ,
116
137
ast:: Expr :: AwaitExpr ( _)
117
138
| ast:: Expr :: BinExpr ( _)
118
139
| ast:: Expr :: CallExpr ( _)
@@ -129,16 +150,61 @@ fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, Str
129
150
} ,
130
151
None => false ,
131
152
} ;
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) )
136
156
}
137
157
// 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
+ }
139
163
} )
140
164
}
141
165
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
+
142
208
fn whitespace_start ( it : Option < SyntaxElement > ) -> Option < TextSize > {
143
209
Some ( it?. into_token ( ) . and_then ( ast:: Whitespace :: cast) ?. syntax ( ) . text_range ( ) . start ( ) )
144
210
}
@@ -287,4 +353,32 @@ fn f() {
287
353
check_assist_not_applicable ( remove_dbg, r#"$0dbg$0!(0)"# ) ;
288
354
check_assist_not_applicable ( remove_dbg, r#"$0dbg!(0$0)"# ) ;
289
355
}
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
+ }
290
384
}
0 commit comments