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::(try_expr.clone().into(), expr_match.into()); }, ); if let Some(let_stmt) = try_expr.syntax().parent().and_then(ast::LetStmt::cast) && 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", ); } }