Skip to main content

ide_assists/handlers/
desugar_try_expr.rs

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
15// Assist: desugar_try_expr_match
16//
17// Replaces a `try` expression with a `match` expression.
18//
19// ```
20// # //- minicore: try, option
21// fn handle() {
22//     let pat = Some(true)$0?;
23// }
24// ```
25// ->
26// ```
27// fn handle() {
28//     let pat = match Some(true) {
29//         Some(it) => it,
30//         None => return None,
31//     };
32// }
33// ```
34
35// Assist: desugar_try_expr_let_else
36//
37// Replaces a `try` expression with a `let else` statement.
38//
39// ```
40// # //- minicore: try, option
41// fn handle() {
42//     let pat = Some(true)$0?;
43// }
44// ```
45// ->
46// ```
47// fn handle() {
48//     let Some(pat) = Some(true) else {
49//         return None;
50//     };
51// }
52// ```
53pub(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}