Skip to content

Commit 467a040

Browse files
bors[bot]slyngbaek
andauthored
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]>
2 parents e095289 + b5ba9c3 commit 467a040

File tree

1 file changed

+177
-25
lines changed

1 file changed

+177
-25
lines changed

crates/ra_assists/src/handlers/fill_match_arms.rs

Lines changed: 177 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ use std::iter;
44

55
use hir::{Adt, HasSource, Semantics};
66
use ra_ide_db::RootDatabase;
7-
use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner};
87

98
use crate::{Assist, AssistCtx, AssistId};
9+
use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner};
10+
11+
use ast::{MatchArm, Pat};
1012

1113
// Assist: fill_match_arms
1214
//
@@ -36,16 +38,6 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
3638
let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
3739
let match_arm_list = match_expr.match_arm_list()?;
3840

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-
4941
let expr = match_expr.expr()?;
5042
let enum_def = resolve_enum_def(&ctx.sema, &expr)?;
5143
let module = ctx.sema.scope(expr.syntax()).module()?;
@@ -55,30 +47,54 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
5547
return None;
5648
}
5749

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+
5857
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+
}
5968

6069
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);
6271

63-
let new_arm_list = {
64-
let arms = variants
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-
};
72+
let indent_level = IndentLevel::from_node(match_arm_list.syntax());
73+
let new_arm_list = indent_level.increase_indent(make::match_arm_list(arms));
7074

7175
edit.target(match_expr.syntax().text_range());
7276
edit.set_cursor(expr.syntax().text_range().start());
7377
edit.replace_ast(match_arm_list, new_arm_list);
7478
})
7579
}
7680

77-
fn is_trivial(arm: &ast::MatchArm) -> bool {
78-
match arm.pat() {
79-
Some(ast::Pat::PlaceholderPat(..)) => true,
80-
_ => false,
81-
}
81+
fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool {
82+
existing_arms.iter().filter_map(|arm| arm.pat()).all(|pat| {
83+
// Special casee OrPat as separate top-level pats
84+
let top_level_pats: Vec<Pat> = match pat {
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
8298
}
8399

84100
fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> {
@@ -110,10 +126,146 @@ fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> O
110126

111127
#[cfg(test)]
112128
mod tests {
113-
use crate::helpers::{check_assist, check_assist_target};
129+
use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
114130

115131
use super::fill_match_arms;
116132

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+
117269
#[test]
118270
fn fill_match_arms_empty_body() {
119271
check_assist(

0 commit comments

Comments
 (0)