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,124 @@ 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 paren_expr = bin_expr. syntax ( ) . parent ( ) . and_then ( ast:: ParenExpr :: cast) ;
91+ let neg_expr = paren_expr
92+ . clone ( )
93+ . and_then ( |paren_expr| paren_expr. syntax ( ) . parent ( ) )
94+ . and_then ( ast:: PrefixExpr :: cast)
95+ . and_then ( |prefix_expr| {
96+ if prefix_expr. op_kind ( ) . unwrap ( ) == ast:: UnaryOp :: Not {
97+ Some ( prefix_expr)
98+ } else {
99+ None
100+ }
101+ } ) ;
102+
80103 acc. add (
81104 AssistId ( "apply_demorgan" , AssistKind :: RefactorRewrite ) ,
82105 "Apply De Morgan's law" ,
83106 op_range,
84107 |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-
90- let neg_expr = paren_expr
91- . clone ( )
92- . and_then ( |paren_expr| paren_expr. syntax ( ) . parent ( ) )
93- . and_then ( ast:: PrefixExpr :: cast)
94- . and_then ( |prefix_expr| {
95- if prefix_expr. op_kind ( ) . unwrap ( ) == ast:: UnaryOp :: Not {
96- Some ( prefix_expr)
97- } else {
98- None
99- }
100- } ) ;
101-
102- for op_range in op_ranges {
103- edit. replace ( op_range, opposite_op) ;
104- }
105-
106108 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-
114109 if let Some ( neg_expr) = neg_expr {
115110 cov_mark:: hit!( demorgan_double_negation) ;
116- edit. replace ( neg_expr . op_token ( ) . unwrap ( ) . text_range ( ) , "" ) ;
111+ edit. replace_ast ( ast :: Expr :: PrefixExpr ( neg_expr ) , demorganed . into ( ) ) ;
117112 } else {
118113 cov_mark:: hit!( demorgan_double_parens) ;
119- edit. replace ( paren_expr. l_paren_token ( ) . unwrap ( ) . text_range ( ) , "!(" ) ;
114+ ted:: insert_all_raw (
115+ Position :: before ( demorganed. lhs ( ) . unwrap ( ) . syntax ( ) ) ,
116+ vec ! [
117+ syntax:: NodeOrToken :: Token ( ast:: make:: token( SyntaxKind :: BANG ) ) ,
118+ syntax:: NodeOrToken :: Token ( ast:: make:: token( SyntaxKind :: L_PAREN ) ) ,
119+ ] ,
120+ ) ;
121+
122+ ted:: append_child_raw (
123+ demorganed. syntax ( ) ,
124+ syntax:: NodeOrToken :: Token ( ast:: make:: token ( SyntaxKind :: R_PAREN ) ) ,
125+ ) ;
126+
127+ edit. replace_ast ( ast:: Expr :: ParenExpr ( paren_expr) , demorganed. into ( ) ) ;
120128 }
121129 } 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- }
130+ ted:: insert_all_raw (
131+ Position :: before ( demorganed. lhs ( ) . unwrap ( ) . syntax ( ) ) ,
132+ vec ! [
133+ syntax:: NodeOrToken :: Token ( ast:: make:: token( SyntaxKind :: BANG ) ) ,
134+ syntax:: NodeOrToken :: Token ( ast:: make:: token( SyntaxKind :: L_PAREN ) ) ,
135+ ] ,
136+ ) ;
137+ ted:: append_child_raw ( demorganed. syntax ( ) , ast:: make:: token ( SyntaxKind :: R_PAREN ) ) ;
138+ edit. replace_ast ( bin_expr, demorganed) ;
141139 }
142140 } ,
143141 )
144142}
145143
146144#[ cfg( test) ]
147145mod tests {
148- use crate :: tests:: { check_assist, check_assist_not_applicable} ;
149-
150146 use super :: * ;
147+ use crate :: tests:: { check_assist, check_assist_not_applicable} ;
151148
152149 #[ test]
153150 fn demorgan_handles_leq ( ) {
@@ -213,7 +210,7 @@ fn f() { !(S <= S || S < S) }
213210 #[ test]
214211 fn demorgan_doesnt_double_negation ( ) {
215212 cov_mark:: check!( demorgan_double_negation) ;
216- check_assist ( apply_demorgan, "fn f() { !(x ||$0 x) }" , "fn f() { ( !x && !x) }" )
213+ check_assist ( apply_demorgan, "fn f() { !(x ||$0 x) }" , "fn f() { !x && !x }" )
217214 }
218215
219216 #[ test]
@@ -222,13 +219,38 @@ fn f() { !(S <= S || S < S) }
222219 check_assist ( apply_demorgan, "fn f() { (x ||$0 x) }" , "fn f() { !(!x && !x) }" )
223220 }
224221
225- // https://github.com/rust-lang/rust-analyzer/issues/10963
222+ // FIXME : This needs to go.
223+ // // https://github.com/rust-lang/rust-analyzer/issues/10963
224+ // #[test]
225+ // fn demorgan_doesnt_hang() {
226+ // check_assist(
227+ // apply_demorgan,
228+ // "fn f() { 1 || 3 &&$0 4 || 5 }",
229+ // "fn f() { !(!1 || !3 || !4) || 5 }",
230+ // )
231+ // }
232+
233+ #[ test]
234+ fn demorgan_keep_pars_for_op_precedence ( ) {
235+ check_assist (
236+ apply_demorgan,
237+ "fn main() {
238+ let _ = !(!a ||$0 !(b || c));
239+ }
240+ " ,
241+ "fn main() {
242+ let _ = a && (b || c);
243+ }
244+ " ,
245+ ) ;
246+ }
247+
226248 #[ test]
227- fn demorgan_doesnt_hang ( ) {
249+ fn demorgan_removes_pars_in_eq_precedence ( ) {
228250 check_assist (
229251 apply_demorgan,
230- "fn f () { 1 || 3 &&$0 4 || 5 }" ,
231- "fn f () { !(!1 || !3 || !4) || 5 }" ,
252+ "fn() { let x = a && !(!b |$0| !c); }" ,
253+ "fn() { let x = a && b && c; }" ,
232254 )
233255 }
234256}
0 commit comments