1 use hir::{known, Semantics};
3 helpers::{for_each_tail_expr, FamousDefs},
7 ast::{self, make, ArgListOwner},
8 ted, AstNode, SyntaxNode,
12 utils::{invert_boolean_expression, unwrap_trivial_block},
13 AssistContext, AssistId, AssistKind, Assists,
16 // Assist: convert_if_to_bool_then
18 // Converts an if expression into a corresponding `bool::then` call.
21 // # //- minicore: option
36 pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
37 // todo, applies to match as well
38 let expr = ctx.find_node_at_offset::<ast::IfExpr>()?;
39 if !expr.if_token()?.text_range().contains_inclusive(ctx.offset()) {
43 let cond = expr.condition().filter(|cond| !cond.is_pattern_cond())?;
44 let cond = cond.expr()?;
45 let then = expr.then_branch()?;
46 let else_ = match expr.else_branch()? {
47 ast::ElseBranch::Block(b) => b,
48 ast::ElseBranch::IfExpr(_) => {
49 cov_mark::hit!(convert_if_to_bool_then_chain);
54 let (none_variant, some_variant) = option_variants(&ctx.sema, expr.syntax())?;
56 let (invert_cond, closure_body) = match (
57 block_is_none_variant(&ctx.sema, &then, none_variant),
58 block_is_none_variant(&ctx.sema, &else_, none_variant),
60 (invert @ true, false) => (invert, ast::Expr::BlockExpr(else_)),
61 (invert @ false, true) => (invert, ast::Expr::BlockExpr(then)),
65 if is_invalid_body(&ctx.sema, some_variant, &closure_body) {
66 cov_mark::hit!(convert_if_to_bool_then_pattern_invalid_body);
70 let target = expr.syntax().text_range();
72 AssistId("convert_if_to_bool_then", AssistKind::RefactorRewrite),
73 "Convert `if` expression to `bool::then` call",
76 let closure_body = closure_body.clone_for_update();
77 // Rewrite all `Some(e)` in tail position to `e`
78 for_each_tail_expr(&closure_body, &mut |e| {
80 ast::Expr::BreakExpr(e) => e.expr(),
81 e @ ast::Expr::CallExpr(_) => Some(e.clone()),
84 if let Some(ast::Expr::CallExpr(call)) = e {
85 if let Some(arg_list) = call.arg_list() {
86 if let Some(arg) = arg_list.args().next() {
87 ted::replace(call.syntax(), arg.syntax());
92 let closure_body = match closure_body {
93 ast::Expr::BlockExpr(block) => unwrap_trivial_block(block),
97 let cond = if invert_cond { invert_boolean_expression(&ctx.sema, cond) } else { cond };
98 let arg_list = make::arg_list(Some(make::expr_closure(None, closure_body)));
99 let mcall = make::expr_method_call(cond, make::name_ref("then"), arg_list);
100 builder.replace(target, mcall.to_string());
106 sema: &Semantics<RootDatabase>,
108 ) -> Option<(hir::Variant, hir::Variant)> {
109 let fam = FamousDefs(&sema, sema.scope(expr).krate());
110 let option_variants = fam.core_option_Option()?.variants(sema.db);
111 match &*option_variants {
112 &[variant0, variant1] => Some(if variant0.name(sema.db) == known::None {
121 /// Traverses the expression checking if it contains `return` or `?` expressions or if any tail is not a `Some(expr)` expression.
122 /// If any of these conditions are met it is impossible to rewrite this as a `bool::then` call.
124 sema: &Semantics<RootDatabase>,
125 some_variant: hir::Variant,
128 let mut invalid = false;
129 expr.preorder(&mut |e| {
131 matches!(e, syntax::WalkEvent::Enter(ast::Expr::TryExpr(_) | ast::Expr::ReturnExpr(_)));
135 for_each_tail_expr(&expr, &mut |e| {
140 ast::Expr::BreakExpr(e) => e.expr(),
141 e @ ast::Expr::CallExpr(_) => Some(e.clone()),
144 if let Some(ast::Expr::CallExpr(call)) = e {
145 if let Some(ast::Expr::PathExpr(p)) = call.expr() {
146 let res = p.path().and_then(|p| sema.resolve_path(&p));
147 if let Some(hir::PathResolution::Def(hir::ModuleDef::Variant(v))) = res {
148 return invalid |= v != some_variant;
158 fn block_is_none_variant(
159 sema: &Semantics<RootDatabase>,
160 block: &ast::BlockExpr,
161 none_variant: hir::Variant,
163 block.as_lone_tail().and_then(|e| match e {
164 ast::Expr::PathExpr(pat) => match sema.resolve_path(&pat.path()?)? {
165 hir::PathResolution::Def(hir::ModuleDef::Variant(v)) => Some(v),
169 }) == Some(none_variant)
174 use crate::tests::{check_assist, check_assist_not_applicable};
179 fn convert_if_to_bool_then_simple() {
181 convert_if_to_bool_then,
201 fn convert_if_to_bool_then_invert() {
203 convert_if_to_bool_then,
223 fn convert_if_to_bool_then_none_none() {
224 check_assist_not_applicable(
225 convert_if_to_bool_then,
240 fn convert_if_to_bool_then_some_some() {
241 check_assist_not_applicable(
242 convert_if_to_bool_then,
257 fn convert_if_to_bool_then_mixed() {
258 check_assist_not_applicable(
259 convert_if_to_bool_then,
278 fn convert_if_to_bool_then_chain() {
279 cov_mark::check!(convert_if_to_bool_then_chain);
280 check_assist_not_applicable(
281 convert_if_to_bool_then,
298 fn convert_if_to_bool_then_pattern_cond() {
299 check_assist_not_applicable(
300 convert_if_to_bool_then,
304 if$0 let true = true {
315 fn convert_if_to_bool_then_pattern_invalid_body() {
316 cov_mark::check_count!(convert_if_to_bool_then_pattern_invalid_body, 2);
317 check_assist_not_applicable(
318 convert_if_to_bool_then,
321 fn make_me_an_option() -> Option<i32> { None }
335 check_assist_not_applicable(
336 convert_if_to_bool_then,