Skip to content

Commit 20c110e

Browse files
Merge #3732
3732: Assist: replace unwrap with match r=matklad a=unrealhoang attempt on #3669 Co-authored-by: Unreal Hoang <unrealhoang@gmail.com>
2 parents 0a8e65c + d9df0f4 commit 20c110e

File tree

5 files changed

+230
-0
lines changed

5 files changed

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

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_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)