diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2019-08-23 06:43:32 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2019-08-23 06:43:32 +0100 |
commit | e055cfacdfe3b3451484dae5d6ed08aefba133ca (patch) | |
tree | 259613dfe4b76b63966d491f03742813bfa969e9 /crates/ra_assists/src | |
parent | 0c35d82329bc0952bd179ec37933c0955fd5ed26 (diff) | |
parent | e84f93cb5b651696637d87b98653d7e8f9149086 (diff) |
Merge #1724
1724: Refactor fill_match_arms assist to use AstBuilder facilities r=matklad a=viorina
Co-authored-by: Ekaterina Babshukova <[email protected]>
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r-- | crates/ra_assists/src/add_missing_impl_members.rs | 14 | ||||
-rw-r--r-- | crates/ra_assists/src/ast_editor.rs | 93 | ||||
-rw-r--r-- | crates/ra_assists/src/fill_match_arms.rs | 195 |
3 files changed, 170 insertions, 132 deletions
diff --git a/crates/ra_assists/src/add_missing_impl_members.rs b/crates/ra_assists/src/add_missing_impl_members.rs index 31c7d4e80..cbeb7054f 100644 --- a/crates/ra_assists/src/add_missing_impl_members.rs +++ b/crates/ra_assists/src/add_missing_impl_members.rs | |||
@@ -1,13 +1,14 @@ | |||
1 | use hir::{db::HirDatabase, HasSource}; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, NameOwner}, | ||
4 | SmolStr, | ||
5 | }; | ||
6 | |||
1 | use crate::{ | 7 | use crate::{ |
2 | ast_editor::{AstBuilder, AstEditor}, | 8 | ast_editor::{AstBuilder, AstEditor}, |
3 | Assist, AssistCtx, AssistId, | 9 | Assist, AssistCtx, AssistId, |
4 | }; | 10 | }; |
5 | 11 | ||
6 | use hir::{db::HirDatabase, HasSource}; | ||
7 | use ra_db::FilePosition; | ||
8 | use ra_syntax::ast::{self, AstNode, NameOwner}; | ||
9 | use ra_syntax::SmolStr; | ||
10 | |||
11 | #[derive(PartialEq)] | 12 | #[derive(PartialEq)] |
12 | enum AddMissingImplMembersMode { | 13 | enum AddMissingImplMembersMode { |
13 | DefaultMethodsOnly, | 14 | DefaultMethodsOnly, |
@@ -43,8 +44,7 @@ fn add_missing_impl_members_inner( | |||
43 | 44 | ||
44 | let trait_def = { | 45 | let trait_def = { |
45 | let file_id = ctx.frange.file_id; | 46 | let file_id = ctx.frange.file_id; |
46 | let position = FilePosition { file_id, offset: impl_node.syntax().text_range().start() }; | 47 | let analyzer = hir::SourceAnalyzer::new(ctx.db, file_id, impl_node.syntax(), None); |
47 | let analyzer = hir::SourceAnalyzer::new(ctx.db, position.file_id, impl_node.syntax(), None); | ||
48 | 48 | ||
49 | resolve_target_trait_def(ctx.db, &analyzer, &impl_node)? | 49 | resolve_target_trait_def(ctx.db, &analyzer, &impl_node)? |
50 | }; | 50 | }; |
diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs index 95b871b30..5b6952426 100644 --- a/crates/ra_assists/src/ast_editor.rs +++ b/crates/ra_assists/src/ast_editor.rs | |||
@@ -1,6 +1,8 @@ | |||
1 | use std::{iter, ops::RangeInclusive}; | 1 | use std::{iter, ops::RangeInclusive}; |
2 | 2 | ||
3 | use arrayvec::ArrayVec; | 3 | use arrayvec::ArrayVec; |
4 | use itertools::Itertools; | ||
5 | |||
4 | use hir::Name; | 6 | use hir::Name; |
5 | use ra_fmt::leading_indent; | 7 | use ra_fmt::leading_indent; |
6 | use ra_syntax::{ | 8 | use ra_syntax::{ |
@@ -168,8 +170,7 @@ impl AstEditor<ast::NamedFieldList> { | |||
168 | 170 | ||
169 | impl AstEditor<ast::ItemList> { | 171 | impl AstEditor<ast::ItemList> { |
170 | pub fn append_items(&mut self, items: impl Iterator<Item = ast::ImplItem>) { | 172 | pub fn append_items(&mut self, items: impl Iterator<Item = ast::ImplItem>) { |
171 | let n_existing_items = self.ast().impl_items().count(); | 173 | if !self.ast().syntax().text().contains_char('\n') { |
172 | if n_existing_items == 0 { | ||
173 | self.do_make_multiline(); | 174 | self.do_make_multiline(); |
174 | } | 175 | } |
175 | items.for_each(|it| self.append_item(it)); | 176 | items.for_each(|it| self.append_item(it)); |
@@ -288,6 +289,94 @@ impl AstBuilder<ast::NameRef> { | |||
288 | } | 289 | } |
289 | } | 290 | } |
290 | 291 | ||
292 | impl AstBuilder<ast::Path> { | ||
293 | fn from_text(text: &str) -> ast::Path { | ||
294 | ast_node_from_file_text(text) | ||
295 | } | ||
296 | |||
297 | pub fn from_pieces(enum_name: ast::Name, var_name: ast::Name) -> ast::Path { | ||
298 | Self::from_text(&format!("{}::{}", enum_name.syntax(), var_name.syntax())) | ||
299 | } | ||
300 | } | ||
301 | |||
302 | impl AstBuilder<ast::BindPat> { | ||
303 | fn from_text(text: &str) -> ast::BindPat { | ||
304 | ast_node_from_file_text(&format!("fn f({}: ())", text)) | ||
305 | } | ||
306 | |||
307 | pub fn from_name(name: &ast::Name) -> ast::BindPat { | ||
308 | Self::from_text(name.text()) | ||
309 | } | ||
310 | } | ||
311 | |||
312 | impl AstBuilder<ast::PlaceholderPat> { | ||
313 | fn from_text(text: &str) -> ast::PlaceholderPat { | ||
314 | ast_node_from_file_text(&format!("fn f({}: ())", text)) | ||
315 | } | ||
316 | |||
317 | pub fn placeholder() -> ast::PlaceholderPat { | ||
318 | Self::from_text("_") | ||
319 | } | ||
320 | } | ||
321 | |||
322 | impl AstBuilder<ast::TupleStructPat> { | ||
323 | fn from_text(text: &str) -> ast::TupleStructPat { | ||
324 | ast_node_from_file_text(&format!("fn f({}: ())", text)) | ||
325 | } | ||
326 | |||
327 | pub fn from_pieces( | ||
328 | path: &ast::Path, | ||
329 | pats: impl Iterator<Item = ast::Pat>, | ||
330 | ) -> ast::TupleStructPat { | ||
331 | let pats_str = pats.map(|p| p.syntax().to_string()).collect::<Vec<_>>().join(", "); | ||
332 | Self::from_text(&format!("{}({})", path.syntax(), pats_str)) | ||
333 | } | ||
334 | } | ||
335 | |||
336 | impl AstBuilder<ast::StructPat> { | ||
337 | fn from_text(text: &str) -> ast::StructPat { | ||
338 | ast_node_from_file_text(&format!("fn f({}: ())", text)) | ||
339 | } | ||
340 | |||
341 | pub fn from_pieces(path: &ast::Path, pats: impl Iterator<Item = ast::Pat>) -> ast::StructPat { | ||
342 | let pats_str = pats.map(|p| p.syntax().to_string()).collect::<Vec<_>>().join(", "); | ||
343 | Self::from_text(&format!("{}{{ {} }}", path.syntax(), pats_str)) | ||
344 | } | ||
345 | } | ||
346 | |||
347 | impl AstBuilder<ast::PathPat> { | ||
348 | fn from_text(text: &str) -> ast::PathPat { | ||
349 | ast_node_from_file_text(&format!("fn f({}: ())", text)) | ||
350 | } | ||
351 | |||
352 | pub fn from_path(path: &ast::Path) -> ast::PathPat { | ||
353 | let path_str = path.syntax().text().to_string(); | ||
354 | Self::from_text(path_str.as_str()) | ||
355 | } | ||
356 | } | ||
357 | |||
358 | impl AstBuilder<ast::MatchArm> { | ||
359 | fn from_text(text: &str) -> ast::MatchArm { | ||
360 | ast_node_from_file_text(&format!("fn f() {{ match () {{{}}} }}", text)) | ||
361 | } | ||
362 | |||
363 | pub fn from_pieces(pats: impl Iterator<Item = ast::Pat>, expr: &ast::Expr) -> ast::MatchArm { | ||
364 | let pats_str = pats.map(|p| p.syntax().to_string()).join(" | "); | ||
365 | Self::from_text(&format!("{} => {}", pats_str, expr.syntax())) | ||
366 | } | ||
367 | } | ||
368 | |||
369 | impl AstBuilder<ast::MatchArmList> { | ||
370 | fn from_text(text: &str) -> ast::MatchArmList { | ||
371 | ast_node_from_file_text(&format!("fn f() {{ match () {{{}}} }}", text)) | ||
372 | } | ||
373 | |||
374 | pub fn from_arms(arms: impl Iterator<Item = ast::MatchArm>) -> ast::MatchArmList { | ||
375 | let arms_str = arms.map(|arm| format!("\n {}", arm.syntax())).join(","); | ||
376 | Self::from_text(&format!("{},\n", arms_str)) | ||
377 | } | ||
378 | } | ||
379 | |||
291 | fn ast_node_from_file_text<N: AstNode>(text: &str) -> N { | 380 | fn ast_node_from_file_text<N: AstNode>(text: &str) -> N { |
292 | let parse = SourceFile::parse(text); | 381 | let parse = SourceFile::parse(text); |
293 | let res = parse.tree().syntax().descendants().find_map(N::cast).unwrap().to_owned(); | 382 | let res = parse.tree().syntax().descendants().find_map(N::cast).unwrap().to_owned(); |
diff --git a/crates/ra_assists/src/fill_match_arms.rs b/crates/ra_assists/src/fill_match_arms.rs index 85ff5c052..ce715a449 100644 --- a/crates/ra_assists/src/fill_match_arms.rs +++ b/crates/ra_assists/src/fill_match_arms.rs | |||
@@ -1,97 +1,91 @@ | |||
1 | use itertools::Itertools; | 1 | use std::iter; |
2 | use std::fmt::Write; | ||
3 | 2 | ||
4 | use hir::{db::HirDatabase, AdtDef, FieldSource, HasSource}; | 3 | use hir::{db::HirDatabase, AdtDef, HasSource}; |
5 | use ra_syntax::ast::{self, AstNode}; | 4 | use ra_syntax::ast::{self, AstNode, NameOwner}; |
6 | 5 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 6 | use crate::{ast_editor::AstBuilder, Assist, AssistCtx, AssistId}; |
8 | |||
9 | fn is_trivial_arm(arm: &ast::MatchArm) -> bool { | ||
10 | fn single_pattern(arm: &ast::MatchArm) -> Option<ast::Pat> { | ||
11 | let (pat,) = arm.pats().collect_tuple()?; | ||
12 | Some(pat) | ||
13 | } | ||
14 | match single_pattern(arm) { | ||
15 | Some(ast::Pat::PlaceholderPat(..)) => true, | ||
16 | _ => false, | ||
17 | } | ||
18 | } | ||
19 | 7 | ||
20 | pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 8 | pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
21 | let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?; | 9 | let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?; |
10 | let match_arm_list = match_expr.match_arm_list()?; | ||
22 | 11 | ||
23 | // We already have some match arms, so we don't provide any assists. | 12 | // We already have some match arms, so we don't provide any assists. |
24 | // Unless if there is only one trivial match arm possibly created | 13 | // Unless if there is only one trivial match arm possibly created |
25 | // by match postfix complete. Trivial match arm is the catch all arm. | 14 | // by match postfix complete. Trivial match arm is the catch all arm. |
26 | if let Some(arm_list) = match_expr.match_arm_list() { | 15 | let mut existing_arms = match_arm_list.arms(); |
27 | let mut arm_iter = arm_list.arms(); | 16 | if let Some(arm) = existing_arms.next() { |
28 | let first = arm_iter.next(); | 17 | if !is_trivial(&arm) || existing_arms.next().is_some() { |
29 | 18 | return None; | |
30 | match &first { | ||
31 | // If there arm list is empty or there is only one trivial arm, then proceed. | ||
32 | Some(arm) if is_trivial_arm(arm) => { | ||
33 | if arm_iter.next() != None { | ||
34 | return None; | ||
35 | } | ||
36 | } | ||
37 | None => {} | ||
38 | |||
39 | _ => { | ||
40 | return None; | ||
41 | } | ||
42 | } | 19 | } |
43 | }; | 20 | }; |
44 | 21 | ||
45 | let expr = match_expr.expr()?; | 22 | let expr = match_expr.expr()?; |
46 | let analyzer = hir::SourceAnalyzer::new(ctx.db, ctx.frange.file_id, expr.syntax(), None); | 23 | let enum_def = { |
47 | let match_expr_ty = analyzer.type_of(ctx.db, &expr)?; | 24 | let file_id = ctx.frange.file_id; |
48 | let enum_def = analyzer.autoderef(ctx.db, match_expr_ty).find_map(|ty| match ty.as_adt() { | 25 | let analyzer = hir::SourceAnalyzer::new(ctx.db, file_id, expr.syntax(), None); |
49 | Some((AdtDef::Enum(e), _)) => Some(e), | 26 | resolve_enum_def(ctx.db, &analyzer, &expr)? |
50 | _ => None, | 27 | }; |
51 | })?; | 28 | let variant_list = enum_def.variant_list()?; |
52 | let enum_name = enum_def.name(ctx.db)?; | ||
53 | let db = ctx.db; | ||
54 | 29 | ||
55 | ctx.add_action(AssistId("fill_match_arms"), "fill match arms", |edit| { | 30 | ctx.add_action(AssistId("fill_match_arms"), "fill match arms", |edit| { |
56 | let mut buf = format!("match {} {{\n", expr.syntax().text().to_string()); | 31 | let variants = variant_list.variants(); |
57 | let variants = enum_def.variants(db); | 32 | let arms = variants.into_iter().filter_map(build_pat).map(|pat| { |
58 | for variant in variants { | 33 | AstBuilder::<ast::MatchArm>::from_pieces( |
59 | let name = match variant.name(db) { | 34 | iter::once(pat), |
60 | Some(it) => it, | 35 | &AstBuilder::<ast::Expr>::unit(), |
61 | None => continue, | 36 | ) |
62 | }; | 37 | }); |
63 | write!(&mut buf, " {}::{}", enum_name, name.to_string()).unwrap(); | 38 | let new_arm_list = AstBuilder::<ast::MatchArmList>::from_arms(arms); |
64 | |||
65 | let pat = variant | ||
66 | .fields(db) | ||
67 | .into_iter() | ||
68 | .map(|field| { | ||
69 | let name = field.name(db).to_string(); | ||
70 | let src = field.source(db); | ||
71 | match src.ast { | ||
72 | FieldSource::Named(_) => name, | ||
73 | FieldSource::Pos(_) => "_".to_string(), | ||
74 | } | ||
75 | }) | ||
76 | .collect::<Vec<_>>(); | ||
77 | 39 | ||
78 | match pat.first().map(|s| s.as_str()) { | ||
79 | Some("_") => write!(&mut buf, "({})", pat.join(", ")).unwrap(), | ||
80 | Some(_) => write!(&mut buf, "{{{}}}", pat.join(", ")).unwrap(), | ||
81 | None => (), | ||
82 | }; | ||
83 | |||
84 | buf.push_str(" => (),\n"); | ||
85 | } | ||
86 | buf.push_str("}"); | ||
87 | edit.target(match_expr.syntax().text_range()); | 40 | edit.target(match_expr.syntax().text_range()); |
88 | edit.set_cursor(expr.syntax().text_range().start()); | 41 | edit.set_cursor(expr.syntax().text_range().start()); |
89 | edit.replace_node_and_indent(match_expr.syntax(), buf); | 42 | edit.replace_node_and_indent(match_arm_list.syntax(), new_arm_list.syntax().text()); |
90 | }); | 43 | }); |
91 | 44 | ||
92 | ctx.build() | 45 | ctx.build() |
93 | } | 46 | } |
94 | 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((AdtDef::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::StructPat>::from_pieces(path, pats).into() | ||
82 | } | ||
83 | ast::StructKind::Unit => AstBuilder::<ast::PathPat>::from_path(path).into(), | ||
84 | }; | ||
85 | |||
86 | Some(pat) | ||
87 | } | ||
88 | |||
95 | #[cfg(test)] | 89 | #[cfg(test)] |
96 | mod tests { | 90 | mod tests { |
97 | use crate::helpers::{check_assist, check_assist_target}; | 91 | use crate::helpers::{check_assist, check_assist_target}; |
@@ -108,7 +102,7 @@ mod tests { | |||
108 | Bs, | 102 | Bs, |
109 | Cs(String), | 103 | Cs(String), |
110 | Ds(String, String), | 104 | Ds(String, String), |
111 | Es{x: usize, y: usize} | 105 | Es{ x: usize, y: usize } |
112 | } | 106 | } |
113 | 107 | ||
114 | fn main() { | 108 | fn main() { |
@@ -122,7 +116,7 @@ mod tests { | |||
122 | Bs, | 116 | Bs, |
123 | Cs(String), | 117 | Cs(String), |
124 | Ds(String, String), | 118 | Ds(String, String), |
125 | Es{x: usize, y: usize} | 119 | Es{ x: usize, y: usize } |
126 | } | 120 | } |
127 | 121 | ||
128 | fn main() { | 122 | fn main() { |
@@ -132,7 +126,7 @@ mod tests { | |||
132 | A::Bs => (), | 126 | A::Bs => (), |
133 | A::Cs(_) => (), | 127 | A::Cs(_) => (), |
134 | A::Ds(_, _) => (), | 128 | A::Ds(_, _) => (), |
135 | A::Es{x, y} => (), | 129 | A::Es{ x, y } => (), |
136 | } | 130 | } |
137 | } | 131 | } |
138 | "#, | 132 | "#, |
@@ -170,7 +164,7 @@ mod tests { | |||
170 | fill_match_arms, | 164 | fill_match_arms, |
171 | r#" | 165 | r#" |
172 | enum A { | 166 | enum A { |
173 | Es{x: usize, y: usize} | 167 | Es{ x: usize, y: usize } |
174 | } | 168 | } |
175 | 169 | ||
176 | fn foo(a: &mut A) { | 170 | fn foo(a: &mut A) { |
@@ -180,57 +174,12 @@ mod tests { | |||
180 | "#, | 174 | "#, |
181 | r#" | 175 | r#" |
182 | enum A { | 176 | enum A { |
183 | Es{x: usize, y: usize} | 177 | Es{ x: usize, y: usize } |
184 | } | 178 | } |
185 | 179 | ||
186 | fn foo(a: &mut A) { | 180 | fn foo(a: &mut A) { |
187 | match <|>a { | 181 | match <|>a { |
188 | A::Es{x, y} => (), | 182 | A::Es{ x, y } => (), |
189 | } | ||
190 | } | ||
191 | "#, | ||
192 | ); | ||
193 | |||
194 | check_assist( | ||
195 | fill_match_arms, | ||
196 | r#" | ||
197 | enum E { X, Y} | ||
198 | |||
199 | fn main() { | ||
200 | match &E::X<|> | ||
201 | } | ||
202 | "#, | ||
203 | r#" | ||
204 | enum E { X, Y} | ||
205 | |||
206 | fn main() { | ||
207 | match <|>&E::X { | ||
208 | E::X => (), | ||
209 | E::Y => (), | ||
210 | } | ||
211 | } | ||
212 | "#, | ||
213 | ); | ||
214 | } | ||
215 | |||
216 | #[test] | ||
217 | fn fill_match_arms_no_body() { | ||
218 | check_assist( | ||
219 | fill_match_arms, | ||
220 | r#" | ||
221 | enum E { X, Y} | ||
222 | |||
223 | fn main() { | ||
224 | match E::X<|> | ||
225 | } | ||
226 | "#, | ||
227 | r#" | ||
228 | enum E { X, Y} | ||
229 | |||
230 | fn main() { | ||
231 | match <|>E::X { | ||
232 | E::X => (), | ||
233 | E::Y => (), | ||
234 | } | 183 | } |
235 | } | 184 | } |
236 | "#, | 185 | "#, |
@@ -242,7 +191,7 @@ mod tests { | |||
242 | check_assist_target( | 191 | check_assist_target( |
243 | fill_match_arms, | 192 | fill_match_arms, |
244 | r#" | 193 | r#" |
245 | enum E { X, Y} | 194 | enum E { X, Y } |
246 | 195 | ||
247 | fn main() { | 196 | fn main() { |
248 | match E::X<|> {} | 197 | match E::X<|> {} |