Skip to main content

ide_assists/handlers/
convert_let_else_to_match.rs

1use syntax::T;
2use syntax::ast::RangeItem;
3use syntax::ast::edit::AstNodeEdit;
4use syntax::ast::{self, AstNode, HasName, LetStmt, Pat};
5use syntax::syntax_editor::SyntaxEditor;
6
7use crate::{AssistContext, AssistId, Assists};
8
9// Assist: convert_let_else_to_match
10//
11// Converts let-else statement to let statement and match expression.
12//
13// ```
14// fn main() {
15//     let Ok(mut x) = f() else$0 { return };
16// }
17// ```
18// ->
19// ```
20// fn main() {
21//     let mut x = match f() {
22//         Ok(x) => x,
23//         _ => return,
24//     };
25// }
26// ```
27pub(crate) fn convert_let_else_to_match(
28    acc: &mut Assists,
29    ctx: &AssistContext<'_, '_>,
30) -> Option<()> {
31    let (editor, _) = SyntaxEditor::new(ctx.source_file().syntax().clone());
32    // Should focus on the `else` token to trigger
33    let let_stmt = ctx
34        .find_token_syntax_at_offset(T![else])
35        .and_then(|it| it.parent()?.parent())
36        .or_else(|| ctx.find_token_syntax_at_offset(T![let])?.parent())?;
37    let let_stmt = LetStmt::cast(let_stmt)?;
38    let else_block = let_stmt.let_else()?.block_expr()?;
39    let else_expr = if else_block.statements().next().is_none()
40        && let Some(tail_expr) = else_block.tail_expr()
41    {
42        tail_expr.reset_indent()
43    } else {
44        else_block.reset_indent().into()
45    };
46    let init = let_stmt.initializer()?;
47    // Ignore let stmt with type annotation
48    if let_stmt.ty().is_some() {
49        return None;
50    }
51    let pat = let_stmt.pat()?;
52    let mut idents = Vec::default();
53    let pat_without_mut = remove_mut_and_collect_idents(&editor, &pat, &mut idents)?;
54    let bindings = idents
55        .into_iter()
56        .filter_map(|ref pat| {
57            // Identifiers which resolve to constants are not bindings
58            if ctx.sema.resolve_bind_pat_to_const(pat).is_none() {
59                Some((pat.name()?, pat.ref_token().is_none() && pat.mut_token().is_some()))
60            } else {
61                None
62            }
63        })
64        .collect::<Vec<_>>();
65
66    acc.add(
67        AssistId::refactor_rewrite("convert_let_else_to_match"),
68        if bindings.is_empty() {
69            "Convert let-else to match"
70        } else {
71            "Convert let-else to let and match"
72        },
73        let_stmt.syntax().text_range(),
74        |builder| {
75            let make = editor.make();
76            let binding_paths = bindings
77                .iter()
78                .map(|(name, _)| make.expr_path(make.ident_path(&name.to_string())))
79                .collect::<Vec<_>>();
80
81            let binding_arm = make.match_arm(
82                pat_without_mut,
83                None,
84                // There are three possible cases:
85                //
86                // - No bindings: `None => {}`
87                // - Single binding: `Some(it) => it`
88                // - Multiple bindings: `Foo::Bar { a, b, .. } => (a, b)`
89                match binding_paths.len() {
90                    0 => make.expr_empty_block().into(),
91
92                    1 => binding_paths[0].clone(),
93                    _ => make.expr_tuple(binding_paths).into(),
94                },
95            );
96            let else_arm = make.match_arm(make.wildcard_pat().into(), None, else_expr);
97            let arms = [binding_arm, else_arm].map(|arm| arm.indent(1.into()));
98            let match_ = make.expr_match(init, make.match_arm_list(arms));
99            let match_ = match_.indent(let_stmt.indent_level());
100
101            if bindings.is_empty() {
102                editor.replace(let_stmt.syntax(), match_.syntax());
103            } else {
104                let ident_pats = bindings
105                    .into_iter()
106                    .map(|(name, is_mut)| make.ident_pat(false, is_mut, name).into())
107                    .collect::<Vec<Pat>>();
108                let new_let_stmt = make.let_stmt(
109                    if ident_pats.len() == 1 {
110                        ident_pats[0].clone()
111                    } else {
112                        make.tuple_pat(ident_pats).into()
113                    },
114                    None,
115                    Some(match_.into()),
116                );
117                editor.replace(let_stmt.syntax(), new_let_stmt.syntax());
118            }
119            builder.add_file_edits(ctx.vfs_file_id(), editor);
120        },
121    )
122}
123
124fn remove_mut_and_collect_idents(
125    editor: &SyntaxEditor,
126    pat: &ast::Pat,
127    acc: &mut Vec<ast::IdentPat>,
128) -> Option<ast::Pat> {
129    let make = editor.make();
130    Some(match pat {
131        ast::Pat::IdentPat(p) => {
132            acc.push(p.clone());
133            let non_mut_pat = make.ident_pat(
134                p.ref_token().is_some(),
135                p.ref_token().is_some() && p.mut_token().is_some(),
136                p.name()?,
137            );
138            let non_mut_pat = if let Some(inner) = p.pat() {
139                non_mut_pat.set_pat(remove_mut_and_collect_idents(editor, &inner, acc), editor)
140            } else {
141                non_mut_pat
142            };
143            non_mut_pat.into()
144        }
145        ast::Pat::BoxPat(p) => {
146            let pat = remove_mut_and_collect_idents(editor, &p.pat()?, acc)?;
147            make.box_pat(pat).into()
148        }
149        ast::Pat::DerefPat(p) => {
150            let pat = remove_mut_and_collect_idents(editor, &p.pat()?, acc)?;
151            make.deref_pat(pat)
152        }
153        ast::Pat::OrPat(p) => {
154            let pats = p
155                .pats()
156                .map(|pat| remove_mut_and_collect_idents(editor, &pat, acc))
157                .collect::<Option<Vec<_>>>()?;
158            make.or_pat(pats, p.leading_pipe().is_some()).into()
159        }
160        ast::Pat::ParenPat(p) => {
161            let pat = remove_mut_and_collect_idents(editor, &p.pat()?, acc)?;
162            make.paren_pat(pat).into()
163        }
164        ast::Pat::RangePat(p) => {
165            let start = if let Some(start) = p.start() {
166                Some(remove_mut_and_collect_idents(editor, &start, acc)?)
167            } else {
168                None
169            };
170            let end = if let Some(end) = p.end() {
171                Some(remove_mut_and_collect_idents(editor, &end, acc)?)
172            } else {
173                None
174            };
175            make.range_pat(start, end).into()
176        }
177        ast::Pat::RecordPat(p) => {
178            let fields = p
179                .record_pat_field_list()?
180                .fields()
181                .map(|field| {
182                    remove_mut_and_collect_idents(editor, &field.pat()?, acc).map(|pat| {
183                        if let Some(name_ref) = field.name_ref() {
184                            make.record_pat_field(name_ref, pat)
185                        } else {
186                            make.record_pat_field_shorthand(pat)
187                        }
188                    })
189                })
190                .collect::<Option<Vec<_>>>()?;
191            make.record_pat_with_fields(
192                p.path()?,
193                make.record_pat_field_list(fields, p.record_pat_field_list()?.rest_pat()),
194            )
195            .into()
196        }
197        ast::Pat::RefPat(p) => {
198            let inner = p.pat()?;
199            if let ast::Pat::IdentPat(ident) = inner {
200                acc.push(ident);
201                p.clone().into()
202            } else {
203                let pat = remove_mut_and_collect_idents(editor, &inner, acc)?;
204                make.ref_pat(pat).into()
205            }
206        }
207        ast::Pat::SlicePat(p) => {
208            let pats = p
209                .pats()
210                .map(|pat| remove_mut_and_collect_idents(editor, &pat, acc))
211                .collect::<Option<Vec<_>>>()?;
212            make.slice_pat(pats).into()
213        }
214        ast::Pat::TuplePat(p) => {
215            let pats = p
216                .fields()
217                .map(|field| remove_mut_and_collect_idents(editor, &field, acc))
218                .collect::<Option<Vec<_>>>()?;
219            make.tuple_pat(pats).into()
220        }
221        ast::Pat::TupleStructPat(p) => {
222            let fields = p
223                .fields()
224                .map(|field| remove_mut_and_collect_idents(editor, &field, acc))
225                .collect::<Option<Vec<_>>>()?;
226            make.tuple_struct_pat(p.path()?, fields).into()
227        }
228        ast::Pat::RestPat(_)
229        | ast::Pat::LiteralPat(_)
230        | ast::Pat::PathPat(_)
231        | ast::Pat::WildcardPat(_)
232        | ast::Pat::NotNull(_)
233        | ast::Pat::ConstBlockPat(_) => pat.clone(),
234        // don't support macro pat yet
235        ast::Pat::MacroPat(_) => return None,
236    })
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
244
245    #[test]
246    fn convert_let_else_to_match_no_type_let() {
247        check_assist_not_applicable(
248            convert_let_else_to_match,
249            r#"
250fn main() {
251    let 1: u32 = v.iter().sum() else$0 { return };
252}"#,
253        );
254    }
255
256    #[test]
257    fn convert_let_else_to_match_on_else() {
258        check_assist_not_applicable(
259            convert_let_else_to_match,
260            r#"
261fn main() {
262    let Ok(x) = f() else {$0 return };
263}
264            "#,
265        );
266    }
267
268    #[test]
269    fn convert_let_else_to_match_no_macropat() {
270        check_assist_not_applicable(
271            convert_let_else_to_match,
272            r#"
273fn main() {
274    let m!() = g() else$0 { return };
275}
276            "#,
277        );
278    }
279
280    #[test]
281    fn convert_let_else_to_match_target() {
282        check_assist_target(
283            convert_let_else_to_match,
284            r"
285fn main() {
286    let Ok(x) = f() else$0 { continue };
287}",
288            "let Ok(x) = f() else { continue };",
289        );
290    }
291
292    #[test]
293    fn convert_let_else_to_match_basic() {
294        check_assist(
295            convert_let_else_to_match,
296            r"
297fn main() {
298    let Ok(x) = f() else$0 { continue };
299}",
300            r"
301fn main() {
302    let x = match f() {
303        Ok(x) => x,
304        _ => continue,
305    };
306}",
307        );
308    }
309
310    #[test]
311    fn convert_let_else_to_match_with_empty_else_block() {
312        check_assist(
313            convert_let_else_to_match,
314            r"
315fn main() {
316    let Ok(x) = f() else$0 {};
317}",
318            r"
319fn main() {
320    let x = match f() {
321        Ok(x) => x,
322        _ => {}
323    };
324}",
325        );
326    }
327
328    #[test]
329    fn convert_let_else_to_match_with_some_indent() {
330        check_assist(
331            convert_let_else_to_match,
332            r#"
333mod indent {
334    fn main() {
335        let Ok(x) = f() else$0 {
336            log();
337            unreachable!(
338                "..."
339            );
340        };
341    }
342}"#,
343            r#"
344mod indent {
345    fn main() {
346        let x = match f() {
347            Ok(x) => x,
348            _ => {
349                log();
350                unreachable!(
351                    "..."
352                );
353            }
354        };
355    }
356}"#,
357        );
358
359        check_assist(
360            convert_let_else_to_match,
361            r#"
362mod indent {
363    fn main() {
364        let Ok(x) = f() else$0 {
365            unreachable!(
366                "..."
367            )
368        };
369    }
370}"#,
371            r#"
372mod indent {
373    fn main() {
374        let x = match f() {
375            Ok(x) => x,
376            _ => unreachable!(
377                "..."
378            ),
379        };
380    }
381}"#,
382        );
383    }
384
385    #[test]
386    fn convert_let_else_to_match_const_ref() {
387        check_assist(
388            convert_let_else_to_match,
389            r"
390enum Option<T> {
391    Some(T),
392    None,
393}
394use Option::*;
395fn main() {
396    let None = f() el$0se { continue };
397}",
398            r"
399enum Option<T> {
400    Some(T),
401    None,
402}
403use Option::*;
404fn main() {
405    match f() {
406        None => {}
407        _ => continue,
408    }
409}",
410        );
411    }
412
413    #[test]
414    fn convert_let_else_to_match_const_ref_const() {
415        check_assist(
416            convert_let_else_to_match,
417            r"
418const NEG1: i32 = -1;
419fn main() {
420    let NEG1 = f() el$0se { continue };
421}",
422            r"
423const NEG1: i32 = -1;
424fn main() {
425    match f() {
426        NEG1 => {}
427        _ => continue,
428    }
429}",
430        );
431    }
432
433    #[test]
434    fn convert_let_else_to_match_mut() {
435        check_assist(
436            convert_let_else_to_match,
437            r"
438fn main() {
439    let Ok(mut x) = f() el$0se { continue };
440}",
441            r"
442fn main() {
443    let mut x = match f() {
444        Ok(x) => x,
445        _ => continue,
446    };
447}",
448        );
449    }
450
451    #[test]
452    fn convert_let_else_to_match_multi_binders() {
453        check_assist(
454            convert_let_else_to_match,
455            r#"
456fn main() {
457    let ControlFlow::Break((x, "tag", y, ..)) = f() else$0 { g(); return };
458}"#,
459            r#"
460fn main() {
461    let (x, y) = match f() {
462        ControlFlow::Break((x, "tag", y, ..)) => (x, y),
463        _ => { g(); return }
464    };
465}"#,
466        );
467    }
468
469    #[test]
470    fn convert_let_else_to_match_slice() {
471        check_assist(
472            convert_let_else_to_match,
473            r#"
474fn main() {
475    let [one, 1001, other] = f() else$0 { break };
476}"#,
477            r#"
478fn main() {
479    let (one, other) = match f() {
480        [one, 1001, other] => (one, other),
481        _ => break,
482    };
483}"#,
484        );
485    }
486
487    #[test]
488    fn convert_let_else_to_match_struct() {
489        check_assist(
490            convert_let_else_to_match,
491            r#"
492fn main() {
493    let [Struct { inner: Some(it) }, 1001, other] = f() else$0 { break };
494}"#,
495            r#"
496fn main() {
497    let (it, other) = match f() {
498        [Struct { inner: Some(it) }, 1001, other] => (it, other),
499        _ => break,
500    };
501}"#,
502        );
503    }
504
505    #[test]
506    fn convert_let_else_to_match_struct_ident_pat() {
507        check_assist(
508            convert_let_else_to_match,
509            r#"
510fn main() {
511    let [Struct { inner }, 1001, other] = f() else$0 { break };
512}"#,
513            r#"
514fn main() {
515    let (inner, other) = match f() {
516        [Struct { inner }, 1001, other] => (inner, other),
517        _ => break,
518    };
519}"#,
520        );
521    }
522
523    #[test]
524    fn convert_let_else_to_match_no_binder() {
525        check_assist(
526            convert_let_else_to_match,
527            r#"
528fn main() {
529    let (8 | 9) = f() else$0 { panic!() };
530}"#,
531            r#"
532fn main() {
533    match f() {
534        (8 | 9) => {}
535        _ => panic!(),
536    }
537}"#,
538        );
539    }
540
541    #[test]
542    fn convert_let_else_to_match_range() {
543        check_assist(
544            convert_let_else_to_match,
545            r#"
546fn main() {
547    let 1.. = f() e$0lse { return };
548}"#,
549            r#"
550fn main() {
551    match f() {
552        1.. => {}
553        _ => return,
554    }
555}"#,
556        );
557    }
558
559    #[test]
560    fn convert_let_else_to_match_refpat() {
561        check_assist(
562            convert_let_else_to_match,
563            r#"
564fn main() {
565    let Ok(&mut x) = f(&mut 0) else$0 { return };
566}"#,
567            r#"
568fn main() {
569    let x = match f(&mut 0) {
570        Ok(&mut x) => x,
571        _ => return,
572    };
573}"#,
574        );
575    }
576
577    #[test]
578    fn convert_let_else_to_match_refmut() {
579        check_assist(
580            convert_let_else_to_match,
581            r#"
582fn main() {
583    let Ok(ref mut x) = f() else$0 { return };
584}"#,
585            r#"
586fn main() {
587    let x = match f() {
588        Ok(ref mut x) => x,
589        _ => return,
590    };
591}"#,
592        );
593    }
594
595    #[test]
596    fn convert_let_else_to_match_atpat() {
597        check_assist(
598            convert_let_else_to_match,
599            r#"
600fn main() {
601    let out @ Ok(ins) = f() else$0 { return };
602}"#,
603            r#"
604fn main() {
605    let (out, ins) = match f() {
606        out @ Ok(ins) => (out, ins),
607        _ => return,
608    };
609}"#,
610        );
611    }
612
613    #[test]
614    fn convert_let_else_to_match_complex_init() {
615        check_assist(
616            convert_let_else_to_match,
617            r#"
618fn main() {
619    let v = vec![1, 2, 3];
620    let &[mut x, y, ..] = &v.iter().collect::<Vec<_>>()[..] else$0 { return };
621}"#,
622            r#"
623fn main() {
624    let v = vec![1, 2, 3];
625    let (mut x, y) = match &v.iter().collect::<Vec<_>>()[..] {
626        &[x, y, ..] => (x, y),
627        _ => return,
628    };
629}"#,
630        );
631    }
632}