1
+ use std:: iter;
2
+
3
+ use ide_db:: { ty_filter:: TryEnum , RootDatabase } ;
1
4
use syntax:: {
2
5
ast:: {
3
6
self ,
@@ -8,7 +11,6 @@ use syntax::{
8
11
} ;
9
12
10
13
use crate :: { utils:: unwrap_trivial_block, AssistContext , AssistId , AssistKind , Assists } ;
11
- use ide_db:: ty_filter:: TryEnum ;
12
14
13
15
// Assist: replace_if_let_with_match
14
16
//
@@ -79,6 +81,91 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
79
81
)
80
82
}
81
83
84
+ // Assist: replace_match_with_if_let
85
+ //
86
+ // Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
87
+ //
88
+ // ```
89
+ // enum Action { Move { distance: u32 }, Stop }
90
+ //
91
+ // fn handle(action: Action) {
92
+ // <|>match action {
93
+ // Action::Move { distance } => foo(distance),
94
+ // _ => bar(),
95
+ // }
96
+ // }
97
+ // ```
98
+ // ->
99
+ // ```
100
+ // enum Action { Move { distance: u32 }, Stop }
101
+ //
102
+ // fn handle(action: Action) {
103
+ // if let Action::Move { distance } = action {
104
+ // foo(distance)
105
+ // } else {
106
+ // bar()
107
+ // }
108
+ // }
109
+ // ```
110
+ pub ( crate ) fn replace_match_with_if_let ( acc : & mut Assists , ctx : & AssistContext ) -> Option < ( ) > {
111
+ let match_expr: ast:: MatchExpr = ctx. find_node_at_offset ( ) ?;
112
+ let mut arms = match_expr. match_arm_list ( ) ?. arms ( ) ;
113
+ let first_arm = arms. next ( ) ?;
114
+ let second_arm = arms. next ( ) ?;
115
+ if arms. next ( ) . is_some ( ) || first_arm. guard ( ) . is_some ( ) || second_arm. guard ( ) . is_some ( ) {
116
+ return None ;
117
+ }
118
+ let condition_expr = match_expr. expr ( ) ?;
119
+ let ( if_let_pat, then_expr, else_expr) = if is_pat_wildcard_or_sad ( & ctx. sema , & first_arm. pat ( ) ?)
120
+ {
121
+ ( second_arm. pat ( ) ?, second_arm. expr ( ) ?, first_arm. expr ( ) ?)
122
+ } else if is_pat_wildcard_or_sad ( & ctx. sema , & second_arm. pat ( ) ?) {
123
+ ( first_arm. pat ( ) ?, first_arm. expr ( ) ?, second_arm. expr ( ) ?)
124
+ } else {
125
+ return None ;
126
+ } ;
127
+
128
+ let target = match_expr. syntax ( ) . text_range ( ) ;
129
+ acc. add (
130
+ AssistId ( "replace_match_with_if_let" , AssistKind :: RefactorRewrite ) ,
131
+ "Replace with if let" ,
132
+ target,
133
+ move |edit| {
134
+ let condition = make:: condition ( condition_expr, Some ( if_let_pat) ) ;
135
+ let then_block = match then_expr. reset_indent ( ) {
136
+ ast:: Expr :: BlockExpr ( block) => block,
137
+ expr => make:: block_expr ( iter:: empty ( ) , Some ( expr) ) ,
138
+ } ;
139
+ let else_expr = match else_expr {
140
+ ast:: Expr :: BlockExpr ( block)
141
+ if block. statements ( ) . count ( ) == 0 && block. expr ( ) . is_none ( ) =>
142
+ {
143
+ None
144
+ }
145
+ ast:: Expr :: TupleExpr ( tuple) if tuple. fields ( ) . count ( ) == 0 => None ,
146
+ expr => Some ( expr) ,
147
+ } ;
148
+ let if_let_expr = make:: expr_if (
149
+ condition,
150
+ then_block,
151
+ else_expr. map ( |else_expr| {
152
+ ast:: ElseBranch :: Block ( make:: block_expr ( iter:: empty ( ) , Some ( else_expr) ) )
153
+ } ) ,
154
+ )
155
+ . indent ( IndentLevel :: from_node ( match_expr. syntax ( ) ) ) ;
156
+
157
+ edit. replace_ast :: < ast:: Expr > ( match_expr. into ( ) , if_let_expr) ;
158
+ } ,
159
+ )
160
+ }
161
+
162
+ fn is_pat_wildcard_or_sad ( sema : & hir:: Semantics < RootDatabase > , pat : & ast:: Pat ) -> bool {
163
+ sema. type_of_pat ( & pat)
164
+ . and_then ( |ty| TryEnum :: from_ty ( sema, & ty) )
165
+ . map ( |it| it. sad_pattern ( ) . syntax ( ) . text ( ) == pat. syntax ( ) . text ( ) )
166
+ . unwrap_or_else ( || matches ! ( pat, ast:: Pat :: WildcardPat ( _) ) )
167
+ }
168
+
82
169
#[ cfg( test) ]
83
170
mod tests {
84
171
use super :: * ;
@@ -249,6 +336,194 @@ fn main() {
249
336
}
250
337
}
251
338
}
339
+ "# ,
340
+ )
341
+ }
342
+
343
+ #[ test]
344
+ fn test_replace_match_with_if_let_unwraps_simple_expressions ( ) {
345
+ check_assist (
346
+ replace_match_with_if_let,
347
+ r#"
348
+ impl VariantData {
349
+ pub fn is_struct(&self) -> bool {
350
+ <|>match *self {
351
+ VariantData::Struct(..) => true,
352
+ _ => false,
353
+ }
354
+ }
355
+ } "# ,
356
+ r#"
357
+ impl VariantData {
358
+ pub fn is_struct(&self) -> bool {
359
+ if let VariantData::Struct(..) = *self {
360
+ true
361
+ } else {
362
+ false
363
+ }
364
+ }
365
+ } "# ,
366
+ )
367
+ }
368
+
369
+ #[ test]
370
+ fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions ( ) {
371
+ check_assist (
372
+ replace_match_with_if_let,
373
+ r#"
374
+ fn foo() {
375
+ <|>match a {
376
+ VariantData::Struct(..) => {
377
+ bar(
378
+ 123
379
+ )
380
+ }
381
+ _ => false,
382
+ }
383
+ } "# ,
384
+ r#"
385
+ fn foo() {
386
+ if let VariantData::Struct(..) = a {
387
+ bar(
388
+ 123
389
+ )
390
+ } else {
391
+ false
392
+ }
393
+ } "# ,
394
+ )
395
+ }
396
+
397
+ #[ test]
398
+ fn replace_match_with_if_let_target ( ) {
399
+ check_assist_target (
400
+ replace_match_with_if_let,
401
+ r#"
402
+ impl VariantData {
403
+ pub fn is_struct(&self) -> bool {
404
+ <|>match *self {
405
+ VariantData::Struct(..) => true,
406
+ _ => false,
407
+ }
408
+ }
409
+ } "# ,
410
+ r#"match *self {
411
+ VariantData::Struct(..) => true,
412
+ _ => false,
413
+ }"# ,
414
+ ) ;
415
+ }
416
+
417
+ #[ test]
418
+ fn special_case_option_match_to_if_let ( ) {
419
+ check_assist (
420
+ replace_match_with_if_let,
421
+ r#"
422
+ enum Option<T> { Some(T), None }
423
+ use Option::*;
424
+
425
+ fn foo(x: Option<i32>) {
426
+ <|>match x {
427
+ Some(x) => println!("{}", x),
428
+ None => println!("none"),
429
+ }
430
+ }
431
+ "# ,
432
+ r#"
433
+ enum Option<T> { Some(T), None }
434
+ use Option::*;
435
+
436
+ fn foo(x: Option<i32>) {
437
+ if let Some(x) = x {
438
+ println!("{}", x)
439
+ } else {
440
+ println!("none")
441
+ }
442
+ }
443
+ "# ,
444
+ ) ;
445
+ }
446
+
447
+ #[ test]
448
+ fn special_case_result_match_to_if_let ( ) {
449
+ check_assist (
450
+ replace_match_with_if_let,
451
+ r#"
452
+ enum Result<T, E> { Ok(T), Err(E) }
453
+ use Result::*;
454
+
455
+ fn foo(x: Result<i32, ()>) {
456
+ <|>match x {
457
+ Ok(x) => println!("{}", x),
458
+ Err(_) => println!("none"),
459
+ }
460
+ }
461
+ "# ,
462
+ r#"
463
+ enum Result<T, E> { Ok(T), Err(E) }
464
+ use Result::*;
465
+
466
+ fn foo(x: Result<i32, ()>) {
467
+ if let Ok(x) = x {
468
+ println!("{}", x)
469
+ } else {
470
+ println!("none")
471
+ }
472
+ }
473
+ "# ,
474
+ ) ;
475
+ }
476
+
477
+ #[ test]
478
+ fn nested_indent_match_to_if_let ( ) {
479
+ check_assist (
480
+ replace_match_with_if_let,
481
+ r#"
482
+ fn main() {
483
+ if true {
484
+ <|>match path.strip_prefix(root_path) {
485
+ Ok(rel_path) => {
486
+ let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
487
+ Some((*id, rel_path))
488
+ }
489
+ _ => None,
490
+ }
491
+ }
492
+ }
493
+ "# ,
494
+ r#"
495
+ fn main() {
496
+ if true {
497
+ if let Ok(rel_path) = path.strip_prefix(root_path) {
498
+ let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
499
+ Some((*id, rel_path))
500
+ } else {
501
+ None
502
+ }
503
+ }
504
+ }
505
+ "# ,
506
+ )
507
+ }
508
+
509
+ #[ test]
510
+ fn replace_match_with_if_let_empty_wildcard_expr ( ) {
511
+ check_assist (
512
+ replace_match_with_if_let,
513
+ r#"
514
+ fn main() {
515
+ <|>match path.strip_prefix(root_path) {
516
+ Ok(rel_path) => println!("{}", rel_path),
517
+ _ => (),
518
+ }
519
+ }
520
+ "# ,
521
+ r#"
522
+ fn main() {
523
+ if let Ok(rel_path) = path.strip_prefix(root_path) {
524
+ println!("{}", rel_path)
525
+ }
526
+ }
252
527
"# ,
253
528
)
254
529
}
0 commit comments