Skip to content

Commit cd3ad5b

Browse files
committed
Assist: replace unwrap with match
1 parent 785eb32 commit cd3ad5b

File tree

7 files changed

+243
-1
lines changed

7 files changed

+243
-1
lines changed

crates/ra_assists/src/doc_tests/generated.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,30 @@ fn process(map: HashMap<String, String>) {}
622622
)
623623
}
624624

625+
#[test]
626+
fn doctest_replace_unwrap_with_match() {
627+
check(
628+
"replace_unwrap_with_match",
629+
r#####"
630+
enum Result<T, E> { Ok(T), Err(E) }
631+
fn main() {
632+
let x: Result<i32, i32> = Result::Ok(92);
633+
let y = x.<|>unwrap();
634+
}
635+
"#####,
636+
r#####"
637+
enum Result<T, E> { Ok(T), Err(E) }
638+
fn main() {
639+
let x: Result<i32, i32> = Result::Ok(92);
640+
let y = match x {
641+
Ok(a) => a,
642+
_ => unreachable!(),
643+
};
644+
}
645+
"#####,
646+
)
647+
}
648+
625649
#[test]
626650
fn doctest_split_import() {
627651
check(
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use std::iter;
2+
3+
use ra_syntax::{
4+
ast::{self, make},
5+
AstNode,
6+
};
7+
use hir::name;
8+
9+
use crate::{Assist, AssistCtx, AssistId};
10+
use ast::edit::IndentLevel;
11+
12+
// Assist: replace_unwrap_with_match
13+
//
14+
// Replaces `unwrap` a `match` expression. Works for Result and Option.
15+
//
16+
// ```
17+
// enum Result<T, E> { Ok(T), Err(E) }
18+
// fn main() {
19+
// let x: Result<i32, i32> = Result::Ok(92);
20+
// let y = x.<|>unwrap();
21+
// }
22+
// ```
23+
// ->
24+
// ```
25+
// enum Result<T, E> { Ok(T), Err(E) }
26+
// fn main() {
27+
// let x: Result<i32, i32> = Result::Ok(92);
28+
// let y = match x {
29+
// Ok(a) => a,
30+
// _ => unreachable!(),
31+
// };
32+
// }
33+
// ```
34+
pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
35+
let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
36+
let name = method_call.name_ref()?;
37+
if name.text() != "unwrap" {
38+
return None;
39+
}
40+
let caller = method_call.expr()?;
41+
let ty = ctx.sema.type_of_expr(&caller)?;
42+
43+
let type_name = ty.as_adt()?.name(ctx.sema.db);
44+
if type_name == name![Result] {
45+
let ok_path = make::path_unqualified(make::path_segment(make::name_ref("Ok")));
46+
let bind_pat = make::bind_pat(make::name("a")).into();
47+
let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(bind_pat)).into();
48+
let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
49+
50+
let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
51+
52+
let unreachable_call = make::unreachable_macro_call().into();
53+
let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
54+
let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
55+
let match_expr = make::expr_match(method_call.expr()?, match_arm_list);
56+
let match_expr = IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr);
57+
58+
return ctx.add_assist(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", |edit| {
59+
edit.target(method_call.syntax().text_range());
60+
edit.set_cursor(caller.syntax().text_range().start());
61+
edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
62+
});
63+
}
64+
65+
if type_name == name![Option] {
66+
let some_path = make::path_unqualified(make::path_segment(make::name_ref("Some")));
67+
let bind_pat = make::bind_pat(make::name("a")).into();
68+
let some_tuple = make::tuple_struct_pat(some_path, iter::once(bind_pat)).into();
69+
let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
70+
let some_arm = make::match_arm(iter::once(some_tuple), make::expr_path(bind_path));
71+
72+
let unreachable_call = make::unreachable_macro_call().into();
73+
let none_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
74+
let match_arm_list = make::match_arm_list(vec![some_arm, none_arm]);
75+
let match_expr = make::expr_match(method_call.expr()?, match_arm_list);
76+
let match_expr = IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr);
77+
78+
return ctx.add_assist(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", |edit| {
79+
edit.target(method_call.syntax().text_range());
80+
edit.set_cursor(caller.syntax().text_range().start());
81+
edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
82+
});
83+
}
84+
85+
None
86+
}
87+
88+
#[cfg(test)]
89+
mod tests {
90+
use super::*;
91+
use crate::helpers::{check_assist, check_assist_target};
92+
93+
#[test]
94+
fn test_replace_result_unwrap_with_match() {
95+
check_assist(
96+
replace_unwrap_with_match,
97+
"
98+
enum Result<T, E> { Ok(T), Err(E) }
99+
fn i<T>(a: T) -> T { a }
100+
fn main() {
101+
let x: Result<i32, i32> = Result::Ok(92);
102+
let y = i(x).<|>unwrap();
103+
}
104+
",
105+
"
106+
enum Result<T, E> { Ok(T), Err(E) }
107+
fn i<T>(a: T) -> T { a }
108+
fn main() {
109+
let x: Result<i32, i32> = Result::Ok(92);
110+
let y = <|>match i(x) {
111+
Ok(a) => a,
112+
_ => unreachable!(),
113+
};
114+
}
115+
",
116+
)
117+
}
118+
119+
#[test]
120+
fn test_replace_option_unwrap_with_match() {
121+
check_assist(
122+
replace_unwrap_with_match,
123+
"
124+
enum Option<T> { Some(T), None }
125+
fn i<T>(a: T) -> T { a }
126+
fn main() {
127+
let x = Option::Some(92);
128+
let y = i(x).<|>unwrap();
129+
}
130+
",
131+
"
132+
enum Option<T> { Some(T), None }
133+
fn i<T>(a: T) -> T { a }
134+
fn main() {
135+
let x = Option::Some(92);
136+
let y = <|>match i(x) {
137+
Some(a) => a,
138+
_ => unreachable!(),
139+
};
140+
}
141+
",
142+
);
143+
}
144+
145+
#[test]
146+
fn test_replace_result_unwrap_with_match_chaining() {
147+
check_assist(
148+
replace_unwrap_with_match,
149+
"
150+
enum Result<T, E> { Ok(T), Err(E) }
151+
fn i<T>(a: T) -> T { a }
152+
fn main() {
153+
let x: Result<i32, i32> = Result::Ok(92);
154+
let y = i(x).<|>unwrap().count_zeroes();
155+
}
156+
",
157+
"
158+
enum Result<T, E> { Ok(T), Err(E) }
159+
fn i<T>(a: T) -> T { a }
160+
fn main() {
161+
let x: Result<i32, i32> = Result::Ok(92);
162+
let y = <|>match i(x) {
163+
Ok(a) => a,
164+
_ => unreachable!(),
165+
}.count_zeroes();
166+
}
167+
",
168+
)
169+
}
170+
171+
172+
#[test]
173+
fn replace_unwrap_with_match_target() {
174+
check_assist_target(
175+
replace_unwrap_with_match,
176+
"
177+
enum Option<T> { Some(T), None }
178+
fn i<T>(a: T) -> T { a }
179+
fn main() {
180+
let x = Option::Some(92);
181+
let y = i(x).<|>unwrap();
182+
}
183+
",
184+
"i(x).unwrap()"
185+
);
186+
}
187+
188+
}

crates/ra_assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ mod handlers {
119119
mod remove_mut;
120120
mod replace_if_let_with_match;
121121
mod replace_qualified_name_with_use;
122+
mod replace_unwrap_with_match;
122123
mod split_import;
123124

124125
pub(crate) fn all() -> &'static [AssistHandler] {
@@ -154,6 +155,7 @@ mod handlers {
154155
remove_mut::remove_mut,
155156
replace_if_let_with_match::replace_if_let_with_match,
156157
replace_qualified_name_with_use::replace_qualified_name_with_use,
158+
replace_unwrap_with_match::replace_unwrap_with_match,
157159
split_import::split_import,
158160
]
159161
}

crates/ra_hir/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,6 @@ pub use hir_def::{
7070
type_ref::Mutability,
7171
};
7272
pub use hir_expand::{
73-
name::Name, HirFileId, InFile, MacroCallId, MacroCallLoc, MacroDefId, MacroFile, Origin,
73+
name::Name, name::name, HirFileId, InFile, MacroCallId, MacroCallLoc, MacroDefId, MacroFile, Origin,
7474
};
7575
pub use hir_ty::{display::HirDisplay, CallableDef};

crates/ra_hir_expand/src/name.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ pub mod known {
155155
Ok,
156156
Future,
157157
Result,
158+
Option,
158159
Output,
159160
Target,
160161
Box,

crates/ra_syntax/src/ast/make.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,10 @@ pub fn token(kind: SyntaxKind) -> SyntaxToken {
250250
.unwrap_or_else(|| panic!("unhandled token: {:?}", kind))
251251
}
252252

253+
pub fn unreachable_macro_call() -> ast::MacroCall {
254+
ast_from_text(&format!("unreachable!()"))
255+
}
256+
253257
fn ast_from_text<N: AstNode>(text: &str) -> N {
254258
let parse = SourceFile::parse(text);
255259
let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap();

docs/user/assists.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,29 @@ use std::collections::HashMap;
597597
fn process(map: HashMap<String, String>) {}
598598
```
599599

600+
## `replace_unwrap_with_match`
601+
602+
Replaces `unwrap` a `match` expression. Works for Result and Option.
603+
604+
```rust
605+
// BEFORE
606+
enum Result<T, E> { Ok(T), Err(E) }
607+
fn main() {
608+
let x: Result<i32, i32> = Result::Ok(92);
609+
let y = x.unwrap();
610+
}
611+
612+
// AFTER
613+
enum Result<T, E> { Ok(T), Err(E) }
614+
fn main() {
615+
let x: Result<i32, i32> = Result::Ok(92);
616+
let y = match x {
617+
Ok(a) => a,
618+
_ => unreachable!(),
619+
};
620+
}
621+
```
622+
600623
## `split_import`
601624

602625
Wraps the tail of import into braces.

0 commit comments

Comments
 (0)