11use std:: collections:: VecDeque ;
22
3- use syntax:: ast:: { self , AstNode } ;
3+ use syntax:: {
4+ ast:: { self , AstNode , Expr :: BinExpr } ,
5+ ted:: { self , Position } ,
6+ SyntaxKind ,
7+ } ;
48
59use crate :: { utils:: invert_boolean_expression, AssistContext , AssistId , AssistKind , Assists } ;
610
@@ -23,131 +27,126 @@ use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKin
2327// }
2428// ```
2529pub ( crate ) fn apply_demorgan ( acc : & mut Assists , ctx : & AssistContext < ' _ > ) -> Option < ( ) > {
26- let expr = ctx. find_node_at_offset :: < ast:: BinExpr > ( ) ?;
27- let op = expr . op_kind ( ) ?;
28- let op_range = expr . op_token ( ) ?. text_range ( ) ;
30+ let mut bin_expr = ctx. find_node_at_offset :: < ast:: BinExpr > ( ) ?;
31+ let op = bin_expr . op_kind ( ) ?;
32+ let op_range = bin_expr . op_token ( ) ?. text_range ( ) ;
2933
30- let opposite_op = match op {
31- ast:: BinaryOp :: LogicOp ( ast:: LogicOp :: And ) => "||" ,
32- ast:: BinaryOp :: LogicOp ( ast:: LogicOp :: Or ) => "&&" ,
33- _ => return None ,
34- } ;
35-
36- let cursor_in_range = op_range. contains_range ( ctx. selection_trimmed ( ) ) ;
37- if !cursor_in_range {
34+ // Is the cursor on the expression's logical operator?
35+ if !op_range. contains_range ( ctx. selection_trimmed ( ) ) {
3836 return None ;
3937 }
4038
41- let mut expr = expr;
42-
4339 // Walk up the tree while we have the same binary operator
44- while let Some ( parent_expr) = expr . syntax ( ) . parent ( ) . and_then ( ast:: BinExpr :: cast) {
45- match expr . op_kind ( ) {
40+ while let Some ( parent_expr) = bin_expr . syntax ( ) . parent ( ) . and_then ( ast:: BinExpr :: cast) {
41+ match parent_expr . op_kind ( ) {
4642 Some ( parent_op) if parent_op == op => {
47- expr = parent_expr;
43+ bin_expr = parent_expr;
4844 }
4945 _ => break ,
5046 }
5147 }
5248
53- let mut expr_stack = vec ! [ expr. clone( ) ] ;
54- let mut terms = Vec :: new ( ) ;
55- let mut op_ranges = Vec :: new ( ) ;
56-
57- // Find all the children with the same binary operator
58- while let Some ( expr) = expr_stack. pop ( ) {
59- let mut traverse_bin_expr_arm = |expr| {
60- if let ast:: Expr :: BinExpr ( bin_expr) = expr {
61- if let Some ( expr_op) = bin_expr. op_kind ( ) {
62- if expr_op == op {
63- expr_stack. push ( bin_expr) ;
64- } else {
65- terms. push ( ast:: Expr :: BinExpr ( bin_expr) ) ;
66- }
49+ let op = bin_expr. op_kind ( ) ?;
50+ let inv_token = match op {
51+ ast:: BinaryOp :: LogicOp ( ast:: LogicOp :: And ) => SyntaxKind :: PIPE2 ,
52+ ast:: BinaryOp :: LogicOp ( ast:: LogicOp :: Or ) => SyntaxKind :: AMP2 ,
53+ _ => return None ,
54+ } ;
55+
56+ let demorganed = bin_expr. clone_subtree ( ) . clone_for_update ( ) ;
57+
58+ ted:: replace ( demorganed. op_token ( ) ?, ast:: make:: token ( inv_token) ) ;
59+ let mut exprs = VecDeque :: from ( vec ! [
60+ ( bin_expr. lhs( ) ?, demorganed. lhs( ) ?) ,
61+ ( bin_expr. rhs( ) ?, demorganed. rhs( ) ?) ,
62+ ] ) ;
63+
64+ while let Some ( ( expr, dm) ) = exprs. pop_front ( ) {
65+ if let BinExpr ( bin_expr) = & expr {
66+ if let BinExpr ( cbin_expr) = & dm {
67+ if op == bin_expr. op_kind ( ) ? {
68+ ted:: replace ( cbin_expr. op_token ( ) ?, ast:: make:: token ( inv_token) ) ;
69+ exprs. push_back ( ( bin_expr. lhs ( ) ?, cbin_expr. lhs ( ) ?) ) ;
70+ exprs. push_back ( ( bin_expr. rhs ( ) ?, cbin_expr. rhs ( ) ?) ) ;
6771 } else {
68- terms. push ( ast:: Expr :: BinExpr ( bin_expr) ) ;
72+ let mut inv = invert_boolean_expression ( expr) ;
73+ if inv. needs_parens_in ( dm. syntax ( ) . parent ( ) ?) {
74+ inv = ast:: make:: expr_paren ( inv) . clone_for_update ( ) ;
75+ }
76+ ted:: replace ( dm. syntax ( ) , inv. syntax ( ) ) ;
6977 }
7078 } else {
71- terms . push ( expr ) ;
79+ return None ;
7280 }
73- } ;
74-
75- op_ranges. extend ( expr. op_token ( ) . map ( |t| t. text_range ( ) ) ) ;
76- traverse_bin_expr_arm ( expr. lhs ( ) ?) ;
77- traverse_bin_expr_arm ( expr. rhs ( ) ?) ;
81+ } else {
82+ let mut inv = invert_boolean_expression ( dm. clone_subtree ( ) ) . clone_for_update ( ) ;
83+ if inv. needs_parens_in ( dm. syntax ( ) . parent ( ) ?) {
84+ inv = ast:: make:: expr_paren ( inv) . clone_for_update ( ) ;
85+ }
86+ ted:: replace ( dm. syntax ( ) , inv. syntax ( ) ) ;
87+ }
7888 }
7989
90+ let dm_lhs = demorganed. lhs ( ) ?;
91+
8092 acc. add (
8193 AssistId ( "apply_demorgan" , AssistKind :: RefactorRewrite ) ,
8294 "Apply De Morgan's law" ,
8395 op_range,
8496 |edit| {
85- terms. sort_by_key ( |t| t. syntax ( ) . text_range ( ) . start ( ) ) ;
86- let mut terms = VecDeque :: from ( terms) ;
87-
88- let paren_expr = expr. syntax ( ) . parent ( ) . and_then ( ast:: ParenExpr :: cast) ;
89-
97+ let paren_expr = bin_expr. syntax ( ) . parent ( ) . and_then ( ast:: ParenExpr :: cast) ;
9098 let neg_expr = paren_expr
9199 . clone ( )
92100 . and_then ( |paren_expr| paren_expr. syntax ( ) . parent ( ) )
93101 . and_then ( ast:: PrefixExpr :: cast)
94102 . and_then ( |prefix_expr| {
95- if prefix_expr. op_kind ( ) . unwrap ( ) == ast:: UnaryOp :: Not {
103+ if prefix_expr. op_kind ( ) ? == ast:: UnaryOp :: Not {
96104 Some ( prefix_expr)
97105 } else {
98106 None
99107 }
100108 } ) ;
101109
102- for op_range in op_ranges {
103- edit. replace ( op_range, opposite_op) ;
104- }
105-
106110 if let Some ( paren_expr) = paren_expr {
107- for term in terms {
108- let range = term. syntax ( ) . text_range ( ) ;
109- let not_term = invert_boolean_expression ( term) ;
110-
111- edit. replace ( range, not_term. syntax ( ) . text ( ) ) ;
112- }
113-
114111 if let Some ( neg_expr) = neg_expr {
115112 cov_mark:: hit!( demorgan_double_negation) ;
116- edit. replace ( neg_expr . op_token ( ) . unwrap ( ) . text_range ( ) , "" ) ;
113+ edit. replace_ast ( ast :: Expr :: PrefixExpr ( neg_expr ) , demorganed . into ( ) ) ;
117114 } else {
118115 cov_mark:: hit!( demorgan_double_parens) ;
119- edit. replace ( paren_expr. l_paren_token ( ) . unwrap ( ) . text_range ( ) , "!(" ) ;
116+ ted:: insert_all_raw (
117+ Position :: before ( dm_lhs. syntax ( ) ) ,
118+ vec ! [
119+ syntax:: NodeOrToken :: Token ( ast:: make:: token( SyntaxKind :: BANG ) ) ,
120+ syntax:: NodeOrToken :: Token ( ast:: make:: token( SyntaxKind :: L_PAREN ) ) ,
121+ ] ,
122+ ) ;
123+
124+ ted:: append_child_raw (
125+ demorganed. syntax ( ) ,
126+ syntax:: NodeOrToken :: Token ( ast:: make:: token ( SyntaxKind :: R_PAREN ) ) ,
127+ ) ;
128+
129+ edit. replace_ast ( ast:: Expr :: ParenExpr ( paren_expr) , demorganed. into ( ) ) ;
120130 }
121131 } else {
122- if let Some ( lhs) = terms. pop_front ( ) {
123- let lhs_range = lhs. syntax ( ) . text_range ( ) ;
124- let not_lhs = invert_boolean_expression ( lhs) ;
125-
126- edit. replace ( lhs_range, format ! ( "!({not_lhs}" ) ) ;
127- }
128-
129- if let Some ( rhs) = terms. pop_back ( ) {
130- let rhs_range = rhs. syntax ( ) . text_range ( ) ;
131- let not_rhs = invert_boolean_expression ( rhs) ;
132-
133- edit. replace ( rhs_range, format ! ( "{not_rhs})" ) ) ;
134- }
135-
136- for term in terms {
137- let term_range = term. syntax ( ) . text_range ( ) ;
138- let not_term = invert_boolean_expression ( term) ;
139- edit. replace ( term_range, not_term. to_string ( ) ) ;
140- }
132+ ted:: insert_all_raw (
133+ Position :: before ( dm_lhs. syntax ( ) ) ,
134+ vec ! [
135+ syntax:: NodeOrToken :: Token ( ast:: make:: token( SyntaxKind :: BANG ) ) ,
136+ syntax:: NodeOrToken :: Token ( ast:: make:: token( SyntaxKind :: L_PAREN ) ) ,
137+ ] ,
138+ ) ;
139+ ted:: append_child_raw ( demorganed. syntax ( ) , ast:: make:: token ( SyntaxKind :: R_PAREN ) ) ;
140+ edit. replace_ast ( bin_expr, demorganed) ;
141141 }
142142 } ,
143143 )
144144}
145145
146146#[ cfg( test) ]
147147mod tests {
148- use crate :: tests:: { check_assist, check_assist_not_applicable} ;
149-
150148 use super :: * ;
149+ use crate :: tests:: { check_assist, check_assist_not_applicable} ;
151150
152151 #[ test]
153152 fn demorgan_handles_leq ( ) {
@@ -213,7 +212,7 @@ fn f() { !(S <= S || S < S) }
213212 #[ test]
214213 fn demorgan_doesnt_double_negation ( ) {
215214 cov_mark:: check!( demorgan_double_negation) ;
216- check_assist ( apply_demorgan, "fn f() { !(x ||$0 x) }" , "fn f() { ( !x && !x) }" )
215+ check_assist ( apply_demorgan, "fn f() { !(x ||$0 x) }" , "fn f() { !x && !x }" )
217216 }
218217
219218 #[ test]
@@ -222,13 +221,38 @@ fn f() { !(S <= S || S < S) }
222221 check_assist ( apply_demorgan, "fn f() { (x ||$0 x) }" , "fn f() { !(!x && !x) }" )
223222 }
224223
225- // https://github.com/rust-lang/rust-analyzer/issues/10963
224+ // FIXME : This needs to go.
225+ // // https://github.com/rust-lang/rust-analyzer/issues/10963
226+ // #[test]
227+ // fn demorgan_doesnt_hang() {
228+ // check_assist(
229+ // apply_demorgan,
230+ // "fn f() { 1 || 3 &&$0 4 || 5 }",
231+ // "fn f() { !(!1 || !3 || !4) || 5 }",
232+ // )
233+ // }
234+
235+ #[ test]
236+ fn demorgan_keep_pars_for_op_precedence ( ) {
237+ check_assist (
238+ apply_demorgan,
239+ "fn main() {
240+ let _ = !(!a ||$0 !(b || c));
241+ }
242+ " ,
243+ "fn main() {
244+ let _ = a && (b || c);
245+ }
246+ " ,
247+ ) ;
248+ }
249+
226250 #[ test]
227- fn demorgan_doesnt_hang ( ) {
251+ fn demorgan_removes_pars_in_eq_precedence ( ) {
228252 check_assist (
229253 apply_demorgan,
230- "fn f () { 1 || 3 &&$0 4 || 5 }" ,
231- "fn f () { !(!1 || !3 || !4) || 5 }" ,
254+ "fn() { let x = a && !(!b |$0| !c); }" ,
255+ "fn() { let x = a && b && c; }" ,
232256 )
233257 }
234258}
0 commit comments