diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-03-20 08:26:15 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-03-20 08:26:15 +0000 |
commit | 467a040509cd6732de48907e025f83b57b5701b9 (patch) | |
tree | 217fec908db7dbcd39e5b452d43257a9f2977c53 /crates/ra_assists/src | |
parent | e0952899aee50b813379ca8e6dcab49a895ff0a0 (diff) | |
parent | b5ba9c3e3ae25b6add36c670de75967a7b38bb53 (diff) |
Merge #3623
3623: 'Fill match arms' should work with existing match arms r=matklad a=slyngbaek
Addresses #3039
This essentially adds missing match arms. The algorithm for this
can get complicated rather quickly so bail in certain conditions
and rely on a PlaceholderPat.
The algorighm works as such:
- Iterate through the Enum Def Variants
- Attempt to see if the variant already exists as a match arm
- If yes, skip the enum variant. If no, include it.
- If it becomes complicated, rather than exhaustively deal with every
branch, mark it as a "partial match" and simply include the
placeholder.
Conditions for "complication":
- The match arm contains a match guard
- Any kind of nested destrucuring
Order the resulting merged match branches as such:
1. Provided match arms
2. Missing enum variant branch arms
3. End with Placeholder if required
- Add extra tests
Co-authored-by: Steffen Lyngbaek <[email protected]>
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r-- | crates/ra_assists/src/handlers/fill_match_arms.rs | 202 |
1 files changed, 177 insertions, 25 deletions
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs index a1e4c2eb7..fbd6a3ec3 100644 --- a/crates/ra_assists/src/handlers/fill_match_arms.rs +++ b/crates/ra_assists/src/handlers/fill_match_arms.rs | |||
@@ -4,9 +4,11 @@ use std::iter; | |||
4 | 4 | ||
5 | use hir::{Adt, HasSource, Semantics}; | 5 | use hir::{Adt, HasSource, Semantics}; |
6 | use ra_ide_db::RootDatabase; | 6 | use ra_ide_db::RootDatabase; |
7 | use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner}; | ||
8 | 7 | ||
9 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{Assist, AssistCtx, AssistId}; |
9 | use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner}; | ||
10 | |||
11 | use ast::{MatchArm, Pat}; | ||
10 | 12 | ||
11 | // Assist: fill_match_arms | 13 | // Assist: fill_match_arms |
12 | // | 14 | // |
@@ -36,16 +38,6 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
36 | let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; | 38 | let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; |
37 | let match_arm_list = match_expr.match_arm_list()?; | 39 | let match_arm_list = match_expr.match_arm_list()?; |
38 | 40 | ||
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()?; | 41 | let expr = match_expr.expr()?; |
50 | let enum_def = resolve_enum_def(&ctx.sema, &expr)?; | 42 | let enum_def = resolve_enum_def(&ctx.sema, &expr)?; |
51 | let module = ctx.sema.scope(expr.syntax()).module()?; | 43 | let module = ctx.sema.scope(expr.syntax()).module()?; |
@@ -55,18 +47,30 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
55 | return None; | 47 | return None; |
56 | } | 48 | } |
57 | 49 | ||
50 | let mut arms: Vec<MatchArm> = match_arm_list.arms().collect(); | ||
51 | if arms.len() == 1 { | ||
52 | if let Some(Pat::PlaceholderPat(..)) = arms[0].pat() { | ||
53 | arms.clear(); | ||
54 | } | ||
55 | } | ||
56 | |||
58 | let db = ctx.db; | 57 | let db = ctx.db; |
58 | let missing_arms: Vec<MatchArm> = variants | ||
59 | .into_iter() | ||
60 | .filter_map(|variant| build_pat(db, module, variant)) | ||
61 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) | ||
62 | .map(|pat| make::match_arm(iter::once(pat), make::expr_unit())) | ||
63 | .collect(); | ||
64 | |||
65 | if missing_arms.is_empty() { | ||
66 | return None; | ||
67 | } | ||
59 | 68 | ||
60 | ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| { | 69 | ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| { |
61 | let indent_level = IndentLevel::from_node(match_arm_list.syntax()); | 70 | arms.extend(missing_arms); |
62 | 71 | ||
63 | let new_arm_list = { | 72 | let indent_level = IndentLevel::from_node(match_arm_list.syntax()); |
64 | let arms = variants | 73 | let new_arm_list = indent_level.increase_indent(make::match_arm_list(arms)); |
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 | 74 | ||
71 | edit.target(match_expr.syntax().text_range()); | 75 | edit.target(match_expr.syntax().text_range()); |
72 | edit.set_cursor(expr.syntax().text_range().start()); | 76 | edit.set_cursor(expr.syntax().text_range().start()); |
@@ -74,11 +78,23 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
74 | }) | 78 | }) |
75 | } | 79 | } |
76 | 80 | ||
77 | fn is_trivial(arm: &ast::MatchArm) -> bool { | 81 | fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool { |
78 | match arm.pat() { | 82 | existing_arms.iter().filter_map(|arm| arm.pat()).all(|pat| { |
79 | Some(ast::Pat::PlaceholderPat(..)) => true, | 83 | // Special casee OrPat as separate top-level pats |
80 | _ => false, | 84 | let top_level_pats: Vec<Pat> = match pat { |
81 | } | 85 | Pat::OrPat(pats) => pats.pats().collect::<Vec<_>>(), |
86 | _ => vec![pat], | ||
87 | }; | ||
88 | |||
89 | !top_level_pats.iter().any(|pat| does_pat_match_variant(pat, var)) | ||
90 | }) | ||
91 | } | ||
92 | |||
93 | fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool { | ||
94 | let pat_head = pat.syntax().first_child().map(|node| node.text()); | ||
95 | let var_head = var.syntax().first_child().map(|node| node.text()); | ||
96 | |||
97 | pat_head == var_head | ||
82 | } | 98 | } |
83 | 99 | ||
84 | fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> { | 100 | fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> { |
@@ -110,11 +126,147 @@ fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> O | |||
110 | 126 | ||
111 | #[cfg(test)] | 127 | #[cfg(test)] |
112 | mod tests { | 128 | mod tests { |
113 | use crate::helpers::{check_assist, check_assist_target}; | 129 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; |
114 | 130 | ||
115 | use super::fill_match_arms; | 131 | use super::fill_match_arms; |
116 | 132 | ||
117 | #[test] | 133 | #[test] |
134 | fn all_match_arms_provided() { | ||
135 | check_assist_not_applicable( | ||
136 | fill_match_arms, | ||
137 | r#" | ||
138 | enum A { | ||
139 | As, | ||
140 | Bs{x:i32, y:Option<i32>}, | ||
141 | Cs(i32, Option<i32>), | ||
142 | } | ||
143 | fn main() { | ||
144 | match A::As<|> { | ||
145 | A::As, | ||
146 | A::Bs{x,y:Some(_)} => (), | ||
147 | A::Cs(_, Some(_)) => (), | ||
148 | } | ||
149 | } | ||
150 | "#, | ||
151 | ); | ||
152 | } | ||
153 | |||
154 | #[test] | ||
155 | fn partial_fill_record_tuple() { | ||
156 | check_assist( | ||
157 | fill_match_arms, | ||
158 | r#" | ||
159 | enum A { | ||
160 | As, | ||
161 | Bs{x:i32, y:Option<i32>}, | ||
162 | Cs(i32, Option<i32>), | ||
163 | } | ||
164 | fn main() { | ||
165 | match A::As<|> { | ||
166 | A::Bs{x,y:Some(_)} => (), | ||
167 | A::Cs(_, Some(_)) => (), | ||
168 | } | ||
169 | } | ||
170 | "#, | ||
171 | r#" | ||
172 | enum A { | ||
173 | As, | ||
174 | Bs{x:i32, y:Option<i32>}, | ||
175 | Cs(i32, Option<i32>), | ||
176 | } | ||
177 | fn main() { | ||
178 | match <|>A::As { | ||
179 | A::Bs{x,y:Some(_)} => (), | ||
180 | A::Cs(_, Some(_)) => (), | ||
181 | A::As => (), | ||
182 | } | ||
183 | } | ||
184 | "#, | ||
185 | ); | ||
186 | } | ||
187 | |||
188 | #[test] | ||
189 | fn partial_fill_or_pat() { | ||
190 | check_assist( | ||
191 | fill_match_arms, | ||
192 | r#" | ||
193 | enum A { | ||
194 | As, | ||
195 | Bs, | ||
196 | Cs(Option<i32>), | ||
197 | } | ||
198 | fn main() { | ||
199 | match A::As<|> { | ||
200 | A::Cs(_) | A::Bs => (), | ||
201 | } | ||
202 | } | ||
203 | "#, | ||
204 | r#" | ||
205 | enum A { | ||
206 | As, | ||
207 | Bs, | ||
208 | Cs(Option<i32>), | ||
209 | } | ||
210 | fn main() { | ||
211 | match <|>A::As { | ||
212 | A::Cs(_) | A::Bs => (), | ||
213 | A::As => (), | ||
214 | } | ||
215 | } | ||
216 | "#, | ||
217 | ); | ||
218 | } | ||
219 | |||
220 | #[test] | ||
221 | fn partial_fill() { | ||
222 | check_assist( | ||
223 | fill_match_arms, | ||
224 | r#" | ||
225 | enum A { | ||
226 | As, | ||
227 | Bs, | ||
228 | Cs, | ||
229 | Ds(String), | ||
230 | Es(B), | ||
231 | } | ||
232 | enum B { | ||
233 | Xs, | ||
234 | Ys, | ||
235 | } | ||
236 | fn main() { | ||
237 | match A::As<|> { | ||
238 | A::Bs if 0 < 1 => (), | ||
239 | A::Ds(_value) => (), | ||
240 | A::Es(B::Xs) => (), | ||
241 | } | ||
242 | } | ||
243 | "#, | ||
244 | r#" | ||
245 | enum A { | ||
246 | As, | ||
247 | Bs, | ||
248 | Cs, | ||
249 | Ds(String), | ||
250 | Es(B), | ||
251 | } | ||
252 | enum B { | ||
253 | Xs, | ||
254 | Ys, | ||
255 | } | ||
256 | fn main() { | ||
257 | match <|>A::As { | ||
258 | A::Bs if 0 < 1 => (), | ||
259 | A::Ds(_value) => (), | ||
260 | A::Es(B::Xs) => (), | ||
261 | A::As => (), | ||
262 | A::Cs => (), | ||
263 | } | ||
264 | } | ||
265 | "#, | ||
266 | ); | ||
267 | } | ||
268 | |||
269 | #[test] | ||
118 | fn fill_match_arms_empty_body() { | 270 | fn fill_match_arms_empty_body() { |
119 | check_assist( | 271 | check_assist( |
120 | fill_match_arms, | 272 | fill_match_arms, |