use either::Either; use ide_db::syntax_helpers::suggest_name; use syntax::ast::{self, AstNode, HasArgList, syntax_factory::SyntaxFactory}; use crate::{AssistContext, AssistId, Assists, utils::cover_let_chain}; // Assist: replace_is_some_with_if_let_some // // Replace `if x.is_some()` with `if let Some(_tmp) = x` or `if x.is_ok()` with `if let Ok(_tmp) = x`. // // ``` // fn main() { // let x = Some(1); // if x.is_som$0e() {} // } // ``` // -> // ``` // fn main() { // let x = Some(1); // if let Some(${0:x1}) = x {} // } // ``` pub(crate) fn replace_is_method_with_if_let_method( acc: &mut Assists, ctx: &AssistContext<'_>, ) -> Option<()> { let has_cond = ctx.find_node_at_offset::>()?; let cond = either::for_both!(&has_cond, it => it.condition())?; let cond = cover_let_chain(cond, ctx.selection_trimmed())?; let call_expr = match cond { ast::Expr::MethodCallExpr(call) => call, _ => return None, }; let token = call_expr.name_ref()?.ident_token()?; let method_kind = token.text().strip_suffix("_and").unwrap_or(token.text()); match method_kind { "is_some" | "is_ok" => { let receiver = call_expr.receiver()?; let mut name_generator = suggest_name::NameGenerator::new_from_scope_locals( ctx.sema.scope(has_cond.syntax()), ); let var_name = if let ast::Expr::PathExpr(path_expr) = receiver.clone() { name_generator.suggest_name(&path_expr.path()?.to_string()) } else { name_generator.for_variable(&receiver, &ctx.sema) }; let (pat, predicate) = method_predicate(&call_expr).unzip(); let (assist_id, message, text) = if method_kind == "is_some" { ("replace_is_some_with_if_let_some", "Replace `is_some` with `let Some`", "Some") } else { ("replace_is_ok_with_if_let_ok", "Replace `is_ok` with `let Ok`", "Ok") }; acc.add( AssistId::refactor_rewrite(assist_id), message, call_expr.syntax().text_range(), |edit| { let make = SyntaxFactory::with_mappings(); let mut editor = edit.make_editor(call_expr.syntax()); let var_pat = pat.unwrap_or_else(|| { make.ident_pat(false, false, make.name(&var_name)).into() }); let pat = make.tuple_struct_pat(make.ident_path(text), [var_pat]).into(); let let_expr = make.expr_let(pat, receiver); if let Some(cap) = ctx.config.snippet_cap && let Some(ast::Pat::TupleStructPat(pat)) = let_expr.pat() && let Some(first_var) = pat.fields().next() && predicate.is_none() { let placeholder = edit.make_placeholder_snippet(cap); editor.add_annotation(first_var.syntax(), placeholder); } let new_expr = if let Some(predicate) = predicate { let op = ast::BinaryOp::LogicOp(ast::LogicOp::And); make.expr_bin(let_expr.into(), op, predicate).into() } else { ast::Expr::from(let_expr) }; editor.replace(call_expr.syntax(), new_expr.syntax()); editor.add_mappings(make.finish_with_mappings()); edit.add_file_edits(ctx.vfs_file_id(), editor); }, ) } _ => None, } } fn method_predicate(call_expr: &ast::MethodCallExpr) -> Option<(ast::Pat, ast::Expr)> { let argument = call_expr.arg_list()?.args().next()?; match argument { ast::Expr::ClosureExpr(it) => { let pat = it.param_list()?.params().next()?.pat()?; Some((pat, it.body()?)) } _ => None, } } #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; use super::replace_is_method_with_if_let_method; #[test] fn replace_is_some_with_if_let_some_works() { check_assist( replace_is_method_with_if_let_method, r#" fn main() { let x = Some(1); if x.is_som$0e() {} } "#, r#" fn main() { let x = Some(1); if let Some(${0:x1}) = x {} } "#, ); check_assist( replace_is_method_with_if_let_method, r#" fn test() -> Option { Some(1) } fn main() { if test().is_som$0e() {} } "#, r#" fn test() -> Option { Some(1) } fn main() { if let Some(${0:test}) = test() {} } "#, ); } #[test] fn replace_is_some_with_if_let_some_not_applicable() { check_assist_not_applicable( replace_is_method_with_if_let_method, r#" fn main() { let x = Some(1); if x.is_non$0e() {} } "#, ); } #[test] fn replace_is_ok_with_if_let_ok_works() { check_assist( replace_is_method_with_if_let_method, r#" fn main() { let x = Ok(1); if x.is_o$0k() {} } "#, r#" fn main() { let x = Ok(1); if let Ok(${0:x1}) = x {} } "#, ); check_assist( replace_is_method_with_if_let_method, r#" fn test() -> Result { Ok(1) } fn main() { if test().is_o$0k() {} } "#, r#" fn test() -> Result { Ok(1) } fn main() { if let Ok(${0:test}) = test() {} } "#, ); } #[test] fn replace_is_ok_with_if_let_ok_not_applicable() { check_assist_not_applicable( replace_is_method_with_if_let_method, r#" fn main() { let x = Ok(1); if x.is_e$0rr() {} } "#, ); } #[test] fn replace_is_some_and_with_if_let_chain_some_works() { check_assist( replace_is_method_with_if_let_method, r#" fn main() { let x = Some(1); if x.is_som$0e_and(|it| it != 3) {} } "#, r#" fn main() { let x = Some(1); if let Some(it) = x && it != 3 {} } "#, ); } #[test] fn replace_is_some_with_if_let_some_in_let_chain() { check_assist( replace_is_method_with_if_let_method, r#" fn main() { let x = Some(1); let cond = true; if cond && x.is_som$0e() {} } "#, r#" fn main() { let x = Some(1); let cond = true; if cond && let Some(${0:x1}) = x {} } "#, ); check_assist( replace_is_method_with_if_let_method, r#" fn main() { let x = Some(1); let cond = true; if x.is_som$0e() && cond {} } "#, r#" fn main() { let x = Some(1); let cond = true; if let Some(${0:x1}) = x && cond {} } "#, ); check_assist( replace_is_method_with_if_let_method, r#" fn main() { let x = Some(1); let cond = true; if cond && x.is_som$0e() && cond {} } "#, r#" fn main() { let x = Some(1); let cond = true; if cond && let Some(${0:x1}) = x && cond {} } "#, ); } #[test] fn replace_is_some_with_while_let_some() { check_assist( replace_is_method_with_if_let_method, r#" fn main() { let mut x = Some(1); while x.is_som$0e() { x = None } } "#, r#" fn main() { let mut x = Some(1); while let Some(${0:x1}) = x { x = None } } "#, ); } #[test] fn replace_is_some_with_if_let_some_not_applicable_after_l_curly() { check_assist_not_applicable( replace_is_method_with_if_let_method, r#" fn main() { let x = Some(1); if x.is_some() { ()$0 } } "#, ); } }