aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-03-20 08:26:15 +0000
committerGitHub <[email protected]>2020-03-20 08:26:15 +0000
commit467a040509cd6732de48907e025f83b57b5701b9 (patch)
tree217fec908db7dbcd39e5b452d43257a9f2977c53
parente0952899aee50b813379ca8e6dcab49a895ff0a0 (diff)
parentb5ba9c3e3ae25b6add36c670de75967a7b38bb53 (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]>
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs202
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
5use hir::{Adt, HasSource, Semantics}; 5use hir::{Adt, HasSource, Semantics};
6use ra_ide_db::RootDatabase; 6use ra_ide_db::RootDatabase;
7use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner};
8 7
9use crate::{Assist, AssistCtx, AssistId}; 8use crate::{Assist, AssistCtx, AssistId};
9use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner};
10
11use 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
77fn is_trivial(arm: &ast::MatchArm) -> bool { 81fn 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
93fn 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
84fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> { 100fn 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)]
112mod tests { 128mod 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,