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,124 @@ 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 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
+
80
103
acc. add (
81
104
AssistId ( "apply_demorgan" , AssistKind :: RefactorRewrite ) ,
82
105
"Apply De Morgan's law" ,
83
106
op_range,
84
107
|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
-
106
108
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
109
if let Some ( neg_expr) = neg_expr {
115
110
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 ( ) ) ;
117
112
} else {
118
113
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 ( ) ) ;
120
128
}
121
129
} 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) ;
141
139
}
142
140
} ,
143
141
)
144
142
}
145
143
146
144
#[ cfg( test) ]
147
145
mod tests {
148
- use crate :: tests:: { check_assist, check_assist_not_applicable} ;
149
-
150
146
use super :: * ;
147
+ use crate :: tests:: { check_assist, check_assist_not_applicable} ;
151
148
152
149
#[ test]
153
150
fn demorgan_handles_leq ( ) {
@@ -213,7 +210,7 @@ fn f() { !(S <= S || S < S) }
213
210
#[ test]
214
211
fn demorgan_doesnt_double_negation ( ) {
215
212
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 }" )
217
214
}
218
215
219
216
#[ test]
@@ -222,13 +219,38 @@ fn f() { !(S <= S || S < S) }
222
219
check_assist ( apply_demorgan, "fn f() { (x ||$0 x) }" , "fn f() { !(!x && !x) }" )
223
220
}
224
221
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
+
226
248
#[ test]
227
- fn demorgan_doesnt_hang ( ) {
249
+ fn demorgan_removes_pars_in_eq_precedence ( ) {
228
250
check_assist (
229
251
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; }" ,
232
254
)
233
255
}
234
256
}
0 commit comments