Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/desugar_try_expr.rs')
-rw-r--r--crates/ide-assists/src/handlers/desugar_try_expr.rs281
1 files changed, 281 insertions, 0 deletions
diff --git a/crates/ide-assists/src/handlers/desugar_try_expr.rs b/crates/ide-assists/src/handlers/desugar_try_expr.rs
new file mode 100644
index 0000000000..efadde9e36
--- /dev/null
+++ b/crates/ide-assists/src/handlers/desugar_try_expr.rs
@@ -0,0 +1,281 @@
+use std::iter;
+
+use ide_db::{
+ assists::{AssistId, ExprFillDefaultMode},
+ ty_filter::TryEnum,
+};
+use syntax::{
+ AstNode, T,
+ ast::{
+ self,
+ edit::{AstNodeEdit, IndentLevel},
+ make,
+ syntax_factory::SyntaxFactory,
+ },
+};
+
+use crate::assist_context::{AssistContext, Assists};
+
+// Assist: desugar_try_expr_match
+//
+// Replaces a `try` expression with a `match` expression.
+//
+// ```
+// # //- minicore: try, option
+// fn handle() {
+// let pat = Some(true)$0?;
+// }
+// ```
+// ->
+// ```
+// fn handle() {
+// let pat = match Some(true) {
+// Some(it) => it,
+// None => return None,
+// };
+// }
+// ```
+
+// Assist: desugar_try_expr_let_else
+//
+// Replaces a `try` expression with a `let else` statement.
+//
+// ```
+// # //- minicore: try, option
+// fn handle() {
+// let pat = Some(true)$0?;
+// }
+// ```
+// ->
+// ```
+// fn handle() {
+// let Some(pat) = Some(true) else {
+// return None;
+// };
+// }
+// ```
+pub(crate) fn desugar_try_expr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let question_tok = ctx.find_token_syntax_at_offset(T![?])?;
+ let try_expr = question_tok.parent().and_then(ast::TryExpr::cast)?;
+
+ let expr = try_expr.expr()?;
+ let expr_type_info = ctx.sema.type_of_expr(&expr)?;
+
+ let try_enum = TryEnum::from_ty(&ctx.sema, &expr_type_info.original)?;
+
+ let target = try_expr.syntax().text_range();
+ acc.add(
+ AssistId::refactor_rewrite("desugar_try_expr_match"),
+ "Replace try expression with match",
+ target,
+ |edit| {
+ let sad_pat = match try_enum {
+ TryEnum::Option => make::path_pat(make::ext::ident_path("None")),
+ TryEnum::Result => make::tuple_struct_pat(
+ make::ext::ident_path("Err"),
+ iter::once(make::path_pat(make::ext::ident_path("err"))),
+ )
+ .into(),
+ };
+ let sad_expr = match try_enum {
+ TryEnum::Option => {
+ make::expr_return(Some(make::expr_path(make::ext::ident_path("None"))))
+ }
+ TryEnum::Result => make::expr_return(Some(
+ make::expr_call(
+ make::expr_path(make::ext::ident_path("Err")),
+ make::arg_list(iter::once(make::expr_path(make::ext::ident_path("err")))),
+ )
+ .into(),
+ )),
+ };
+
+ let happy_arm = make::match_arm(
+ try_enum.happy_pattern(make::ident_pat(false, false, make::name("it")).into()),
+ None,
+ make::expr_path(make::ext::ident_path("it")),
+ );
+ let sad_arm = make::match_arm(sad_pat, None, sad_expr);
+
+ let match_arm_list = make::match_arm_list([happy_arm, sad_arm]);
+
+ let expr_match = make::expr_match(expr.clone(), match_arm_list)
+ .indent(IndentLevel::from_node(try_expr.syntax()));
+
+ edit.replace_ast::<ast::Expr>(try_expr.clone().into(), expr_match.into());
+ },
+ );
+
+ if let Some(let_stmt) = try_expr.syntax().parent().and_then(ast::LetStmt::cast) {
+ if let_stmt.let_else().is_none() {
+ let pat = let_stmt.pat()?;
+ acc.add(
+ AssistId::refactor_rewrite("desugar_try_expr_let_else"),
+ "Replace try expression with let else",
+ target,
+ |builder| {
+ let make = SyntaxFactory::with_mappings();
+ let mut editor = builder.make_editor(let_stmt.syntax());
+
+ let indent_level = IndentLevel::from_node(let_stmt.syntax());
+ let new_let_stmt = make.let_else_stmt(
+ try_enum.happy_pattern(pat),
+ let_stmt.ty(),
+ expr,
+ make.block_expr(
+ iter::once(
+ make.expr_stmt(
+ make.expr_return(Some(match try_enum {
+ TryEnum::Option => make.expr_path(make.ident_path("None")),
+ TryEnum::Result => make
+ .expr_call(
+ make.expr_path(make.ident_path("Err")),
+ make.arg_list(iter::once(
+ match ctx.config.expr_fill_default {
+ ExprFillDefaultMode::Todo => make
+ .expr_macro(
+ make.ident_path("todo"),
+ make.token_tree(
+ syntax::SyntaxKind::L_PAREN,
+ [],
+ ),
+ )
+ .into(),
+ ExprFillDefaultMode::Underscore => {
+ make.expr_underscore().into()
+ }
+ ExprFillDefaultMode::Default => make
+ .expr_macro(
+ make.ident_path("todo"),
+ make.token_tree(
+ syntax::SyntaxKind::L_PAREN,
+ [],
+ ),
+ )
+ .into(),
+ },
+ )),
+ )
+ .into(),
+ }))
+ .indent(indent_level + 1)
+ .into(),
+ )
+ .into(),
+ ),
+ None,
+ )
+ .indent(indent_level),
+ );
+ editor.replace(let_stmt.syntax(), new_let_stmt.syntax());
+ editor.add_mappings(make.finish_with_mappings());
+ builder.add_file_edits(ctx.vfs_file_id(), editor);
+ },
+ );
+ }
+ }
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::{check_assist, check_assist_by_label, check_assist_not_applicable};
+
+ #[test]
+ fn test_desugar_try_expr_not_applicable() {
+ check_assist_not_applicable(
+ desugar_try_expr,
+ r#"
+ fn test() {
+ let pat: u32 = 25$0;
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_desugar_try_expr_option() {
+ check_assist(
+ desugar_try_expr,
+ r#"
+//- minicore: try, option
+fn test() {
+ let pat = Some(true)$0?;
+}
+ "#,
+ r#"
+fn test() {
+ let pat = match Some(true) {
+ Some(it) => it,
+ None => return None,
+ };
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_desugar_try_expr_result() {
+ check_assist(
+ desugar_try_expr,
+ r#"
+//- minicore: try, from, result
+fn test() {
+ let pat = Ok(true)$0?;
+}
+ "#,
+ r#"
+fn test() {
+ let pat = match Ok(true) {
+ Ok(it) => it,
+ Err(err) => return Err(err),
+ };
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn test_desugar_try_expr_option_let_else() {
+ check_assist_by_label(
+ desugar_try_expr,
+ r#"
+//- minicore: try, option
+fn test() {
+ let pat = Some(true)$0?;
+}
+ "#,
+ r#"
+fn test() {
+ let Some(pat) = Some(true) else {
+ return None;
+ };
+}
+ "#,
+ "Replace try expression with let else",
+ );
+ }
+
+ #[test]
+ fn test_desugar_try_expr_result_let_else() {
+ check_assist_by_label(
+ desugar_try_expr,
+ r#"
+//- minicore: try, from, result
+fn test() {
+ let pat = Ok(true)$0?;
+}
+ "#,
+ r#"
+fn test() {
+ let Ok(pat) = Ok(true) else {
+ return Err(todo!());
+ };
+}
+ "#,
+ "Replace try expression with let else",
+ );
+ }
+}