1 use hir::{known, AsAssocItem, Semantics};
5 node_ext::{block_as_lone_tail, is_pattern_cond, preorder_expr},
10 use itertools::Itertools;
12 ast::{self, edit::AstNodeEdit, make, HasArgList},
13 ted, AstNode, SyntaxNode,
17 utils::{invert_boolean_expression, unwrap_trivial_block},
18 AssistContext, AssistId, AssistKind, Assists,
21 // Assist: convert_if_to_bool_then
23 // Converts an if expression into a corresponding `bool::then` call.
26 // # //- minicore: option
41 pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
42 // FIXME applies to match as well
43 let expr = ctx.find_node_at_offset::<ast::IfExpr>()?;
44 if !expr.if_token()?.text_range().contains_inclusive(ctx.offset()) {
48 let cond = expr.condition().filter(|cond| !is_pattern_cond(cond.clone()))?;
49 let then = expr.then_branch()?;
50 let else_ = match expr.else_branch()? {
51 ast::ElseBranch::Block(b) => b,
52 ast::ElseBranch::IfExpr(_) => {
53 cov_mark::hit!(convert_if_to_bool_then_chain);
58 let (none_variant, some_variant) = option_variants(&ctx.sema, expr.syntax())?;
60 let (invert_cond, closure_body) = match (
61 block_is_none_variant(&ctx.sema, &then, none_variant),
62 block_is_none_variant(&ctx.sema, &else_, none_variant),
64 (invert @ true, false) => (invert, ast::Expr::BlockExpr(else_)),
65 (invert @ false, true) => (invert, ast::Expr::BlockExpr(then)),
69 if is_invalid_body(&ctx.sema, some_variant, &closure_body) {
70 cov_mark::hit!(convert_if_to_bool_then_pattern_invalid_body);
74 let target = expr.syntax().text_range();
76 AssistId("convert_if_to_bool_then", AssistKind::RefactorRewrite),
77 "Convert `if` expression to `bool::then` call",
80 let closure_body = closure_body.clone_for_update();
81 // Rewrite all `Some(e)` in tail position to `e`
82 let mut replacements = Vec::new();
83 for_each_tail_expr(&closure_body, &mut |e| {
85 ast::Expr::BreakExpr(e) => e.expr(),
86 e @ ast::Expr::CallExpr(_) => Some(e.clone()),
89 if let Some(ast::Expr::CallExpr(call)) = e {
90 if let Some(arg_list) = call.arg_list() {
91 if let Some(arg) = arg_list.args().next() {
92 replacements.push((call.syntax().clone(), arg.syntax().clone()));
97 replacements.into_iter().for_each(|(old, new)| ted::replace(old, new));
98 let closure_body = match closure_body {
99 ast::Expr::BlockExpr(block) => unwrap_trivial_block(block),
103 let parenthesize = matches!(
105 ast::Expr::BinExpr(_)
106 | ast::Expr::BlockExpr(_)
107 | ast::Expr::BoxExpr(_)
108 | ast::Expr::BreakExpr(_)
109 | ast::Expr::CastExpr(_)
110 | ast::Expr::ClosureExpr(_)
111 | ast::Expr::ContinueExpr(_)
112 | ast::Expr::ForExpr(_)
113 | ast::Expr::IfExpr(_)
114 | ast::Expr::LoopExpr(_)
115 | ast::Expr::MacroCall(_)
116 | ast::Expr::MatchExpr(_)
117 | ast::Expr::PrefixExpr(_)
118 | ast::Expr::RangeExpr(_)
119 | ast::Expr::RefExpr(_)
120 | ast::Expr::ReturnExpr(_)
121 | ast::Expr::WhileExpr(_)
122 | ast::Expr::YieldExpr(_)
124 let cond = if invert_cond { invert_boolean_expression(cond) } else { cond };
125 let cond = if parenthesize { make::expr_paren(cond) } else { cond };
126 let arg_list = make::arg_list(Some(make::expr_closure(None, closure_body)));
127 let mcall = make::expr_method_call(cond, make::name_ref("then"), arg_list);
128 builder.replace(target, mcall.to_string());
133 // Assist: convert_bool_then_to_if
135 // Converts a `bool::then` method call to an equivalent if expression.
138 // # //- minicore: bool_impl
140 // (0 == 0).then$0(|| val)
153 pub(crate) fn convert_bool_then_to_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
154 let name_ref = ctx.find_node_at_offset::<ast::NameRef>()?;
155 let mcall = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
156 let receiver = mcall.receiver()?;
157 let closure_body = mcall.arg_list()?.args().exactly_one().ok()?;
158 let closure_body = match closure_body {
159 ast::Expr::ClosureExpr(expr) => expr.body()?,
162 // Verify this is `bool::then` that is being called.
163 let func = ctx.sema.resolve_method_call(&mcall)?;
164 if func.name(ctx.sema.db).to_string() != "then" {
167 let assoc = func.as_assoc_item(ctx.sema.db)?;
168 match assoc.container(ctx.sema.db) {
169 hir::AssocItemContainer::Impl(impl_) if impl_.self_ty(ctx.sema.db).is_bool() => {}
173 let target = mcall.syntax().text_range();
175 AssistId("convert_bool_then_to_if", AssistKind::RefactorRewrite),
176 "Convert `bool::then` call to `if`",
179 let closure_body = match closure_body {
180 ast::Expr::BlockExpr(block) => block,
181 e => make::block_expr(None, Some(e)),
184 let closure_body = closure_body.clone_for_update();
185 // Wrap all tails in `Some(...)`
186 let none_path = make::expr_path(make::ext::ident_path("None"));
187 let some_path = make::expr_path(make::ext::ident_path("Some"));
188 let mut replacements = Vec::new();
189 for_each_tail_expr(&ast::Expr::BlockExpr(closure_body.clone()), &mut |e| {
191 ast::Expr::BreakExpr(e) => e.expr(),
192 ast::Expr::ReturnExpr(e) => e.expr(),
193 _ => Some(e.clone()),
195 if let Some(expr) = e {
197 expr.syntax().clone(),
198 make::expr_call(some_path.clone(), make::arg_list(Some(expr)))
204 replacements.into_iter().for_each(|(old, new)| ted::replace(old, new));
206 let cond = match &receiver {
207 ast::Expr::ParenExpr(expr) => expr.expr().unwrap_or(receiver),
210 let if_expr = make::expr_if(
212 closure_body.reset_indent(),
213 Some(ast::ElseBranch::Block(make::block_expr(None, Some(none_path)))),
215 .indent(mcall.indent_level());
217 builder.replace(target, if_expr.to_string());
223 sema: &Semantics<RootDatabase>,
225 ) -> Option<(hir::Variant, hir::Variant)> {
226 let fam = FamousDefs(sema, sema.scope(expr).krate());
227 let option_variants = fam.core_option_Option()?.variants(sema.db);
228 match &*option_variants {
229 &[variant0, variant1] => Some(if variant0.name(sema.db) == known::None {
238 /// Traverses the expression checking if it contains `return` or `?` expressions or if any tail is not a `Some(expr)` expression.
239 /// If any of these conditions are met it is impossible to rewrite this as a `bool::then` call.
241 sema: &Semantics<RootDatabase>,
242 some_variant: hir::Variant,
245 let mut invalid = false;
246 preorder_expr(expr, &mut |e| {
248 matches!(e, syntax::WalkEvent::Enter(ast::Expr::TryExpr(_) | ast::Expr::ReturnExpr(_)));
252 for_each_tail_expr(expr, &mut |e| {
257 ast::Expr::BreakExpr(e) => e.expr(),
258 e @ ast::Expr::CallExpr(_) => Some(e.clone()),
261 if let Some(ast::Expr::CallExpr(call)) = e {
262 if let Some(ast::Expr::PathExpr(p)) = call.expr() {
263 let res = p.path().and_then(|p| sema.resolve_path(&p));
264 if let Some(hir::PathResolution::Def(hir::ModuleDef::Variant(v))) = res {
265 return invalid |= v != some_variant;
275 fn block_is_none_variant(
276 sema: &Semantics<RootDatabase>,
277 block: &ast::BlockExpr,
278 none_variant: hir::Variant,
280 block_as_lone_tail(block).and_then(|e| match e {
281 ast::Expr::PathExpr(pat) => match sema.resolve_path(&pat.path()?)? {
282 hir::PathResolution::Def(hir::ModuleDef::Variant(v)) => Some(v),
286 }) == Some(none_variant)
291 use crate::tests::{check_assist, check_assist_not_applicable};
296 fn convert_if_to_bool_then_simple() {
298 convert_if_to_bool_then,
318 fn convert_if_to_bool_then_invert() {
320 convert_if_to_bool_then,
340 fn convert_if_to_bool_then_none_none() {
341 check_assist_not_applicable(
342 convert_if_to_bool_then,
357 fn convert_if_to_bool_then_some_some() {
358 check_assist_not_applicable(
359 convert_if_to_bool_then,
374 fn convert_if_to_bool_then_mixed() {
375 check_assist_not_applicable(
376 convert_if_to_bool_then,
395 fn convert_if_to_bool_then_chain() {
396 cov_mark::check!(convert_if_to_bool_then_chain);
397 check_assist_not_applicable(
398 convert_if_to_bool_then,
415 fn convert_if_to_bool_then_pattern_cond() {
416 check_assist_not_applicable(
417 convert_if_to_bool_then,
421 if$0 let true = true {
432 fn convert_if_to_bool_then_pattern_invalid_body() {
433 cov_mark::check_count!(convert_if_to_bool_then_pattern_invalid_body, 2);
434 check_assist_not_applicable(
435 convert_if_to_bool_then,
438 fn make_me_an_option() -> Option<i32> { None }
452 check_assist_not_applicable(
453 convert_if_to_bool_then,
471 fn convert_bool_then_to_if_inapplicable() {
472 check_assist_not_applicable(
473 convert_bool_then_to_if,
475 //- minicore:bool_impl
481 check_assist_not_applicable(
482 convert_bool_then_to_if,
484 //- minicore:bool_impl
490 check_assist_not_applicable(
491 convert_bool_then_to_if,
493 //- minicore:bool_impl
495 true.t$0hen(|| 15, 15);
502 fn convert_bool_then_to_if_simple() {
504 convert_bool_then_to_if,
506 //- minicore:bool_impl
522 convert_bool_then_to_if,
524 //- minicore:bool_impl
544 fn convert_bool_then_to_if_tails() {
546 convert_bool_then_to_if,
548 //- minicore:bool_impl