1 use hir::{known, AsAssocItem, Semantics};
3 helpers::{for_each_tail_expr, FamousDefs},
6 use itertools::Itertools;
8 ast::{self, edit::AstNodeEdit, make, ArgListOwner},
9 ted, AstNode, SyntaxNode,
13 utils::{invert_boolean_expression, unwrap_trivial_block},
14 AssistContext, AssistId, AssistKind, Assists,
17 // Assist: convert_if_to_bool_then
19 // Converts an if expression into a corresponding `bool::then` call.
22 // # //- minicore: option
37 pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 // todo, applies to match as well
39 let expr = ctx.find_node_at_offset::<ast::IfExpr>()?;
40 if !expr.if_token()?.text_range().contains_inclusive(ctx.offset()) {
44 let cond = expr.condition().filter(|cond| !cond.is_pattern_cond())?;
45 let cond = cond.expr()?;
46 let then = expr.then_branch()?;
47 let else_ = match expr.else_branch()? {
48 ast::ElseBranch::Block(b) => b,
49 ast::ElseBranch::IfExpr(_) => {
50 cov_mark::hit!(convert_if_to_bool_then_chain);
55 let (none_variant, some_variant) = option_variants(&ctx.sema, expr.syntax())?;
57 let (invert_cond, closure_body) = match (
58 block_is_none_variant(&ctx.sema, &then, none_variant),
59 block_is_none_variant(&ctx.sema, &else_, none_variant),
61 (invert @ true, false) => (invert, ast::Expr::BlockExpr(else_)),
62 (invert @ false, true) => (invert, ast::Expr::BlockExpr(then)),
66 if is_invalid_body(&ctx.sema, some_variant, &closure_body) {
67 cov_mark::hit!(convert_if_to_bool_then_pattern_invalid_body);
71 let target = expr.syntax().text_range();
73 AssistId("convert_if_to_bool_then", AssistKind::RefactorRewrite),
74 "Convert `if` expression to `bool::then` call",
77 let closure_body = closure_body.clone_for_update();
78 // Rewrite all `Some(e)` in tail position to `e`
79 let mut replacements = Vec::new();
80 for_each_tail_expr(&closure_body, &mut |e| {
82 ast::Expr::BreakExpr(e) => e.expr(),
83 e @ ast::Expr::CallExpr(_) => Some(e.clone()),
86 if let Some(ast::Expr::CallExpr(call)) = e {
87 if let Some(arg_list) = call.arg_list() {
88 if let Some(arg) = arg_list.args().next() {
89 replacements.push((call.syntax().clone(), arg.syntax().clone()));
94 replacements.into_iter().for_each(|(old, new)| ted::replace(old, new));
95 let closure_body = match closure_body {
96 ast::Expr::BlockExpr(block) => unwrap_trivial_block(block),
100 let cond = if invert_cond { invert_boolean_expression(&ctx.sema, cond) } else { cond };
101 let arg_list = make::arg_list(Some(make::expr_closure(None, closure_body)));
102 let mcall = make::expr_method_call(cond, make::name_ref("then"), arg_list);
103 builder.replace(target, mcall.to_string());
108 // Assist: convert_bool_then_to_if
110 // Converts a `bool::then` method call to an equivalent if expression.
113 // # //- minicore: bool_impl
115 // (0 == 0).then$0(|| val)
128 pub(crate) fn convert_bool_then_to_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
129 let name_ref = ctx.find_node_at_offset::<ast::NameRef>()?;
130 let mcall = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
131 let receiver = mcall.receiver()?;
132 let closure_body = mcall.arg_list()?.args().exactly_one().ok()?;
133 let closure_body = match closure_body {
134 ast::Expr::ClosureExpr(expr) => expr.body()?,
137 // Verify this is `bool::then` that is being called.
138 let func = ctx.sema.resolve_method_call(&mcall)?;
139 if func.name(ctx.sema.db).to_string() != "then" {
142 let assoc = func.as_assoc_item(ctx.sema.db)?;
143 match assoc.container(ctx.sema.db) {
144 hir::AssocItemContainer::Impl(impl_) if impl_.self_ty(ctx.sema.db).is_bool() => {}
148 let target = mcall.syntax().text_range();
150 AssistId("convert_bool_then_to_if", AssistKind::RefactorRewrite),
151 "Convert `bool::then` call to `if`",
154 let closure_body = match closure_body {
155 ast::Expr::BlockExpr(block) => block,
156 e => make::block_expr(None, Some(e)),
159 let closure_body = closure_body.clone_for_update();
160 // Wrap all tails in `Some(...)`
161 let none_path = make::expr_path(make::ext::ident_path("None"));
162 let some_path = make::expr_path(make::ext::ident_path("Some"));
163 let mut replacements = Vec::new();
164 for_each_tail_expr(&ast::Expr::BlockExpr(closure_body.clone()), &mut |e| {
166 ast::Expr::BreakExpr(e) => e.expr(),
167 ast::Expr::ReturnExpr(e) => e.expr(),
168 _ => Some(e.clone()),
170 if let Some(expr) = e {
172 expr.syntax().clone(),
173 make::expr_call(some_path.clone(), make::arg_list(Some(expr)))
179 replacements.into_iter().for_each(|(old, new)| ted::replace(old, new));
181 let cond = match &receiver {
182 ast::Expr::ParenExpr(expr) => expr.expr().unwrap_or(receiver),
185 let if_expr = make::expr_if(
186 make::condition(cond, None),
187 closure_body.reset_indent(),
188 Some(ast::ElseBranch::Block(make::block_expr(None, Some(none_path)))),
190 .indent(mcall.indent_level());
192 builder.replace(target, if_expr.to_string());
198 sema: &Semantics<RootDatabase>,
200 ) -> Option<(hir::Variant, hir::Variant)> {
201 let fam = FamousDefs(&sema, sema.scope(expr).krate());
202 let option_variants = fam.core_option_Option()?.variants(sema.db);
203 match &*option_variants {
204 &[variant0, variant1] => Some(if variant0.name(sema.db) == known::None {
213 /// Traverses the expression checking if it contains `return` or `?` expressions or if any tail is not a `Some(expr)` expression.
214 /// If any of these conditions are met it is impossible to rewrite this as a `bool::then` call.
216 sema: &Semantics<RootDatabase>,
217 some_variant: hir::Variant,
220 let mut invalid = false;
221 expr.preorder(&mut |e| {
223 matches!(e, syntax::WalkEvent::Enter(ast::Expr::TryExpr(_) | ast::Expr::ReturnExpr(_)));
227 for_each_tail_expr(&expr, &mut |e| {
232 ast::Expr::BreakExpr(e) => e.expr(),
233 e @ ast::Expr::CallExpr(_) => Some(e.clone()),
236 if let Some(ast::Expr::CallExpr(call)) = e {
237 if let Some(ast::Expr::PathExpr(p)) = call.expr() {
238 let res = p.path().and_then(|p| sema.resolve_path(&p));
239 if let Some(hir::PathResolution::Def(hir::ModuleDef::Variant(v))) = res {
240 return invalid |= v != some_variant;
250 fn block_is_none_variant(
251 sema: &Semantics<RootDatabase>,
252 block: &ast::BlockExpr,
253 none_variant: hir::Variant,
255 block.as_lone_tail().and_then(|e| match e {
256 ast::Expr::PathExpr(pat) => match sema.resolve_path(&pat.path()?)? {
257 hir::PathResolution::Def(hir::ModuleDef::Variant(v)) => Some(v),
261 }) == Some(none_variant)
266 use crate::tests::{check_assist, check_assist_not_applicable};
271 fn convert_if_to_bool_then_simple() {
273 convert_if_to_bool_then,
293 fn convert_if_to_bool_then_invert() {
295 convert_if_to_bool_then,
315 fn convert_if_to_bool_then_none_none() {
316 check_assist_not_applicable(
317 convert_if_to_bool_then,
332 fn convert_if_to_bool_then_some_some() {
333 check_assist_not_applicable(
334 convert_if_to_bool_then,
349 fn convert_if_to_bool_then_mixed() {
350 check_assist_not_applicable(
351 convert_if_to_bool_then,
370 fn convert_if_to_bool_then_chain() {
371 cov_mark::check!(convert_if_to_bool_then_chain);
372 check_assist_not_applicable(
373 convert_if_to_bool_then,
390 fn convert_if_to_bool_then_pattern_cond() {
391 check_assist_not_applicable(
392 convert_if_to_bool_then,
396 if$0 let true = true {
407 fn convert_if_to_bool_then_pattern_invalid_body() {
408 cov_mark::check_count!(convert_if_to_bool_then_pattern_invalid_body, 2);
409 check_assist_not_applicable(
410 convert_if_to_bool_then,
413 fn make_me_an_option() -> Option<i32> { None }
427 check_assist_not_applicable(
428 convert_if_to_bool_then,
446 fn convert_bool_then_to_if_inapplicable() {
447 check_assist_not_applicable(
448 convert_bool_then_to_if,
450 //- minicore:bool_impl
456 check_assist_not_applicable(
457 convert_bool_then_to_if,
459 //- minicore:bool_impl
465 check_assist_not_applicable(
466 convert_bool_then_to_if,
468 //- minicore:bool_impl
470 true.t$0hen(|| 15, 15);
477 fn convert_bool_then_to_if_simple() {
479 convert_bool_then_to_if,
481 //- minicore:bool_impl
497 convert_bool_then_to_if,
499 //- minicore:bool_impl
519 fn convert_bool_then_to_if_tails() {
521 convert_bool_then_to_if,
523 //- minicore:bool_impl