diff options
-rw-r--r-- | crates/ra_assists/src/doc_tests/generated.rs | 26 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/replace_let_with_if_let.rs | 108 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/edit.rs | 11 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 5 | ||||
-rw-r--r-- | docs/user/assists.md | 23 |
6 files changed, 170 insertions, 5 deletions
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs index 62dcb3808..c0486a8a9 100644 --- a/crates/ra_assists/src/doc_tests/generated.rs +++ b/crates/ra_assists/src/doc_tests/generated.rs | |||
@@ -608,6 +608,32 @@ fn handle(action: Action) { | |||
608 | } | 608 | } |
609 | 609 | ||
610 | #[test] | 610 | #[test] |
611 | fn doctest_replace_let_with_if_let() { | ||
612 | check( | ||
613 | "replace_let_with_if_let", | ||
614 | r#####" | ||
615 | enum Option<T> { Some(T), None } | ||
616 | |||
617 | fn main(action: Action) { | ||
618 | <|>let x = compute(); | ||
619 | } | ||
620 | |||
621 | fn compute() -> Option<i32> { None } | ||
622 | "#####, | ||
623 | r#####" | ||
624 | enum Option<T> { Some(T), None } | ||
625 | |||
626 | fn main(action: Action) { | ||
627 | if let Some(x) = compute() { | ||
628 | } | ||
629 | } | ||
630 | |||
631 | fn compute() -> Option<i32> { None } | ||
632 | "#####, | ||
633 | ) | ||
634 | } | ||
635 | |||
636 | #[test] | ||
611 | fn doctest_replace_qualified_name_with_use() { | 637 | fn doctest_replace_qualified_name_with_use() { |
612 | check( | 638 | check( |
613 | "replace_qualified_name_with_use", | 639 | "replace_qualified_name_with_use", |
diff --git a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs new file mode 100644 index 000000000..10e41f97e --- /dev/null +++ b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs | |||
@@ -0,0 +1,108 @@ | |||
1 | use hir::Adt; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, make}, | ||
4 | AstNode, T, | ||
5 | }; | ||
6 | |||
7 | use crate::{ | ||
8 | assist_ctx::{Assist, AssistCtx}, | ||
9 | AssistId, | ||
10 | }; | ||
11 | use ast::edit::{AstNodeEdit, IndentLevel}; | ||
12 | use std::iter::once; | ||
13 | |||
14 | // Assist: replace_let_with_if_let | ||
15 | // | ||
16 | // Replaces `if let` with an else branch with a `match` expression. | ||
17 | // | ||
18 | // ``` | ||
19 | // # enum Option<T> { Some(T), None } | ||
20 | // | ||
21 | // fn main(action: Action) { | ||
22 | // <|>let x = compute(); | ||
23 | // } | ||
24 | // | ||
25 | // fn compute() -> Option<i32> { None } | ||
26 | // ``` | ||
27 | // -> | ||
28 | // ``` | ||
29 | // # enum Option<T> { Some(T), None } | ||
30 | // | ||
31 | // fn main(action: Action) { | ||
32 | // if let Some(x) = compute() { | ||
33 | // } | ||
34 | // } | ||
35 | // | ||
36 | // fn compute() -> Option<i32> { None } | ||
37 | // ``` | ||
38 | pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> { | ||
39 | let let_kw = ctx.find_token_at_offset(T![let])?; | ||
40 | let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?; | ||
41 | let init = let_stmt.initializer()?; | ||
42 | let original_pat = let_stmt.pat()?; | ||
43 | let ty = ctx.sema.type_of_expr(&init)?; | ||
44 | let enum_ = match ty.as_adt() { | ||
45 | Some(Adt::Enum(it)) => it, | ||
46 | _ => return None, | ||
47 | }; | ||
48 | let happy_case = | ||
49 | [("Result", "Ok"), ("Option", "Some")].iter().find_map(|(known_type, happy_case)| { | ||
50 | if &enum_.name(ctx.db).to_string() == known_type { | ||
51 | return Some(happy_case); | ||
52 | } | ||
53 | None | ||
54 | }); | ||
55 | |||
56 | ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", |edit| { | ||
57 | let with_placeholder: ast::Pat = match happy_case { | ||
58 | None => make::placeholder_pat().into(), | ||
59 | Some(var_name) => make::tuple_struct_pat( | ||
60 | make::path_unqualified(make::path_segment(make::name_ref(var_name))), | ||
61 | once(make::placeholder_pat().into()), | ||
62 | ) | ||
63 | .into(), | ||
64 | }; | ||
65 | let block = | ||
66 | IndentLevel::from_node(let_stmt.syntax()).increase_indent(make::block_expr(None, None)); | ||
67 | let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block); | ||
68 | let stmt = make::expr_stmt(if_); | ||
69 | |||
70 | let placeholder = stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap(); | ||
71 | let target_offset = | ||
72 | let_stmt.syntax().text_range().start() + placeholder.syntax().text_range().start(); | ||
73 | let stmt = stmt.replace_descendant(placeholder.into(), original_pat); | ||
74 | |||
75 | edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt)); | ||
76 | edit.target(let_kw.text_range()); | ||
77 | edit.set_cursor(target_offset); | ||
78 | }) | ||
79 | } | ||
80 | |||
81 | #[cfg(test)] | ||
82 | mod tests { | ||
83 | use crate::helpers::check_assist; | ||
84 | |||
85 | use super::*; | ||
86 | |||
87 | #[test] | ||
88 | fn replace_let_unknown_enum() { | ||
89 | check_assist( | ||
90 | replace_let_with_if_let, | ||
91 | r" | ||
92 | enum E<T> { X(T), Y(T) } | ||
93 | |||
94 | fn main() { | ||
95 | <|>let x = E::X(92); | ||
96 | } | ||
97 | ", | ||
98 | r" | ||
99 | enum E<T> { X(T), Y(T) } | ||
100 | |||
101 | fn main() { | ||
102 | if let <|>x = E::X(92) { | ||
103 | } | ||
104 | } | ||
105 | ", | ||
106 | ) | ||
107 | } | ||
108 | } | ||
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index bcc9b3f10..67a58fc1f 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -118,6 +118,7 @@ mod handlers { | |||
118 | mod remove_dbg; | 118 | mod remove_dbg; |
119 | mod remove_mut; | 119 | mod remove_mut; |
120 | mod replace_if_let_with_match; | 120 | mod replace_if_let_with_match; |
121 | mod replace_let_with_if_let; | ||
121 | mod replace_qualified_name_with_use; | 122 | mod replace_qualified_name_with_use; |
122 | mod split_import; | 123 | mod split_import; |
123 | 124 | ||
@@ -153,6 +154,7 @@ mod handlers { | |||
153 | remove_dbg::remove_dbg, | 154 | remove_dbg::remove_dbg, |
154 | remove_mut::remove_mut, | 155 | remove_mut::remove_mut, |
155 | replace_if_let_with_match::replace_if_let_with_match, | 156 | replace_if_let_with_match::replace_if_let_with_match, |
157 | replace_let_with_if_let::replace_let_with_if_let, | ||
156 | replace_qualified_name_with_use::replace_qualified_name_with_use, | 158 | replace_qualified_name_with_use::replace_qualified_name_with_use, |
157 | split_import::split_import, | 159 | split_import::split_import, |
158 | ] | 160 | ] |
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index f74c9f9c6..bdaecdc43 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs | |||
@@ -251,7 +251,7 @@ impl ast::UseItem { | |||
251 | #[must_use] | 251 | #[must_use] |
252 | pub fn with_use_tree(&self, use_tree: ast::UseTree) -> ast::UseItem { | 252 | pub fn with_use_tree(&self, use_tree: ast::UseTree) -> ast::UseItem { |
253 | if let Some(old) = self.use_tree() { | 253 | if let Some(old) = self.use_tree() { |
254 | return self.replace_descendants(iter::once((old, use_tree))); | 254 | return self.replace_descendant(old, use_tree); |
255 | } | 255 | } |
256 | self.clone() | 256 | self.clone() |
257 | } | 257 | } |
@@ -283,7 +283,7 @@ impl ast::UseTree { | |||
283 | #[must_use] | 283 | #[must_use] |
284 | pub fn with_path(&self, path: ast::Path) -> ast::UseTree { | 284 | pub fn with_path(&self, path: ast::Path) -> ast::UseTree { |
285 | if let Some(old) = self.path() { | 285 | if let Some(old) = self.path() { |
286 | return self.replace_descendants(iter::once((old, path))); | 286 | return self.replace_descendant(old, path); |
287 | } | 287 | } |
288 | self.clone() | 288 | self.clone() |
289 | } | 289 | } |
@@ -291,7 +291,7 @@ impl ast::UseTree { | |||
291 | #[must_use] | 291 | #[must_use] |
292 | pub fn with_use_tree_list(&self, use_tree_list: ast::UseTreeList) -> ast::UseTree { | 292 | pub fn with_use_tree_list(&self, use_tree_list: ast::UseTreeList) -> ast::UseTree { |
293 | if let Some(old) = self.use_tree_list() { | 293 | if let Some(old) = self.use_tree_list() { |
294 | return self.replace_descendants(iter::once((old, use_tree_list))); | 294 | return self.replace_descendant(old, use_tree_list); |
295 | } | 295 | } |
296 | self.clone() | 296 | self.clone() |
297 | } | 297 | } |
@@ -466,6 +466,11 @@ pub trait AstNodeEdit: AstNode + Sized { | |||
466 | } | 466 | } |
467 | 467 | ||
468 | #[must_use] | 468 | #[must_use] |
469 | fn replace_descendant<D: AstNode>(&self, old: D, new: D) -> Self { | ||
470 | self.replace_descendants(iter::once((old, new))) | ||
471 | } | ||
472 | |||
473 | #[must_use] | ||
469 | fn replace_descendants<D: AstNode>( | 474 | fn replace_descendants<D: AstNode>( |
470 | &self, | 475 | &self, |
471 | replacement_map: impl IntoIterator<Item = (D, D)>, | 476 | replacement_map: impl IntoIterator<Item = (D, D)>, |
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index 6aee39203..c818bba55 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -127,7 +127,7 @@ pub fn condition(expr: ast::Expr, pattern: Option<ast::Pat>) -> ast::Condition { | |||
127 | match pattern { | 127 | match pattern { |
128 | None => ast_from_text(&format!("const _: () = while {} {{}};", expr)), | 128 | None => ast_from_text(&format!("const _: () = while {} {{}};", expr)), |
129 | Some(pattern) => { | 129 | Some(pattern) => { |
130 | ast_from_text(&format!("const _: () = while {} = {} {{}};", pattern, expr)) | 130 | ast_from_text(&format!("const _: () = while let {} = {} {{}};", pattern, expr)) |
131 | } | 131 | } |
132 | } | 132 | } |
133 | } | 133 | } |
@@ -245,7 +245,8 @@ pub fn let_stmt(pattern: ast::Pat, initializer: Option<ast::Expr>) -> ast::LetSt | |||
245 | ast_from_text(&format!("fn f() {{ {} }}", text)) | 245 | ast_from_text(&format!("fn f() {{ {} }}", text)) |
246 | } | 246 | } |
247 | pub fn expr_stmt(expr: ast::Expr) -> ast::ExprStmt { | 247 | pub fn expr_stmt(expr: ast::Expr) -> ast::ExprStmt { |
248 | ast_from_text(&format!("fn f() {{ {}; }}", expr)) | 248 | let semi = if expr.is_block_like() { "" } else { ";" }; |
249 | ast_from_text(&format!("fn f() {{ {}{} (); }}", expr, semi)) | ||
249 | } | 250 | } |
250 | 251 | ||
251 | pub fn token(kind: SyntaxKind) -> SyntaxToken { | 252 | pub fn token(kind: SyntaxKind) -> SyntaxToken { |
diff --git a/docs/user/assists.md b/docs/user/assists.md index f3ce6b0e0..3b7467a26 100644 --- a/docs/user/assists.md +++ b/docs/user/assists.md | |||
@@ -583,6 +583,29 @@ fn handle(action: Action) { | |||
583 | } | 583 | } |
584 | ``` | 584 | ``` |
585 | 585 | ||
586 | ## `replace_let_with_if_let` | ||
587 | |||
588 | Replaces `if let` with an else branch with a `match` expression. | ||
589 | |||
590 | ```rust | ||
591 | // BEFORE | ||
592 | |||
593 | fn main(action: Action) { | ||
594 | ┃let x = compute(); | ||
595 | } | ||
596 | |||
597 | fn compute() -> Option<i32> { None } | ||
598 | |||
599 | // AFTER | ||
600 | |||
601 | fn main(action: Action) { | ||
602 | if let Some(x) = compute() { | ||
603 | } | ||
604 | } | ||
605 | |||
606 | fn compute() -> Option<i32> { None } | ||
607 | ``` | ||
608 | |||
586 | ## `replace_qualified_name_with_use` | 609 | ## `replace_qualified_name_with_use` |
587 | 610 | ||
588 | Adds a use statement for a given fully-qualified name. | 611 | Adds a use statement for a given fully-qualified name. |