1 use hir::{known, AsAssocItem, Semantics};
3 famous_defs::FamousDefs,
4 syntax_helpers::node_ext::{
5 block_as_lone_tail, for_each_tail_expr, is_pattern_cond, preorder_expr,
9 use itertools::Itertools;
11 ast::{self, edit::AstNodeEdit, make, HasArgList},
12 ted, AstNode, SyntaxNode,
16 utils::{invert_boolean_expression, unwrap_trivial_block},
17 AssistContext, AssistId, AssistKind, Assists,
20 // Assist: convert_if_to_bool_then
22 // Converts an if expression into a corresponding `bool::then` call.
25 // # //- minicore: option
40 pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
41 // FIXME applies to match as well
42 let expr = ctx.find_node_at_offset::<ast::IfExpr>()?;
43 if !expr.if_token()?.text_range().contains_inclusive(ctx.offset()) {
47 let cond = expr.condition().filter(|cond| !is_pattern_cond(cond.clone()))?;
48 let then = expr.then_branch()?;
49 let else_ = match expr.else_branch()? {
50 ast::ElseBranch::Block(b) => b,
51 ast::ElseBranch::IfExpr(_) => {
52 cov_mark::hit!(convert_if_to_bool_then_chain);
57 let (none_variant, some_variant) = option_variants(&ctx.sema, expr.syntax())?;
59 let (invert_cond, closure_body) = match (
60 block_is_none_variant(&ctx.sema, &then, none_variant),
61 block_is_none_variant(&ctx.sema, &else_, none_variant),
63 (invert @ true, false) => (invert, ast::Expr::BlockExpr(else_)),
64 (invert @ false, true) => (invert, ast::Expr::BlockExpr(then)),
68 if is_invalid_body(&ctx.sema, some_variant, &closure_body) {
69 cov_mark::hit!(convert_if_to_bool_then_pattern_invalid_body);
73 let target = expr.syntax().text_range();
75 AssistId("convert_if_to_bool_then", AssistKind::RefactorRewrite),
76 "Convert `if` expression to `bool::then` call",
79 let closure_body = closure_body.clone_for_update();
80 // Rewrite all `Some(e)` in tail position to `e`
81 let mut replacements = Vec::new();
82 for_each_tail_expr(&closure_body, &mut |e| {
84 ast::Expr::BreakExpr(e) => e.expr(),
85 e @ ast::Expr::CallExpr(_) => Some(e.clone()),
88 if let Some(ast::Expr::CallExpr(call)) = e {
89 if let Some(arg_list) = call.arg_list() {
90 if let Some(arg) = arg_list.args().next() {
91 replacements.push((call.syntax().clone(), arg.syntax().clone()));
96 replacements.into_iter().for_each(|(old, new)| ted::replace(old, new));
97 let closure_body = match closure_body {
98 ast::Expr::BlockExpr(block) => unwrap_trivial_block(block),
102 let parenthesize = matches!(
104 ast::Expr::BinExpr(_)
105 | ast::Expr::BlockExpr(_)
106 | ast::Expr::BoxExpr(_)
107 | ast::Expr::BreakExpr(_)
108 | ast::Expr::CastExpr(_)
109 | ast::Expr::ClosureExpr(_)
110 | ast::Expr::ContinueExpr(_)
111 | ast::Expr::ForExpr(_)
112 | ast::Expr::IfExpr(_)
113 | ast::Expr::LoopExpr(_)
114 | ast::Expr::MacroExpr(_)
115 | ast::Expr::MatchExpr(_)
116 | ast::Expr::PrefixExpr(_)
117 | ast::Expr::RangeExpr(_)
118 | ast::Expr::RefExpr(_)
119 | ast::Expr::ReturnExpr(_)
120 | ast::Expr::WhileExpr(_)
121 | ast::Expr::YieldExpr(_)
123 let cond = if invert_cond { invert_boolean_expression(cond) } else { cond };
124 let cond = if parenthesize { make::expr_paren(cond) } else { cond };
125 let arg_list = make::arg_list(Some(make::expr_closure(None, closure_body)));
126 let mcall = make::expr_method_call(cond, make::name_ref("then"), arg_list);
127 builder.replace(target, mcall.to_string());
132 // Assist: convert_bool_then_to_if
134 // Converts a `bool::then` method call to an equivalent if expression.
137 // # //- minicore: bool_impl
139 // (0 == 0).then$0(|| val)
152 pub(crate) fn convert_bool_then_to_if(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
153 let name_ref = ctx.find_node_at_offset::<ast::NameRef>()?;
154 let mcall = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
155 let receiver = mcall.receiver()?;
156 let closure_body = mcall.arg_list()?.args().exactly_one().ok()?;
157 let closure_body = match closure_body {
158 ast::Expr::ClosureExpr(expr) => expr.body()?,
161 // Verify this is `bool::then` that is being called.
162 let func = ctx.sema.resolve_method_call(&mcall)?;
163 if func.name(ctx.sema.db).to_string() != "then" {
166 let assoc = func.as_assoc_item(ctx.sema.db)?;
167 match assoc.container(ctx.sema.db) {
168 hir::AssocItemContainer::Impl(impl_) if impl_.self_ty(ctx.sema.db).is_bool() => {}
172 let target = mcall.syntax().text_range();
174 AssistId("convert_bool_then_to_if", AssistKind::RefactorRewrite),
175 "Convert `bool::then` call to `if`",
178 let closure_body = match closure_body {
179 ast::Expr::BlockExpr(block) => block,
180 e => make::block_expr(None, Some(e)),
183 let closure_body = closure_body.clone_for_update();
184 // Wrap all tails in `Some(...)`
185 let none_path = make::expr_path(make::ext::ident_path("None"));
186 let some_path = make::expr_path(make::ext::ident_path("Some"));
187 let mut replacements = Vec::new();
188 for_each_tail_expr(&ast::Expr::BlockExpr(closure_body.clone()), &mut |e| {
190 ast::Expr::BreakExpr(e) => e.expr(),
191 ast::Expr::ReturnExpr(e) => e.expr(),
192 _ => Some(e.clone()),
194 if let Some(expr) = e {
196 expr.syntax().clone(),
197 make::expr_call(some_path.clone(), make::arg_list(Some(expr)))
203 replacements.into_iter().for_each(|(old, new)| ted::replace(old, new));
205 let cond = match &receiver {
206 ast::Expr::ParenExpr(expr) => expr.expr().unwrap_or(receiver),
209 let if_expr = make::expr_if(
211 closure_body.reset_indent(),
212 Some(ast::ElseBranch::Block(make::block_expr(None, Some(none_path)))),
214 .indent(mcall.indent_level());
216 builder.replace(target, if_expr.to_string());
222 sema: &Semantics<'_, RootDatabase>,
224 ) -> Option<(hir::Variant, hir::Variant)> {
225 let fam = FamousDefs(sema, sema.scope(expr)?.krate());
226 let option_variants = fam.core_option_Option()?.variants(sema.db);
227 match &*option_variants {
228 &[variant0, variant1] => Some(if variant0.name(sema.db) == known::None {
237 /// Traverses the expression checking if it contains `return` or `?` expressions or if any tail is not a `Some(expr)` expression.
238 /// If any of these conditions are met it is impossible to rewrite this as a `bool::then` call.
240 sema: &Semantics<'_, RootDatabase>,
241 some_variant: hir::Variant,
244 let mut invalid = false;
245 preorder_expr(expr, &mut |e| {
247 matches!(e, syntax::WalkEvent::Enter(ast::Expr::TryExpr(_) | ast::Expr::ReturnExpr(_)));
251 for_each_tail_expr(expr, &mut |e| {
256 ast::Expr::BreakExpr(e) => e.expr(),
257 e @ ast::Expr::CallExpr(_) => Some(e.clone()),
260 if let Some(ast::Expr::CallExpr(call)) = e {
261 if let Some(ast::Expr::PathExpr(p)) = call.expr() {
262 let res = p.path().and_then(|p| sema.resolve_path(&p));
263 if let Some(hir::PathResolution::Def(hir::ModuleDef::Variant(v))) = res {
264 return invalid |= v != some_variant;
274 fn block_is_none_variant(
275 sema: &Semantics<'_, RootDatabase>,
276 block: &ast::BlockExpr,
277 none_variant: hir::Variant,
279 block_as_lone_tail(block).and_then(|e| match e {
280 ast::Expr::PathExpr(pat) => match sema.resolve_path(&pat.path()?)? {
281 hir::PathResolution::Def(hir::ModuleDef::Variant(v)) => Some(v),
285 }) == Some(none_variant)
290 use crate::tests::{check_assist, check_assist_not_applicable};
295 fn convert_if_to_bool_then_simple() {
297 convert_if_to_bool_then,
317 fn convert_if_to_bool_then_invert() {
319 convert_if_to_bool_then,
339 fn convert_if_to_bool_then_none_none() {
340 check_assist_not_applicable(
341 convert_if_to_bool_then,
356 fn convert_if_to_bool_then_some_some() {
357 check_assist_not_applicable(
358 convert_if_to_bool_then,
373 fn convert_if_to_bool_then_mixed() {
374 check_assist_not_applicable(
375 convert_if_to_bool_then,
394 fn convert_if_to_bool_then_chain() {
395 cov_mark::check!(convert_if_to_bool_then_chain);
396 check_assist_not_applicable(
397 convert_if_to_bool_then,
414 fn convert_if_to_bool_then_pattern_cond() {
415 check_assist_not_applicable(
416 convert_if_to_bool_then,
420 if$0 let true = true {
431 fn convert_if_to_bool_then_pattern_invalid_body() {
432 cov_mark::check_count!(convert_if_to_bool_then_pattern_invalid_body, 2);
433 check_assist_not_applicable(
434 convert_if_to_bool_then,
437 fn make_me_an_option() -> Option<i32> { None }
451 check_assist_not_applicable(
452 convert_if_to_bool_then,
470 fn convert_bool_then_to_if_inapplicable() {
471 check_assist_not_applicable(
472 convert_bool_then_to_if,
474 //- minicore:bool_impl
480 check_assist_not_applicable(
481 convert_bool_then_to_if,
483 //- minicore:bool_impl
489 check_assist_not_applicable(
490 convert_bool_then_to_if,
492 //- minicore:bool_impl
494 true.t$0hen(|| 15, 15);
501 fn convert_bool_then_to_if_simple() {
503 convert_bool_then_to_if,
505 //- minicore:bool_impl
521 convert_bool_then_to_if,
523 //- minicore:bool_impl
543 fn convert_bool_then_to_if_tails() {
545 convert_bool_then_to_if,
547 //- minicore:bool_impl