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