Skip to content

Commit 91e482b

Browse files
committed
Replace if with if-let
1 parent cbb53cf commit 91e482b

File tree

6 files changed

+170
-5
lines changed

6 files changed

+170
-5
lines changed

crates/ra_assists/src/doc_tests/generated.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,32 @@ fn handle(action: Action) {
607607
)
608608
}
609609

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+
610636
#[test]
611637
fn doctest_replace_qualified_name_with_use() {
612638
check(
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

crates/ra_assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ mod handlers {
118118
mod remove_dbg;
119119
mod remove_mut;
120120
mod replace_if_let_with_match;
121+
mod replace_let_with_if_let;
121122
mod replace_qualified_name_with_use;
122123
mod split_import;
123124

@@ -153,6 +154,7 @@ mod handlers {
153154
remove_dbg::remove_dbg,
154155
remove_mut::remove_mut,
155156
replace_if_let_with_match::replace_if_let_with_match,
157+
replace_let_with_if_let::replace_let_with_if_let,
156158
replace_qualified_name_with_use::replace_qualified_name_with_use,
157159
split_import::split_import,
158160
]

crates/ra_syntax/src/ast/edit.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ impl ast::UseItem {
251251
#[must_use]
252252
pub fn with_use_tree(&self, use_tree: ast::UseTree) -> ast::UseItem {
253253
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);
255255
}
256256
self.clone()
257257
}
@@ -283,15 +283,15 @@ impl ast::UseTree {
283283
#[must_use]
284284
pub fn with_path(&self, path: ast::Path) -> ast::UseTree {
285285
if let Some(old) = self.path() {
286-
return self.replace_descendants(iter::once((old, path)));
286+
return self.replace_descendant(old, path);
287287
}
288288
self.clone()
289289
}
290290

291291
#[must_use]
292292
pub fn with_use_tree_list(&self, use_tree_list: ast::UseTreeList) -> ast::UseTree {
293293
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);
295295
}
296296
self.clone()
297297
}
@@ -465,6 +465,11 @@ pub trait AstNodeEdit: AstNode + Sized {
465465
Self::cast(new_syntax).unwrap()
466466
}
467467

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+
468473
#[must_use]
469474
fn replace_descendants<D: AstNode>(
470475
&self,

crates/ra_syntax/src/ast/make.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ pub fn condition(expr: ast::Expr, pattern: Option<ast::Pat>) -> ast::Condition {
127127
match pattern {
128128
None => ast_from_text(&format!("const _: () = while {} {{}};", expr)),
129129
Some(pattern) => {
130-
ast_from_text(&format!("const _: () = while {} = {} {{}};", pattern, expr))
130+
ast_from_text(&format!("const _: () = while let {} = {} {{}};", pattern, expr))
131131
}
132132
}
133133
}
@@ -245,7 +245,8 @@ pub fn let_stmt(pattern: ast::Pat, initializer: Option<ast::Expr>) -> ast::LetSt
245245
ast_from_text(&format!("fn f() {{ {} }}", text))
246246
}
247247
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))
249250
}
250251

251252
pub fn token(kind: SyntaxKind) -> SyntaxToken {

docs/user/assists.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,29 @@ fn handle(action: Action) {
583583
}
584584
```
585585

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+
586609
## `replace_qualified_name_with_use`
587610

588611
Adds a use statement for a given fully-qualified name.

0 commit comments

Comments
 (0)