aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/assists/fill_match_arms.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/assists/fill_match_arms.rs')
-rw-r--r--crates/ra_assists/src/assists/fill_match_arms.rs229
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..771aa625f
--- /dev/null
+++ b/crates/ra_assists/src/assists/fill_match_arms.rs
@@ -0,0 +1,229 @@
1use std::iter;
2
3use hir::{db::HirDatabase, Adt, HasSource};
4use ra_syntax::ast::{self, AstNode, NameOwner};
5
6use crate::{ast_builder::AstBuilder, Assist, AssistCtx, AssistId};
7
8pub(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
48fn is_trivial(arm: &ast::MatchArm) -> bool {
49 arm.pats().any(|pat| match pat {
50 ast::Pat::PlaceholderPat(..) => true,
51 _ => false,
52 })
53}
54
55fn 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
68fn 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)]
90mod 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}