diff options
Diffstat (limited to 'crates/ra_assists/src/assists/fill_match_arms.rs')
-rw-r--r-- | crates/ra_assists/src/assists/fill_match_arms.rs | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/crates/ra_assists/src/assists/fill_match_arms.rs b/crates/ra_assists/src/assists/fill_match_arms.rs new file mode 100644 index 000000000..f59062bb9 --- /dev/null +++ b/crates/ra_assists/src/assists/fill_match_arms.rs | |||
@@ -0,0 +1,229 @@ | |||
1 | use std::iter; | ||
2 | |||
3 | use hir::{db::HirDatabase, Adt, HasSource}; | ||
4 | use ra_syntax::ast::{self, AstNode, NameOwner}; | ||
5 | |||
6 | use crate::{ast_editor::AstBuilder, Assist, AssistCtx, AssistId}; | ||
7 | |||
8 | pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
9 | let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?; | ||
10 | let match_arm_list = match_expr.match_arm_list()?; | ||
11 | |||
12 | // We already have some match arms, so we don't provide any assists. | ||
13 | // Unless if there is only one trivial match arm possibly created | ||
14 | // by match postfix complete. Trivial match arm is the catch all arm. | ||
15 | let mut existing_arms = match_arm_list.arms(); | ||
16 | if let Some(arm) = existing_arms.next() { | ||
17 | if !is_trivial(&arm) || existing_arms.next().is_some() { | ||
18 | return None; | ||
19 | } | ||
20 | }; | ||
21 | |||
22 | let expr = match_expr.expr()?; | ||
23 | let enum_def = { | ||
24 | let file_id = ctx.frange.file_id; | ||
25 | let analyzer = hir::SourceAnalyzer::new(ctx.db, file_id, expr.syntax(), None); | ||
26 | resolve_enum_def(ctx.db, &analyzer, &expr)? | ||
27 | }; | ||
28 | let variant_list = enum_def.variant_list()?; | ||
29 | |||
30 | ctx.add_action(AssistId("fill_match_arms"), "fill match arms", |edit| { | ||
31 | let variants = variant_list.variants(); | ||
32 | let arms = variants.filter_map(build_pat).map(|pat| { | ||
33 | AstBuilder::<ast::MatchArm>::from_pieces( | ||
34 | iter::once(pat), | ||
35 | &AstBuilder::<ast::Expr>::unit(), | ||
36 | ) | ||
37 | }); | ||
38 | let new_arm_list = AstBuilder::<ast::MatchArmList>::from_arms(arms); | ||
39 | |||
40 | edit.target(match_expr.syntax().text_range()); | ||
41 | edit.set_cursor(expr.syntax().text_range().start()); | ||
42 | edit.replace_node_and_indent(match_arm_list.syntax(), new_arm_list.syntax().text()); | ||
43 | }); | ||
44 | |||
45 | ctx.build() | ||
46 | } | ||
47 | |||
48 | fn is_trivial(arm: &ast::MatchArm) -> bool { | ||
49 | arm.pats().any(|pat| match pat { | ||
50 | ast::Pat::PlaceholderPat(..) => true, | ||
51 | _ => false, | ||
52 | }) | ||
53 | } | ||
54 | |||
55 | fn resolve_enum_def( | ||
56 | db: &impl HirDatabase, | ||
57 | analyzer: &hir::SourceAnalyzer, | ||
58 | expr: &ast::Expr, | ||
59 | ) -> Option<ast::EnumDef> { | ||
60 | let expr_ty = analyzer.type_of(db, &expr)?; | ||
61 | |||
62 | analyzer.autoderef(db, expr_ty).find_map(|ty| match ty.as_adt() { | ||
63 | Some((Adt::Enum(e), _)) => Some(e.source(db).ast), | ||
64 | _ => None, | ||
65 | }) | ||
66 | } | ||
67 | |||
68 | fn build_pat(var: ast::EnumVariant) -> Option<ast::Pat> { | ||
69 | let path = &AstBuilder::<ast::Path>::from_pieces(var.parent_enum().name()?, var.name()?); | ||
70 | |||
71 | let pat: ast::Pat = match var.kind() { | ||
72 | ast::StructKind::Tuple(field_list) => { | ||
73 | let pats = iter::repeat(AstBuilder::<ast::PlaceholderPat>::placeholder().into()) | ||
74 | .take(field_list.fields().count()); | ||
75 | AstBuilder::<ast::TupleStructPat>::from_pieces(path, pats).into() | ||
76 | } | ||
77 | ast::StructKind::Named(field_list) => { | ||
78 | let pats = field_list | ||
79 | .fields() | ||
80 | .map(|f| AstBuilder::<ast::BindPat>::from_name(&f.name().unwrap()).into()); | ||
81 | AstBuilder::<ast::RecordPat>::from_pieces(path, pats).into() | ||
82 | } | ||
83 | ast::StructKind::Unit => AstBuilder::<ast::PathPat>::from_path(path).into(), | ||
84 | }; | ||
85 | |||
86 | Some(pat) | ||
87 | } | ||
88 | |||
89 | #[cfg(test)] | ||
90 | mod tests { | ||
91 | use crate::helpers::{check_assist, check_assist_target}; | ||
92 | |||
93 | use super::fill_match_arms; | ||
94 | |||
95 | #[test] | ||
96 | fn fill_match_arms_empty_body() { | ||
97 | check_assist( | ||
98 | fill_match_arms, | ||
99 | r#" | ||
100 | enum A { | ||
101 | As, | ||
102 | Bs, | ||
103 | Cs(String), | ||
104 | Ds(String, String), | ||
105 | Es{ x: usize, y: usize } | ||
106 | } | ||
107 | |||
108 | fn main() { | ||
109 | let a = A::As; | ||
110 | match a<|> {} | ||
111 | } | ||
112 | "#, | ||
113 | r#" | ||
114 | enum A { | ||
115 | As, | ||
116 | Bs, | ||
117 | Cs(String), | ||
118 | Ds(String, String), | ||
119 | Es{ x: usize, y: usize } | ||
120 | } | ||
121 | |||
122 | fn main() { | ||
123 | let a = A::As; | ||
124 | match <|>a { | ||
125 | A::As => (), | ||
126 | A::Bs => (), | ||
127 | A::Cs(_) => (), | ||
128 | A::Ds(_, _) => (), | ||
129 | A::Es{ x, y } => (), | ||
130 | } | ||
131 | } | ||
132 | "#, | ||
133 | ); | ||
134 | } | ||
135 | |||
136 | #[test] | ||
137 | fn test_fill_match_arm_refs() { | ||
138 | check_assist( | ||
139 | fill_match_arms, | ||
140 | r#" | ||
141 | enum A { | ||
142 | As, | ||
143 | } | ||
144 | |||
145 | fn foo(a: &A) { | ||
146 | match a<|> { | ||
147 | } | ||
148 | } | ||
149 | "#, | ||
150 | r#" | ||
151 | enum A { | ||
152 | As, | ||
153 | } | ||
154 | |||
155 | fn foo(a: &A) { | ||
156 | match <|>a { | ||
157 | A::As => (), | ||
158 | } | ||
159 | } | ||
160 | "#, | ||
161 | ); | ||
162 | |||
163 | check_assist( | ||
164 | fill_match_arms, | ||
165 | r#" | ||
166 | enum A { | ||
167 | Es{ x: usize, y: usize } | ||
168 | } | ||
169 | |||
170 | fn foo(a: &mut A) { | ||
171 | match a<|> { | ||
172 | } | ||
173 | } | ||
174 | "#, | ||
175 | r#" | ||
176 | enum A { | ||
177 | Es{ x: usize, y: usize } | ||
178 | } | ||
179 | |||
180 | fn foo(a: &mut A) { | ||
181 | match <|>a { | ||
182 | A::Es{ x, y } => (), | ||
183 | } | ||
184 | } | ||
185 | "#, | ||
186 | ); | ||
187 | } | ||
188 | |||
189 | #[test] | ||
190 | fn fill_match_arms_target() { | ||
191 | check_assist_target( | ||
192 | fill_match_arms, | ||
193 | r#" | ||
194 | enum E { X, Y } | ||
195 | |||
196 | fn main() { | ||
197 | match E::X<|> {} | ||
198 | } | ||
199 | "#, | ||
200 | "match E::X {}", | ||
201 | ); | ||
202 | } | ||
203 | |||
204 | #[test] | ||
205 | fn fill_match_arms_trivial_arm() { | ||
206 | check_assist( | ||
207 | fill_match_arms, | ||
208 | r#" | ||
209 | enum E { X, Y } | ||
210 | |||
211 | fn main() { | ||
212 | match E::X { | ||
213 | <|>_ => {}, | ||
214 | } | ||
215 | } | ||
216 | "#, | ||
217 | r#" | ||
218 | enum E { X, Y } | ||
219 | |||
220 | fn main() { | ||
221 | match <|>E::X { | ||
222 | E::X => (), | ||
223 | E::Y => (), | ||
224 | } | ||
225 | } | ||
226 | "#, | ||
227 | ); | ||
228 | } | ||
229 | } | ||