]> git.lizzy.rs Git - rust.git/blob - crates/ra_assists/src/handlers/fill_match_arms.rs
Merge #3591
[rust.git] / crates / ra_assists / src / handlers / fill_match_arms.rs
1 //! FIXME: write short doc here
2
3 use std::iter;
4
5 use hir::{Adt, HasSource, Semantics};
6 use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner};
7
8 use crate::{Assist, AssistCtx, AssistId};
9 use ra_ide_db::RootDatabase;
10
11 // Assist: fill_match_arms
12 //
13 // Adds missing clauses to a `match` expression.
14 //
15 // ```
16 // enum Action { Move { distance: u32 }, Stop }
17 //
18 // fn handle(action: Action) {
19 //     match action {
20 //         <|>
21 //     }
22 // }
23 // ```
24 // ->
25 // ```
26 // enum Action { Move { distance: u32 }, Stop }
27 //
28 // fn handle(action: Action) {
29 //     match action {
30 //         Action::Move { distance } => (),
31 //         Action::Stop => (),
32 //     }
33 // }
34 // ```
35 pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
36     let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
37     let match_arm_list = match_expr.match_arm_list()?;
38
39     // We already have some match arms, so we don't provide any assists.
40     // Unless if there is only one trivial match arm possibly created
41     // by match postfix complete. Trivial match arm is the catch all arm.
42     let mut existing_arms = match_arm_list.arms();
43     if let Some(arm) = existing_arms.next() {
44         if !is_trivial(&arm) || existing_arms.next().is_some() {
45             return None;
46         }
47     };
48
49     let expr = match_expr.expr()?;
50     let enum_def = resolve_enum_def(&ctx.sema, &expr)?;
51     let module = ctx.sema.scope(expr.syntax()).module()?;
52
53     let variants = enum_def.variants(ctx.db);
54     if variants.is_empty() {
55         return None;
56     }
57
58     let db = ctx.db;
59
60     ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| {
61         let indent_level = IndentLevel::from_node(match_arm_list.syntax());
62
63         let new_arm_list = {
64             let arms = variants
65                 .into_iter()
66                 .filter_map(|variant| build_pat(db, module, variant))
67                 .map(|pat| make::match_arm(iter::once(pat), make::expr_unit()));
68             indent_level.increase_indent(make::match_arm_list(arms))
69         };
70
71         edit.target(match_expr.syntax().text_range());
72         edit.set_cursor(expr.syntax().text_range().start());
73         edit.replace_ast(match_arm_list, new_arm_list);
74     })
75 }
76
77 fn is_trivial(arm: &ast::MatchArm) -> bool {
78     match arm.pat() {
79         Some(ast::Pat::PlaceholderPat(..)) => true,
80         _ => false,
81     }
82 }
83
84 fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> {
85     sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
86         Some(Adt::Enum(e)) => Some(e),
87         _ => None,
88     })
89 }
90
91 fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> {
92     let path = crate::ast_transform::path_to_ast(module.find_use_path(db, var.into())?);
93
94     // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
95     let pat: ast::Pat = match var.source(db).value.kind() {
96         ast::StructKind::Tuple(field_list) => {
97             let pats =
98                 iter::repeat(make::placeholder_pat().into()).take(field_list.fields().count());
99             make::tuple_struct_pat(path, pats).into()
100         }
101         ast::StructKind::Record(field_list) => {
102             let pats = field_list.fields().map(|f| make::bind_pat(f.name().unwrap()).into());
103             make::record_pat(path, pats).into()
104         }
105         ast::StructKind::Unit => make::path_pat(path),
106     };
107
108     Some(pat)
109 }
110
111 #[cfg(test)]
112 mod tests {
113     use crate::helpers::{check_assist, check_assist_target};
114
115     use super::fill_match_arms;
116
117     #[test]
118     fn fill_match_arms_empty_body() {
119         check_assist(
120             fill_match_arms,
121             r#"
122             enum A {
123                 As,
124                 Bs,
125                 Cs(String),
126                 Ds(String, String),
127                 Es{ x: usize, y: usize }
128             }
129
130             fn main() {
131                 let a = A::As;
132                 match a<|> {}
133             }
134             "#,
135             r#"
136             enum A {
137                 As,
138                 Bs,
139                 Cs(String),
140                 Ds(String, String),
141                 Es{ x: usize, y: usize }
142             }
143
144             fn main() {
145                 let a = A::As;
146                 match <|>a {
147                     A::As => (),
148                     A::Bs => (),
149                     A::Cs(_) => (),
150                     A::Ds(_, _) => (),
151                     A::Es { x, y } => (),
152                 }
153             }
154             "#,
155         );
156     }
157
158     #[test]
159     fn test_fill_match_arm_refs() {
160         check_assist(
161             fill_match_arms,
162             r#"
163             enum A {
164                 As,
165             }
166
167             fn foo(a: &A) {
168                 match a<|> {
169                 }
170             }
171             "#,
172             r#"
173             enum A {
174                 As,
175             }
176
177             fn foo(a: &A) {
178                 match <|>a {
179                     A::As => (),
180                 }
181             }
182             "#,
183         );
184
185         check_assist(
186             fill_match_arms,
187             r#"
188             enum A {
189                 Es{ x: usize, y: usize }
190             }
191
192             fn foo(a: &mut A) {
193                 match a<|> {
194                 }
195             }
196             "#,
197             r#"
198             enum A {
199                 Es{ x: usize, y: usize }
200             }
201
202             fn foo(a: &mut A) {
203                 match <|>a {
204                     A::Es { x, y } => (),
205                 }
206             }
207             "#,
208         );
209     }
210
211     #[test]
212     fn fill_match_arms_target() {
213         check_assist_target(
214             fill_match_arms,
215             r#"
216             enum E { X, Y }
217
218             fn main() {
219                 match E::X<|> {}
220             }
221             "#,
222             "match E::X {}",
223         );
224     }
225
226     #[test]
227     fn fill_match_arms_trivial_arm() {
228         check_assist(
229             fill_match_arms,
230             r#"
231             enum E { X, Y }
232
233             fn main() {
234                 match E::X {
235                     <|>_ => {},
236                 }
237             }
238             "#,
239             r#"
240             enum E { X, Y }
241
242             fn main() {
243                 match <|>E::X {
244                     E::X => (),
245                     E::Y => (),
246                 }
247             }
248             "#,
249         );
250     }
251
252     #[test]
253     fn fill_match_arms_qualifies_path() {
254         check_assist(
255             fill_match_arms,
256             r#"
257             mod foo { pub enum E { X, Y } }
258             use foo::E::X;
259
260             fn main() {
261                 match X {
262                     <|>
263                 }
264             }
265             "#,
266             r#"
267             mod foo { pub enum E { X, Y } }
268             use foo::E::X;
269
270             fn main() {
271                 match <|>X {
272                     X => (),
273                     foo::E::Y => (),
274                 }
275             }
276             "#,
277         );
278     }
279 }