Skip to content

Commit ed608f5

Browse files
authored
Merge pull request #19881 from Veykril/push-wsrmttkymyps
feat: Desugar assist for `let pat = expr?;` -> `let else`
2 parents 5900e25 + 751ca9e commit ed608f5

File tree

5 files changed

+336
-171
lines changed

5 files changed

+336
-171
lines changed
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
use std::iter;
2+
3+
use ide_db::{
4+
assists::{AssistId, ExprFillDefaultMode},
5+
ty_filter::TryEnum,
6+
};
7+
use syntax::{
8+
AstNode, T,
9+
ast::{
10+
self,
11+
edit::{AstNodeEdit, IndentLevel},
12+
make,
13+
syntax_factory::SyntaxFactory,
14+
},
15+
};
16+
17+
use crate::assist_context::{AssistContext, Assists};
18+
19+
// Assist: desugar_try_expr_match
20+
//
21+
// Replaces a `try` expression with a `match` expression.
22+
//
23+
// ```
24+
// # //- minicore: try, option
25+
// fn handle() {
26+
// let pat = Some(true)$0?;
27+
// }
28+
// ```
29+
// ->
30+
// ```
31+
// fn handle() {
32+
// let pat = match Some(true) {
33+
// Some(it) => it,
34+
// None => return None,
35+
// };
36+
// }
37+
// ```
38+
39+
// Assist: desugar_try_expr_let_else
40+
//
41+
// Replaces a `try` expression with a `let else` statement.
42+
//
43+
// ```
44+
// # //- minicore: try, option
45+
// fn handle() {
46+
// let pat = Some(true)$0?;
47+
// }
48+
// ```
49+
// ->
50+
// ```
51+
// fn handle() {
52+
// let Some(pat) = Some(true) else {
53+
// return None;
54+
// };
55+
// }
56+
// ```
57+
pub(crate) fn desugar_try_expr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
58+
let question_tok = ctx.find_token_syntax_at_offset(T![?])?;
59+
let try_expr = question_tok.parent().and_then(ast::TryExpr::cast)?;
60+
61+
let expr = try_expr.expr()?;
62+
let expr_type_info = ctx.sema.type_of_expr(&expr)?;
63+
64+
let try_enum = TryEnum::from_ty(&ctx.sema, &expr_type_info.original)?;
65+
66+
let target = try_expr.syntax().text_range();
67+
acc.add(
68+
AssistId::refactor_rewrite("desugar_try_expr_match"),
69+
"Replace try expression with match",
70+
target,
71+
|edit| {
72+
let sad_pat = match try_enum {
73+
TryEnum::Option => make::path_pat(make::ext::ident_path("None")),
74+
TryEnum::Result => make::tuple_struct_pat(
75+
make::ext::ident_path("Err"),
76+
iter::once(make::path_pat(make::ext::ident_path("err"))),
77+
)
78+
.into(),
79+
};
80+
let sad_expr = match try_enum {
81+
TryEnum::Option => {
82+
make::expr_return(Some(make::expr_path(make::ext::ident_path("None"))))
83+
}
84+
TryEnum::Result => make::expr_return(Some(
85+
make::expr_call(
86+
make::expr_path(make::ext::ident_path("Err")),
87+
make::arg_list(iter::once(make::expr_path(make::ext::ident_path("err")))),
88+
)
89+
.into(),
90+
)),
91+
};
92+
93+
let happy_arm = make::match_arm(
94+
try_enum.happy_pattern(make::ident_pat(false, false, make::name("it")).into()),
95+
None,
96+
make::expr_path(make::ext::ident_path("it")),
97+
);
98+
let sad_arm = make::match_arm(sad_pat, None, sad_expr);
99+
100+
let match_arm_list = make::match_arm_list([happy_arm, sad_arm]);
101+
102+
let expr_match = make::expr_match(expr.clone(), match_arm_list)
103+
.indent(IndentLevel::from_node(try_expr.syntax()));
104+
105+
edit.replace_ast::<ast::Expr>(try_expr.clone().into(), expr_match.into());
106+
},
107+
);
108+
109+
if let Some(let_stmt) = try_expr.syntax().parent().and_then(ast::LetStmt::cast) {
110+
if let_stmt.let_else().is_none() {
111+
let pat = let_stmt.pat()?;
112+
acc.add(
113+
AssistId::refactor_rewrite("desugar_try_expr_let_else"),
114+
"Replace try expression with let else",
115+
target,
116+
|builder| {
117+
let make = SyntaxFactory::with_mappings();
118+
let mut editor = builder.make_editor(let_stmt.syntax());
119+
120+
let indent_level = IndentLevel::from_node(let_stmt.syntax());
121+
let new_let_stmt = make.let_else_stmt(
122+
try_enum.happy_pattern(pat),
123+
let_stmt.ty(),
124+
expr,
125+
make.block_expr(
126+
iter::once(
127+
make.expr_stmt(
128+
make.expr_return(Some(match try_enum {
129+
TryEnum::Option => make.expr_path(make.ident_path("None")),
130+
TryEnum::Result => make
131+
.expr_call(
132+
make.expr_path(make.ident_path("Err")),
133+
make.arg_list(iter::once(
134+
match ctx.config.expr_fill_default {
135+
ExprFillDefaultMode::Todo => make
136+
.expr_macro(
137+
make.ident_path("todo"),
138+
make.token_tree(
139+
syntax::SyntaxKind::L_PAREN,
140+
[],
141+
),
142+
)
143+
.into(),
144+
ExprFillDefaultMode::Underscore => {
145+
make.expr_underscore().into()
146+
}
147+
ExprFillDefaultMode::Default => make
148+
.expr_macro(
149+
make.ident_path("todo"),
150+
make.token_tree(
151+
syntax::SyntaxKind::L_PAREN,
152+
[],
153+
),
154+
)
155+
.into(),
156+
},
157+
)),
158+
)
159+
.into(),
160+
}))
161+
.indent(indent_level + 1)
162+
.into(),
163+
)
164+
.into(),
165+
),
166+
None,
167+
)
168+
.indent(indent_level),
169+
);
170+
editor.replace(let_stmt.syntax(), new_let_stmt.syntax());
171+
editor.add_mappings(make.finish_with_mappings());
172+
builder.add_file_edits(ctx.vfs_file_id(), editor);
173+
},
174+
);
175+
}
176+
}
177+
Some(())
178+
}
179+
180+
#[cfg(test)]
181+
mod tests {
182+
use super::*;
183+
184+
use crate::tests::{check_assist, check_assist_by_label, check_assist_not_applicable};
185+
186+
#[test]
187+
fn test_desugar_try_expr_not_applicable() {
188+
check_assist_not_applicable(
189+
desugar_try_expr,
190+
r#"
191+
fn test() {
192+
let pat: u32 = 25$0;
193+
}
194+
"#,
195+
);
196+
}
197+
198+
#[test]
199+
fn test_desugar_try_expr_option() {
200+
check_assist(
201+
desugar_try_expr,
202+
r#"
203+
//- minicore: try, option
204+
fn test() {
205+
let pat = Some(true)$0?;
206+
}
207+
"#,
208+
r#"
209+
fn test() {
210+
let pat = match Some(true) {
211+
Some(it) => it,
212+
None => return None,
213+
};
214+
}
215+
"#,
216+
);
217+
}
218+
219+
#[test]
220+
fn test_desugar_try_expr_result() {
221+
check_assist(
222+
desugar_try_expr,
223+
r#"
224+
//- minicore: try, from, result
225+
fn test() {
226+
let pat = Ok(true)$0?;
227+
}
228+
"#,
229+
r#"
230+
fn test() {
231+
let pat = match Ok(true) {
232+
Ok(it) => it,
233+
Err(err) => return Err(err),
234+
};
235+
}
236+
"#,
237+
);
238+
}
239+
240+
#[test]
241+
fn test_desugar_try_expr_option_let_else() {
242+
check_assist_by_label(
243+
desugar_try_expr,
244+
r#"
245+
//- minicore: try, option
246+
fn test() {
247+
let pat = Some(true)$0?;
248+
}
249+
"#,
250+
r#"
251+
fn test() {
252+
let Some(pat) = Some(true) else {
253+
return None;
254+
};
255+
}
256+
"#,
257+
"Replace try expression with let else",
258+
);
259+
}
260+
261+
#[test]
262+
fn test_desugar_try_expr_result_let_else() {
263+
check_assist_by_label(
264+
desugar_try_expr,
265+
r#"
266+
//- minicore: try, from, result
267+
fn test() {
268+
let pat = Ok(true)$0?;
269+
}
270+
"#,
271+
r#"
272+
fn test() {
273+
let Ok(pat) = Ok(true) else {
274+
return Err(todo!());
275+
};
276+
}
277+
"#,
278+
"Replace try expression with let else",
279+
);
280+
}
281+
}

0 commit comments

Comments
 (0)