Unnamed repository; edit this file 'description' to name the repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
use hir::{AsAssocItem, HirDisplay};
use ide_db::{
    assists::{AssistId, AssistKind},
    famous_defs::FamousDefs,
};
use syntax::{ast, AstNode};

use crate::assist_context::{AssistContext, Assists};

// Assist: into_to_qualified_from
//
// Convert an `into` method call to a fully qualified `from` call.
//
// ```
// //- minicore: from
// struct B;
// impl From<i32> for B {
//     fn from(a: i32) -> Self {
//        B
//     }
// }
//
// fn main() -> () {
//     let a = 3;
//     let b: B = a.in$0to();
// }
// ```
// ->
// ```
// struct B;
// impl From<i32> for B {
//     fn from(a: i32) -> Self {
//        B
//     }
// }
//
// fn main() -> () {
//     let a = 3;
//     let b: B = B::from(a);
// }
// ```
pub(crate) fn into_to_qualified_from(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
    let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
    let nameref = method_call.name_ref()?;
    let receiver = method_call.receiver()?;
    let db = ctx.db();
    let sema = &ctx.sema;
    let fnc = sema.resolve_method_call(&method_call)?;
    let scope = sema.scope(method_call.syntax())?;
    // Check if the method call refers to Into trait.
    if fnc.as_assoc_item(db)?.implemented_trait(db)?
        == FamousDefs(sema, scope.krate()).core_convert_Into()?
    {
        let type_call = sema.type_of_expr(&method_call.clone().into())?;
        let adjusted_tc = type_call.adjusted();

        if adjusted_tc.contains_unknown() {
            return None;
        }

        let sc = adjusted_tc.display_source_code(db, scope.module().into(), true).ok()?;
        acc.add(
            AssistId("into_to_qualified_from", AssistKind::Generate),
            "Convert `into` to fully qualified `from`",
            nameref.syntax().text_range(),
            |edit| {
                edit.replace(
                    method_call.syntax().text_range(),
                    if sc.chars().all(|c| c.is_alphanumeric() || c == ':') {
                        format!("{sc}::from({receiver})")
                    } else {
                        format!("<{sc}>::from({receiver})")
                    },
                );
            },
        );
    }

    Some(())
}

#[cfg(test)]
mod tests {
    use crate::tests::check_assist;

    use super::into_to_qualified_from;

    #[test]
    fn two_types_in_same_mod() {
        check_assist(
            into_to_qualified_from,
            r#"
//- minicore: from
struct A;
struct B;
impl From<A> for B {
    fn from(a: A) -> Self {
        B
    }
}

fn main() -> () {
    let a: A = A;
    let b: B = a.in$0to();
}"#,
            r#"
struct A;
struct B;
impl From<A> for B {
    fn from(a: A) -> Self {
        B
    }
}

fn main() -> () {
    let a: A = A;
    let b: B = B::from(a);
}"#,
        )
    }

    #[test]
    fn from_in_child_mod_imported() {
        check_assist(
            into_to_qualified_from,
            r#"
//- minicore: from
use C::B;

struct A;

mod C {
    use crate::A;

    pub(super) struct B;
    impl From<A> for B {
        fn from(a: A) -> Self {
            B
        }
    }
}

fn main() -> () {
    let a: A = A;
    let b: B = a.in$0to();
}"#,
            r#"
use C::B;

struct A;

mod C {
    use crate::A;

    pub(super) struct B;
    impl From<A> for B {
        fn from(a: A) -> Self {
            B
        }
    }
}

fn main() -> () {
    let a: A = A;
    let b: B = B::from(a);
}"#,
        )
    }

    #[test]
    fn from_in_child_mod_not_imported() {
        check_assist(
            into_to_qualified_from,
            r#"
//- minicore: from
struct A;

mod C {
    use crate::A;

    pub(super) struct B;
    impl From<A> for B {
        fn from(a: A) -> Self {
            B
        }
    }
}

fn main() -> () {
    let a: A = A;
    let b: C::B = a.in$0to();
}"#,
            r#"
struct A;

mod C {
    use crate::A;

    pub(super) struct B;
    impl From<A> for B {
        fn from(a: A) -> Self {
            B
        }
    }
}

fn main() -> () {
    let a: A = A;
    let b: C::B = C::B::from(a);
}"#,
        )
    }

    #[test]
    fn preceding_type_qualifier() {
        check_assist(
            into_to_qualified_from,
            r#"
//- minicore: from
impl From<(i32,i32)> for [i32;2] {
    fn from(value: (i32,i32)) -> Self {
        [value.0, value.1]
    }
}

fn tuple_to_array() -> [i32; 2] {
    (0,1).in$0to()
}"#,
            r#"
impl From<(i32,i32)> for [i32;2] {
    fn from(value: (i32,i32)) -> Self {
        [value.0, value.1]
    }
}

fn tuple_to_array() -> [i32; 2] {
    <[i32; 2]>::from((0,1))
}"#,
        )
    }

    #[test]
    fn type_with_gens() {
        check_assist(
            into_to_qualified_from,
            r#"
//- minicore: from
struct StructA<Gen>(Gen);

impl From<i32> for StructA<i32> {
    fn from(value: i32) -> Self {
        StructA(value + 1)
    }
}

fn main() -> () {
    let a: StructA<i32> = 3.in$0to();
}"#,
            r#"
struct StructA<Gen>(Gen);

impl From<i32> for StructA<i32> {
    fn from(value: i32) -> Self {
        StructA(value + 1)
    }
}

fn main() -> () {
    let a: StructA<i32> = <StructA<i32>>::from(3);
}"#,
        )
    }
}
id='n476' href='#n476'>476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
use hir::{known, AsAssocItem, Semantics};
use ide_db::{
    famous_defs::FamousDefs,
    syntax_helpers::node_ext::{
        block_as_lone_tail, for_each_tail_expr, is_pattern_cond, preorder_expr,
    },
    RootDatabase,
};
use itertools::Itertools;
use syntax::{
    ast::{self, edit::AstNodeEdit, make, HasArgList},
    ted, AstNode, SyntaxNode,
};

use crate::{
    utils::{invert_boolean_expression, unwrap_trivial_block},
    AssistContext, AssistId, AssistKind, Assists,
};

// Assist: convert_if_to_bool_then
//
// Converts an if expression into a corresponding `bool::then` call.
//
// ```
// # //- minicore: option
// fn main() {
//     if$0 cond {
//         Some(val)
//     } else {
//         None
//     }
// }
// ```
// ->
// ```
// fn main() {
//     cond.then(|| val)
// }
// ```
pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
    // FIXME applies to match as well
    let expr = ctx.find_node_at_offset::<ast::IfExpr>()?;
    if !expr.if_token()?.text_range().contains_inclusive(ctx.offset()) {
        return None;
    }

    let cond = expr.condition().filter(|cond| !is_pattern_cond(cond.clone()))?;
    let then = expr.then_branch()?;
    let else_ = match expr.else_branch()? {
        ast::ElseBranch::Block(b) => b,
        ast::ElseBranch::IfExpr(_) => {
            cov_mark::hit!(convert_if_to_bool_then_chain);
            return None;
        }
    };

    let (none_variant, some_variant) = option_variants(&ctx.sema, expr.syntax())?;

    let (invert_cond, closure_body) = match (
        block_is_none_variant(&ctx.sema, &then, none_variant),
        block_is_none_variant(&ctx.sema, &else_, none_variant),
    ) {
        (invert @ true, false) => (invert, ast::Expr::BlockExpr(else_)),
        (invert @ false, true) => (invert, ast::Expr::BlockExpr(then)),
        _ => return None,
    };

    if is_invalid_body(&ctx.sema, some_variant, &closure_body) {
        cov_mark::hit!(convert_if_to_bool_then_pattern_invalid_body);
        return None;
    }

    let target = expr.syntax().text_range();
    acc.add(
        AssistId("convert_if_to_bool_then", AssistKind::RefactorRewrite),
        "Convert `if` expression to `bool::then` call",
        target,
        |builder| {
            let closure_body = closure_body.clone_for_update();
            // Rewrite all `Some(e)` in tail position to `e`
            let mut replacements = Vec::new();
            for_each_tail_expr(&closure_body, &mut |e| {
                let e = match e {
                    ast::Expr::BreakExpr(e) => e.expr(),
                    e @ ast::Expr::CallExpr(_) => Some(e.clone()),
                    _ => None,
                };
                if let Some(ast::Expr::CallExpr(call)) = e {
                    if let Some(arg_list) = call.arg_list() {
                        if let Some(arg) = arg_list.args().next() {
                            replacements.push((call.syntax().clone(), arg.syntax().clone()));
                        }
                    }
                }
            });
            replacements.into_iter().for_each(|(old, new)| ted::replace(old, new));
            let closure_body = match closure_body {
                ast::Expr::BlockExpr(block) => unwrap_trivial_block(block),
                e => e,
            };

            let parenthesize = matches!(
                cond,
                ast::Expr::BinExpr(_)
                    | ast::Expr::BlockExpr(_)
                    | ast::Expr::BreakExpr(_)
                    | ast::Expr::CastExpr(_)
                    | ast::Expr::ClosureExpr(_)
                    | ast::Expr::ContinueExpr(_)
                    | ast::Expr::ForExpr(_)
                    | ast::Expr::IfExpr(_)
                    | ast::Expr::LoopExpr(_)
                    | ast::Expr::MacroExpr(_)
                    | ast::Expr::MatchExpr(_)
                    | ast::Expr::PrefixExpr(_)
                    | ast::Expr::RangeExpr(_)
                    | ast::Expr::RefExpr(_)
                    | ast::Expr::ReturnExpr(_)
                    | ast::Expr::WhileExpr(_)
                    | ast::Expr::YieldExpr(_)
            );
            let cond = if invert_cond { invert_boolean_expression(cond) } else { cond };
            let cond = if parenthesize { make::expr_paren(cond) } else { cond };
            let arg_list = make::arg_list(Some(make::expr_closure(None, closure_body)));
            let mcall = make::expr_method_call(cond, make::name_ref("then"), arg_list);
            builder.replace(target, mcall.to_string());
        },
    )
}

// Assist: convert_bool_then_to_if
//
// Converts a `bool::then` method call to an equivalent if expression.
//
// ```
// # //- minicore: bool_impl
// fn main() {
//     (0 == 0).then$0(|| val)
// }
// ```
// ->
// ```
// fn main() {
//     if 0 == 0 {
//         Some(val)
//     } else {
//         None
//     }
// }
// ```
pub(crate) fn convert_bool_then_to_if(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
    let name_ref = ctx.find_node_at_offset::<ast::NameRef>()?;
    let mcall = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
    let receiver = mcall.receiver()?;
    let closure_body = mcall.arg_list()?.args().exactly_one().ok()?;
    let closure_body = match closure_body {
        ast::Expr::ClosureExpr(expr) => expr.body()?,
        _ => return None,
    };
    // Verify this is `bool::then` that is being called.
    let func = ctx.sema.resolve_method_call(&mcall)?;
    if func.name(ctx.sema.db).display(ctx.db()).to_string() != "then" {
        return None;
    }
    let assoc = func.as_assoc_item(ctx.sema.db)?;
    match assoc.container(ctx.sema.db) {
        hir::AssocItemContainer::Impl(impl_) if impl_.self_ty(ctx.sema.db).is_bool() => {}
        _ => return None,
    }

    let target = mcall.syntax().text_range();
    acc.add(
        AssistId("convert_bool_then_to_if", AssistKind::RefactorRewrite),
        "Convert `bool::then` call to `if`",
        target,
        |builder| {
            let closure_body = match closure_body {
                ast::Expr::BlockExpr(block) => block,
                e => make::block_expr(None, Some(e)),
            };

            let closure_body = closure_body.clone_for_update();
            // Wrap all tails in `Some(...)`
            let none_path = make::expr_path(make::ext::ident_path("None"));
            let some_path = make::expr_path(make::ext::ident_path("Some"));
            let mut replacements = Vec::new();
            for_each_tail_expr(&ast::Expr::BlockExpr(closure_body.clone()), &mut |e| {
                let e = match e {
                    ast::Expr::BreakExpr(e) => e.expr(),
                    ast::Expr::ReturnExpr(e) => e.expr(),
                    _ => Some(e.clone()),
                };
                if let Some(expr) = e {
                    replacements.push((
                        expr.syntax().clone(),
                        make::expr_call(some_path.clone(), make::arg_list(Some(expr)))
                            .syntax()
                            .clone_for_update(),
                    ));
                }
            });
            replacements.into_iter().for_each(|(old, new)| ted::replace(old, new));

            let cond = match &receiver {
                ast::Expr::ParenExpr(expr) => expr.expr().unwrap_or(receiver),
                _ => receiver,
            };
            let if_expr = make::expr_if(
                cond,
                closure_body.reset_indent(),
                Some(ast::ElseBranch::Block(make::block_expr(None, Some(none_path)))),
            )
            .indent(mcall.indent_level());

            builder.replace(target, if_expr.to_string());
        },
    )
}

fn option_variants(
    sema: &Semantics<'_, RootDatabase>,
    expr: &SyntaxNode,
) -> Option<(hir::Variant, hir::Variant)> {
    let fam = FamousDefs(sema, sema.scope(expr)?.krate());
    let option_variants = fam.core_option_Option()?.variants(sema.db);
    match &*option_variants {
        &[variant0, variant1] => Some(if variant0.name(sema.db) == known::None {
            (variant0, variant1)
        } else {
            (variant1, variant0)
        }),
        _ => None,
    }
}

/// Traverses the expression checking if it contains `return` or `?` expressions or if any tail is not a `Some(expr)` expression.
/// If any of these conditions are met it is impossible to rewrite this as a `bool::then` call.
fn is_invalid_body(
    sema: &Semantics<'_, RootDatabase>,
    some_variant: hir::Variant,
    expr: &ast::Expr,
) -> bool {
    let mut invalid = false;
    preorder_expr(expr, &mut |e| {
        invalid |=
            matches!(e, syntax::WalkEvent::Enter(ast::Expr::TryExpr(_) | ast::Expr::ReturnExpr(_)));
        invalid
    });
    if !invalid {
        for_each_tail_expr(expr, &mut |e| {
            if invalid {
                return;
            }
            let e = match e {
                ast::Expr::BreakExpr(e) => e.expr(),
                e @ ast::Expr::CallExpr(_) => Some(e.clone()),
                _ => None,
            };
            if let Some(ast::Expr::CallExpr(call)) = e {
                if let Some(ast::Expr::PathExpr(p)) = call.expr() {
                    let res = p.path().and_then(|p| sema.resolve_path(&p));
                    if let Some(hir::PathResolution::Def(hir::ModuleDef::Variant(v))) = res {
                        return invalid |= v != some_variant;
                    }
                }
            }
            invalid = true
        });
    }
    invalid
}

fn block_is_none_variant(
    sema: &Semantics<'_, RootDatabase>,
    block: &ast::BlockExpr,
    none_variant: hir::Variant,
) -> bool {
    block_as_lone_tail(block).and_then(|e| match e {
        ast::Expr::PathExpr(pat) => match sema.resolve_path(&pat.path()?)? {
            hir::PathResolution::Def(hir::ModuleDef::Variant(v)) => Some(v),
            _ => None,
        },
        _ => None,
    }) == Some(none_variant)
}

#[cfg(test)]
mod tests {
    use crate::tests::{check_assist, check_assist_not_applicable};

    use super::*;

    #[test]
    fn convert_if_to_bool_then_simple() {
        check_assist(
            convert_if_to_bool_then,
            r"
//- minicore:option
fn main() {
    if$0 true {
        Some(15)
    } else {
        None
    }
}
",
            r"
fn main() {
    true.then(|| 15)
}
",
        );
    }

    #[test]
    fn convert_if_to_bool_then_invert() {
        check_assist(
            convert_if_to_bool_then,
            r"
//- minicore:option
fn main() {
    if$0 true {
        None
    } else {
        Some(15)
    }
}
",
            r"
fn main() {
    false.then(|| 15)
}
",
        );
    }

    #[test]
    fn convert_if_to_bool_then_none_none() {
        check_assist_not_applicable(
            convert_if_to_bool_then,
            r"
//- minicore:option
fn main() {
    if$0 true {
        None
    } else {
        None
    }
}
",
        );
    }

    #[test]
    fn convert_if_to_bool_then_some_some() {
        check_assist_not_applicable(
            convert_if_to_bool_then,
            r"
//- minicore:option
fn main() {
    if$0 true {
        Some(15)
    } else {
        Some(15)
    }
}
",
        );
    }

    #[test]
    fn convert_if_to_bool_then_mixed() {
        check_assist_not_applicable(
            convert_if_to_bool_then,
            r"
//- minicore:option
fn main() {
    if$0 true {
        if true {
            Some(15)
        } else {
            None
        }
    } else {
        None
    }
}
",
        );
    }

    #[test]
    fn convert_if_to_bool_then_chain() {
        cov_mark::check!(convert_if_to_bool_then_chain);
        check_assist_not_applicable(
            convert_if_to_bool_then,
            r"
//- minicore:option
fn main() {
    if$0 true {
        Some(15)
    } else if true {
        None
    } else {
        None
    }
}
",
        );
    }

    #[test]
    fn convert_if_to_bool_then_pattern_cond() {
        check_assist_not_applicable(
            convert_if_to_bool_then,
            r"
//- minicore:option
fn main() {
    if$0 let true = true {
        Some(15)
    } else {
        None
    }
}
",
        );
    }

    #[test]
    fn convert_if_to_bool_then_pattern_invalid_body() {
        cov_mark::check_count!(convert_if_to_bool_then_pattern_invalid_body, 2);
        check_assist_not_applicable(
            convert_if_to_bool_then,
            r"
//- minicore:option
fn make_me_an_option() -> Option<i32> { None }
fn main() {
    if$0 true {
        if true {
            make_me_an_option()
        } else {
            Some(15)
        }
    } else {
        None
    }
}
",
        );
        check_assist_not_applicable(
            convert_if_to_bool_then,
            r"
//- minicore:option
fn main() {
    if$0 true {
        if true {
            return;
        }
        Some(15)
    } else {
        None
    }
}
",
        );
    }

    #[test]
    fn convert_bool_then_to_if_inapplicable() {
        check_assist_not_applicable(
            convert_bool_then_to_if,
            r"
//- minicore:bool_impl
fn main() {
    0.t$0hen(|| 15);
}
",
        );
        check_assist_not_applicable(
            convert_bool_then_to_if,
            r"
//- minicore:bool_impl
fn main() {
    true.t$0hen(15);
}
",
        );
        check_assist_not_applicable(
            convert_bool_then_to_if,
            r"
//- minicore:bool_impl
fn main() {
    true.t$0hen(|| 15, 15);
}
",
        );
    }

    #[test]
    fn convert_bool_then_to_if_simple() {
        check_assist(
            convert_bool_then_to_if,
            r"
//- minicore:bool_impl
fn main() {
    true.t$0hen(|| 15)
}
",
            r"
fn main() {
    if true {
        Some(15)
    } else {
        None
    }
}
",
        );
        check_assist(
            convert_bool_then_to_if,
            r"
//- minicore:bool_impl
fn main() {
    true.t$0hen(|| {
        15
    })
}
",
            r"
fn main() {
    if true {
        Some(15)
    } else {
        None
    }
}
",
        );
    }

    #[test]
    fn convert_bool_then_to_if_tails() {
        check_assist(
            convert_bool_then_to_if,
            r"
//- minicore:bool_impl
fn main() {
    true.t$0hen(|| {
        loop {
            if false {
                break 0;
            }
            break 15;
        }
    })
}
",
            r"
fn main() {
    if true {
        loop {
            if false {
                break Some(0);
            }
            break Some(15);
        }
    } else {
        None
    }
}
",
        );
    }
}