ide_assists/handlers/
convert_let_else_to_match.rs

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