11use itertools:: Itertools ;
22use 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
77use 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// ```
2424pub ( 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+
142208fn 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}
0 commit comments