]> git.lizzy.rs Git - rust.git/blob - crates/ra_assists/src/handlers/fill_match_arms.rs
Merge pull request #3330 from Veetaha/feature/cargo-audit
[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::{db::HirDatabase, 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(
92     db: &impl HirDatabase,
93     module: hir::Module,
94     var: hir::EnumVariant,
95 ) -> Option<ast::Pat> {
96     let path = crate::ast_transform::path_to_ast(module.find_use_path(db, var.into())?);
97
98     // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
99     let pat: ast::Pat = match var.source(db).value.kind() {
100         ast::StructKind::Tuple(field_list) => {
101             let pats =
102                 iter::repeat(make::placeholder_pat().into()).take(field_list.fields().count());
103             make::tuple_struct_pat(path, pats).into()
104         }
105         ast::StructKind::Record(field_list) => {
106             let pats = field_list.fields().map(|f| make::bind_pat(f.name().unwrap()).into());
107             make::record_pat(path, pats).into()
108         }
109         ast::StructKind::Unit => make::path_pat(path),
110     };
111
112     Some(pat)
113 }
114
115 #[cfg(test)]
116 mod tests {
117     use crate::helpers::{check_assist, check_assist_target};
118
119     use super::fill_match_arms;
120
121     #[test]
122     fn fill_match_arms_empty_body() {
123         check_assist(
124             fill_match_arms,
125             r#"
126             enum A {
127                 As,
128                 Bs,
129                 Cs(String),
130                 Ds(String, String),
131                 Es{ x: usize, y: usize }
132             }
133
134             fn main() {
135                 let a = A::As;
136                 match a<|> {}
137             }
138             "#,
139             r#"
140             enum A {
141                 As,
142                 Bs,
143                 Cs(String),
144                 Ds(String, String),
145                 Es{ x: usize, y: usize }
146             }
147
148             fn main() {
149                 let a = A::As;
150                 match <|>a {
151                     A::As => (),
152                     A::Bs => (),
153                     A::Cs(_) => (),
154                     A::Ds(_, _) => (),
155                     A::Es { x, y } => (),
156                 }
157             }
158             "#,
159         );
160     }
161
162     #[test]
163     fn test_fill_match_arm_refs() {
164         check_assist(
165             fill_match_arms,
166             r#"
167             enum A {
168                 As,
169             }
170
171             fn foo(a: &A) {
172                 match a<|> {
173                 }
174             }
175             "#,
176             r#"
177             enum A {
178                 As,
179             }
180
181             fn foo(a: &A) {
182                 match <|>a {
183                     A::As => (),
184                 }
185             }
186             "#,
187         );
188
189         check_assist(
190             fill_match_arms,
191             r#"
192             enum A {
193                 Es{ x: usize, y: usize }
194             }
195
196             fn foo(a: &mut A) {
197                 match a<|> {
198                 }
199             }
200             "#,
201             r#"
202             enum A {
203                 Es{ x: usize, y: usize }
204             }
205
206             fn foo(a: &mut A) {
207                 match <|>a {
208                     A::Es { x, y } => (),
209                 }
210             }
211             "#,
212         );
213     }
214
215     #[test]
216     fn fill_match_arms_target() {
217         check_assist_target(
218             fill_match_arms,
219             r#"
220             enum E { X, Y }
221
222             fn main() {
223                 match E::X<|> {}
224             }
225             "#,
226             "match E::X {}",
227         );
228     }
229
230     #[test]
231     fn fill_match_arms_trivial_arm() {
232         check_assist(
233             fill_match_arms,
234             r#"
235             enum E { X, Y }
236
237             fn main() {
238                 match E::X {
239                     <|>_ => {},
240                 }
241             }
242             "#,
243             r#"
244             enum E { X, Y }
245
246             fn main() {
247                 match <|>E::X {
248                     E::X => (),
249                     E::Y => (),
250                 }
251             }
252             "#,
253         );
254     }
255
256     #[test]
257     fn fill_match_arms_qualifies_path() {
258         check_assist(
259             fill_match_arms,
260             r#"
261             mod foo { pub enum E { X, Y } }
262             use foo::E::X;
263
264             fn main() {
265                 match X {
266                     <|>
267                 }
268             }
269             "#,
270             r#"
271             mod foo { pub enum E { X, Y } }
272             use foo::E::X;
273
274             fn main() {
275                 match <|>X {
276                     X => (),
277                     foo::E::Y => (),
278                 }
279             }
280             "#,
281         );
282     }
283 }