use ide_db::ty_filter::TryEnum; use syntax::{ AstNode, T, ast::{ self, edit::{AstNodeEdit, IndentLevel}, syntax_factory::SyntaxFactory, }, }; use crate::{AssistContext, AssistId, Assists}; // Assist: replace_let_with_if_let // // Replaces `let` with an `if let`. // // ``` // # enum Option { Some(T), None } // // fn main(action: Action) { // $0let x = compute(); // } // // fn compute() -> Option { None } // ``` // -> // ``` // # enum Option { Some(T), None } // // fn main(action: Action) { // if let Some(x) = compute() { // } // } // // fn compute() -> Option { None } // ``` pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let let_kw = ctx.find_token_syntax_at_offset(T![let])?; let let_stmt = let_kw.parent().and_then(ast::LetStmt::cast)?; let init = let_stmt.initializer()?; let original_pat = let_stmt.pat()?; let target = let_kw.text_range(); acc.add( AssistId::refactor_rewrite("replace_let_with_if_let"), "Replace let with if let", target, |builder| { let mut editor = builder.make_editor(let_stmt.syntax()); let make = SyntaxFactory::with_mappings(); let ty = ctx.sema.type_of_expr(&init); let pat = if let_stmt.let_else().is_some() { // Do not add the wrapper type that implements `Try`, // since the statement already wraps the pattern. original_pat } else { let happy_variant = ty .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty.adjusted())) .map(|it| it.happy_case()); match happy_variant { None => original_pat, Some(var_name) => { make.tuple_struct_pat(make.ident_path(var_name), [original_pat]).into() } } }; let init_expr = if let_expr_needs_paren(&init) { make.expr_paren(init).into() } else { init }; let block = make.block_expr([], None); let block = block.indent(IndentLevel::from_node(let_stmt.syntax())); let if_expr = make.expr_if( make.expr_let(pat, init_expr).into(), block, let_stmt .let_else() .and_then(|let_else| let_else.block_expr().map(ast::ElseBranch::from)), ); let if_stmt = make.expr_stmt(if_expr.into()); editor.replace(let_stmt.syntax(), if_stmt.syntax()); editor.add_mappings(make.finish_with_mappings()); builder.add_file_edits(ctx.vfs_file_id(), editor); }, ) } fn let_expr_needs_paren(expr: &ast::Expr) -> bool { let fake_expr_let = ast::make::expr_let(ast::make::tuple_pat(None).into(), ast::make::ext::expr_unit()); let Some(fake_expr) = fake_expr_let.expr() else { stdx::never!(); return false; }; expr.needs_parens_in_place_of(fake_expr_let.syntax(), fake_expr.syntax()) } #[cfg(test)] mod tests { use crate::tests::check_assist; use super::*; #[test] fn replace_let_try_enum_ref() { check_assist( replace_let_with_if_let, r" //- minicore: option fn main(action: Action) { $0let x = compute(); } fn compute() -> &'static Option { &None } ", r" fn main(action: Action) { if let Some(x) = compute() { } } fn compute() -> &'static Option { &None } ", ) } #[test] fn replace_let_unknown_enum() { check_assist( replace_let_with_if_let, r" enum E { X(T), Y(T) } fn main() { $0let x = E::X(92); } ", r" enum E { X(T), Y(T) } fn main() { if let x = E::X(92) { } } ", ) } #[test] fn replace_let_logic_and() { check_assist( replace_let_with_if_let, r" fn main() { $0let x = true && false; } ", r" fn main() { if let x = (true && false) { } } ", ) } #[test] fn replace_let_logic_or() { check_assist( replace_let_with_if_let, r" fn main() { $0let x = true || false; } ", r" fn main() { if let x = (true || false) { } } ", ) } #[test] fn replace_let_else() { check_assist( replace_let_with_if_let, r" //- minicore: option fn main() { let a = Some(1); $0let Some(_) = a else { unreachable!() }; } ", r" fn main() { let a = Some(1); if let Some(_) = a { } else { unreachable!() } } ", ) } }