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