]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs
Rollup merge of #98391 - joboet:sgx_parker, r=m-ou-se
[rust.git] / src / tools / rust-analyzer / crates / ide-assists / src / handlers / convert_let_else_to_match.rs
1 use hir::Semantics;
2 use ide_db::RootDatabase;
3 use syntax::ast::{edit::AstNodeEdit, AstNode, HasName, LetStmt, Name, Pat};
4 use syntax::T;
5
6 use crate::{AssistContext, AssistId, AssistKind, Assists};
7
8 /// Gets a list of binders in a pattern, and whether they are mut.
9 fn binders_in_pat(
10     acc: &mut Vec<(Name, bool)>,
11     pat: &Pat,
12     sem: &Semantics<'_, RootDatabase>,
13 ) -> Option<()> {
14     use Pat::*;
15     match pat {
16         IdentPat(p) => {
17             let ident = p.name()?;
18             let ismut = p.ref_token().is_none() && p.mut_token().is_some();
19             // check for const reference
20             if sem.resolve_bind_pat_to_const(p).is_none() {
21                 acc.push((ident, ismut));
22             }
23             if let Some(inner) = p.pat() {
24                 binders_in_pat(acc, &inner, sem)?;
25             }
26             Some(())
27         }
28         BoxPat(p) => p.pat().and_then(|p| binders_in_pat(acc, &p, sem)),
29         RestPat(_) | LiteralPat(_) | PathPat(_) | WildcardPat(_) | ConstBlockPat(_) => Some(()),
30         OrPat(p) => {
31             for p in p.pats() {
32                 binders_in_pat(acc, &p, sem)?;
33             }
34             Some(())
35         }
36         ParenPat(p) => p.pat().and_then(|p| binders_in_pat(acc, &p, sem)),
37         RangePat(p) => {
38             if let Some(st) = p.start() {
39                 binders_in_pat(acc, &st, sem)?
40             }
41             if let Some(ed) = p.end() {
42                 binders_in_pat(acc, &ed, sem)?
43             }
44             Some(())
45         }
46         RecordPat(p) => {
47             for f in p.record_pat_field_list()?.fields() {
48                 let pat = f.pat()?;
49                 binders_in_pat(acc, &pat, sem)?;
50             }
51             Some(())
52         }
53         RefPat(p) => p.pat().and_then(|p| binders_in_pat(acc, &p, sem)),
54         SlicePat(p) => {
55             for p in p.pats() {
56                 binders_in_pat(acc, &p, sem)?;
57             }
58             Some(())
59         }
60         TuplePat(p) => {
61             for p in p.fields() {
62                 binders_in_pat(acc, &p, sem)?;
63             }
64             Some(())
65         }
66         TupleStructPat(p) => {
67             for p in p.fields() {
68                 binders_in_pat(acc, &p, sem)?;
69             }
70             Some(())
71         }
72         // don't support macro pat yet
73         MacroPat(_) => None,
74     }
75 }
76
77 fn binders_to_str(binders: &[(Name, bool)], addmut: bool) -> String {
78     let vars = binders
79         .iter()
80         .map(
81             |(ident, ismut)| {
82                 if *ismut && addmut {
83                     format!("mut {ident}")
84                 } else {
85                     ident.to_string()
86                 }
87             },
88         )
89         .collect::<Vec<_>>()
90         .join(", ");
91     if binders.is_empty() {
92         String::from("{}")
93     } else if binders.len() == 1 {
94         vars
95     } else {
96         format!("({vars})")
97     }
98 }
99
100 // Assist: convert_let_else_to_match
101 //
102 // Converts let-else statement to let statement and match expression.
103 //
104 // ```
105 // fn main() {
106 //     let Ok(mut x) = f() else$0 { return };
107 // }
108 // ```
109 // ->
110 // ```
111 // fn main() {
112 //     let mut x = match f() {
113 //         Ok(x) => x,
114 //         _ => return,
115 //     };
116 // }
117 // ```
118 pub(crate) fn convert_let_else_to_match(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
119     // should focus on else token to trigger
120     let else_token = ctx.find_token_syntax_at_offset(T![else])?;
121     let let_stmt = LetStmt::cast(else_token.parent()?.parent()?)?;
122     let let_else_block = let_stmt.let_else()?.block_expr()?;
123     let let_init = let_stmt.initializer()?;
124     if let_stmt.ty().is_some() {
125         // don't support let with type annotation
126         return None;
127     }
128     let pat = let_stmt.pat()?;
129     let mut binders = Vec::new();
130     binders_in_pat(&mut binders, &pat, &ctx.sema)?;
131
132     let target = let_stmt.syntax().text_range();
133     acc.add(
134         AssistId("convert_let_else_to_match", AssistKind::RefactorRewrite),
135         "Convert let-else to let and match",
136         target,
137         |edit| {
138             let indent_level = let_stmt.indent_level().0 as usize;
139             let indent = "    ".repeat(indent_level);
140             let indent1 = "    ".repeat(indent_level + 1);
141
142             let binders_str = binders_to_str(&binders, false);
143             let binders_str_mut = binders_to_str(&binders, true);
144
145             let init_expr = let_init.syntax().text();
146             let mut pat_no_mut = pat.syntax().text().to_string();
147             // remove the mut from the pattern
148             for (b, ismut) in binders.iter() {
149                 if *ismut {
150                     pat_no_mut = pat_no_mut.replace(&format!("mut {b}"), &b.to_string());
151                 }
152             }
153
154             let only_expr = let_else_block.statements().next().is_none();
155             let branch2 = match &let_else_block.tail_expr() {
156                 Some(tail) if only_expr => format!("{tail},"),
157                 _ => let_else_block.syntax().text().to_string(),
158             };
159             let replace = if binders.is_empty() {
160                 format!(
161                     "match {init_expr} {{
162 {indent1}{pat_no_mut} => {binders_str}
163 {indent1}_ => {branch2}
164 {indent}}}"
165                 )
166             } else {
167                 format!(
168                     "let {binders_str_mut} = match {init_expr} {{
169 {indent1}{pat_no_mut} => {binders_str},
170 {indent1}_ => {branch2}
171 {indent}}};"
172                 )
173             };
174             edit.replace(target, replace);
175         },
176     )
177 }
178
179 #[cfg(test)]
180 mod tests {
181     use super::*;
182
183     use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
184
185     #[test]
186     fn convert_let_else_to_match_no_type_let() {
187         check_assist_not_applicable(
188             convert_let_else_to_match,
189             r#"
190 fn main() {
191     let 1: u32 = v.iter().sum() else$0 { return };
192 }"#,
193         );
194     }
195
196     #[test]
197     fn convert_let_else_to_match_on_else() {
198         check_assist_not_applicable(
199             convert_let_else_to_match,
200             r#"
201 fn main() {
202     let Ok(x) = f() else {$0 return };
203 }
204             "#,
205         );
206     }
207
208     #[test]
209     fn convert_let_else_to_match_no_macropat() {
210         check_assist_not_applicable(
211             convert_let_else_to_match,
212             r#"
213 fn main() {
214     let m!() = g() else$0 { return };
215 }
216             "#,
217         );
218     }
219
220     #[test]
221     fn convert_let_else_to_match_target() {
222         check_assist_target(
223             convert_let_else_to_match,
224             r"
225 fn main() {
226     let Ok(x) = f() else$0 { continue };
227 }",
228             "let Ok(x) = f() else { continue };",
229         );
230     }
231
232     #[test]
233     fn convert_let_else_to_match_basic() {
234         check_assist(
235             convert_let_else_to_match,
236             r"
237 fn main() {
238     let Ok(x) = f() else$0 { continue };
239 }",
240             r"
241 fn main() {
242     let x = match f() {
243         Ok(x) => x,
244         _ => continue,
245     };
246 }",
247         );
248     }
249
250     #[test]
251     fn convert_let_else_to_match_const_ref() {
252         check_assist(
253             convert_let_else_to_match,
254             r"
255 enum Option<T> {
256     Some(T),
257     None,
258 }
259 use Option::*;
260 fn main() {
261     let None = f() el$0se { continue };
262 }",
263             r"
264 enum Option<T> {
265     Some(T),
266     None,
267 }
268 use Option::*;
269 fn main() {
270     match f() {
271         None => {}
272         _ => continue,
273     }
274 }",
275         );
276     }
277
278     #[test]
279     fn convert_let_else_to_match_const_ref_const() {
280         check_assist(
281             convert_let_else_to_match,
282             r"
283 const NEG1: i32 = -1;
284 fn main() {
285     let NEG1 = f() el$0se { continue };
286 }",
287             r"
288 const NEG1: i32 = -1;
289 fn main() {
290     match f() {
291         NEG1 => {}
292         _ => continue,
293     }
294 }",
295         );
296     }
297
298     #[test]
299     fn convert_let_else_to_match_mut() {
300         check_assist(
301             convert_let_else_to_match,
302             r"
303 fn main() {
304     let Ok(mut x) = f() el$0se { continue };
305 }",
306             r"
307 fn main() {
308     let mut x = match f() {
309         Ok(x) => x,
310         _ => continue,
311     };
312 }",
313         );
314     }
315
316     #[test]
317     fn convert_let_else_to_match_multi_binders() {
318         check_assist(
319             convert_let_else_to_match,
320             r#"
321 fn main() {
322     let ControlFlow::Break((x, "tag", y, ..)) = f() else$0 { g(); return };
323 }"#,
324             r#"
325 fn main() {
326     let (x, y) = match f() {
327         ControlFlow::Break((x, "tag", y, ..)) => (x, y),
328         _ => { g(); return }
329     };
330 }"#,
331         );
332     }
333
334     #[test]
335     fn convert_let_else_to_match_slice() {
336         check_assist(
337             convert_let_else_to_match,
338             r#"
339 fn main() {
340     let [one, 1001, other] = f() else$0 { break };
341 }"#,
342             r#"
343 fn main() {
344     let (one, other) = match f() {
345         [one, 1001, other] => (one, other),
346         _ => break,
347     };
348 }"#,
349         );
350     }
351
352     #[test]
353     fn convert_let_else_to_match_struct() {
354         check_assist(
355             convert_let_else_to_match,
356             r#"
357 fn main() {
358     let [Struct { inner: Some(it) }, 1001, other] = f() else$0 { break };
359 }"#,
360             r#"
361 fn main() {
362     let (it, other) = match f() {
363         [Struct { inner: Some(it) }, 1001, other] => (it, other),
364         _ => break,
365     };
366 }"#,
367         );
368     }
369
370     #[test]
371     fn convert_let_else_to_match_struct_ident_pat() {
372         check_assist(
373             convert_let_else_to_match,
374             r#"
375 fn main() {
376     let [Struct { inner }, 1001, other] = f() else$0 { break };
377 }"#,
378             r#"
379 fn main() {
380     let (inner, other) = match f() {
381         [Struct { inner }, 1001, other] => (inner, other),
382         _ => break,
383     };
384 }"#,
385         );
386     }
387
388     #[test]
389     fn convert_let_else_to_match_no_binder() {
390         check_assist(
391             convert_let_else_to_match,
392             r#"
393 fn main() {
394     let (8 | 9) = f() else$0 { panic!() };
395 }"#,
396             r#"
397 fn main() {
398     match f() {
399         (8 | 9) => {}
400         _ => panic!(),
401     }
402 }"#,
403         );
404     }
405
406     #[test]
407     fn convert_let_else_to_match_range() {
408         check_assist(
409             convert_let_else_to_match,
410             r#"
411 fn main() {
412     let 1.. = f() e$0lse { return };
413 }"#,
414             r#"
415 fn main() {
416     match f() {
417         1.. => {}
418         _ => return,
419     }
420 }"#,
421         );
422     }
423
424     #[test]
425     fn convert_let_else_to_match_refpat() {
426         check_assist(
427             convert_let_else_to_match,
428             r#"
429 fn main() {
430     let Ok(&mut x) = f(&mut 0) else$0 { return };
431 }"#,
432             r#"
433 fn main() {
434     let x = match f(&mut 0) {
435         Ok(&mut x) => x,
436         _ => return,
437     };
438 }"#,
439         );
440     }
441
442     #[test]
443     fn convert_let_else_to_match_refmut() {
444         check_assist(
445             convert_let_else_to_match,
446             r#"
447 fn main() {
448     let Ok(ref mut x) = f() else$0 { return };
449 }"#,
450             r#"
451 fn main() {
452     let x = match f() {
453         Ok(ref mut x) => x,
454         _ => return,
455     };
456 }"#,
457         );
458     }
459
460     #[test]
461     fn convert_let_else_to_match_atpat() {
462         check_assist(
463             convert_let_else_to_match,
464             r#"
465 fn main() {
466     let out @ Ok(ins) = f() else$0 { return };
467 }"#,
468             r#"
469 fn main() {
470     let (out, ins) = match f() {
471         out @ Ok(ins) => (out, ins),
472         _ => return,
473     };
474 }"#,
475         );
476     }
477
478     #[test]
479     fn convert_let_else_to_match_complex_init() {
480         check_assist(
481             convert_let_else_to_match,
482             r#"
483 fn main() {
484     let v = vec![1, 2, 3];
485     let &[mut x, y, ..] = &v.iter().collect::<Vec<_>>()[..] else$0 { return };
486 }"#,
487             r#"
488 fn main() {
489     let v = vec![1, 2, 3];
490     let (mut x, y) = match &v.iter().collect::<Vec<_>>()[..] {
491         &[x, y, ..] => (x, y),
492         _ => return,
493     };
494 }"#,
495         );
496     }
497 }