1
1
use std:: collections:: VecDeque ;
2
2
3
- use syntax:: ast:: { self , AstNode } ;
3
+ use syntax:: {
4
+ ast:: { self , AstNode , Expr :: BinExpr } ,
5
+ ted:: { self , Position } ,
6
+ SyntaxKind ,
7
+ } ;
4
8
5
9
use crate :: { utils:: invert_boolean_expression, AssistContext , AssistId , AssistKind , Assists } ;
6
10
@@ -23,131 +27,126 @@ use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKin
23
27
// }
24
28
// ```
25
29
pub ( 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 ( ) ;
29
33
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 ( ) ) {
38
36
return None ;
39
37
}
40
38
41
- let mut expr = expr;
42
-
43
39
// 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 ( ) {
46
42
Some ( parent_op) if parent_op == op => {
47
- expr = parent_expr;
43
+ bin_expr = parent_expr;
48
44
}
49
45
_ => break ,
50
46
}
51
47
}
52
48
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 ( ) ?) ) ;
67
71
} 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 ( ) ) ;
69
77
}
70
78
} else {
71
- terms . push ( expr ) ;
79
+ return None ;
72
80
}
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
+ }
78
88
}
79
89
90
+ let dm_lhs = demorganed. lhs ( ) ?;
91
+
80
92
acc. add (
81
93
AssistId ( "apply_demorgan" , AssistKind :: RefactorRewrite ) ,
82
94
"Apply De Morgan's law" ,
83
95
op_range,
84
96
|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) ;
90
98
let neg_expr = paren_expr
91
99
. clone ( )
92
100
. and_then ( |paren_expr| paren_expr. syntax ( ) . parent ( ) )
93
101
. and_then ( ast:: PrefixExpr :: cast)
94
102
. and_then ( |prefix_expr| {
95
- if prefix_expr. op_kind ( ) . unwrap ( ) == ast:: UnaryOp :: Not {
103
+ if prefix_expr. op_kind ( ) ? == ast:: UnaryOp :: Not {
96
104
Some ( prefix_expr)
97
105
} else {
98
106
None
99
107
}
100
108
} ) ;
101
109
102
- for op_range in op_ranges {
103
- edit. replace ( op_range, opposite_op) ;
104
- }
105
-
106
110
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
-
114
111
if let Some ( neg_expr) = neg_expr {
115
112
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 ( ) ) ;
117
114
} else {
118
115
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 ( ) ) ;
120
130
}
121
131
} 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) ;
141
141
}
142
142
} ,
143
143
)
144
144
}
145
145
146
146
#[ cfg( test) ]
147
147
mod tests {
148
- use crate :: tests:: { check_assist, check_assist_not_applicable} ;
149
-
150
148
use super :: * ;
149
+ use crate :: tests:: { check_assist, check_assist_not_applicable} ;
151
150
152
151
#[ test]
153
152
fn demorgan_handles_leq ( ) {
@@ -213,7 +212,7 @@ fn f() { !(S <= S || S < S) }
213
212
#[ test]
214
213
fn demorgan_doesnt_double_negation ( ) {
215
214
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 }" )
217
216
}
218
217
219
218
#[ test]
@@ -222,13 +221,38 @@ fn f() { !(S <= S || S < S) }
222
221
check_assist ( apply_demorgan, "fn f() { (x ||$0 x) }" , "fn f() { !(!x && !x) }" )
223
222
}
224
223
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
+
226
250
#[ test]
227
- fn demorgan_doesnt_hang ( ) {
251
+ fn demorgan_removes_pars_in_eq_precedence ( ) {
228
252
check_assist (
229
253
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; }" ,
232
256
)
233
257
}
234
258
}
0 commit comments