//! Completion tests for expressions.
use expect_test::{expect, Expect};

use crate::tests::{check_edit, check_empty, completion_list, BASE_ITEMS_FIXTURE};

fn check(ra_fixture: &str, expect: Expect) {
    let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}{ra_fixture}"));
    expect.assert_eq(&actual)
}

#[test]
fn complete_literal_struct_with_a_private_field() {
    // `FooDesc.bar` is private, the completion should not be triggered.
    check(
        r#"
mod _69latrick {
    pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, bar: bool }
    pub fn create_foo(foo_desc: &FooDesc) -> () { () }
}

fn baz() {
    use _69latrick::*;

    let foo = create_foo(&$0);
}
            "#,
        // This should not contain `FooDesc {…}`.
        expect![[r#"
            ct CONST         Unit
            en Enum          Enum
            fn baz()         fn()
            fn create_foo(…) fn(&FooDesc)
            fn function()    fn()
            ma makro!(…)     macro_rules! makro
            md _69latrick
            md module
            sc STATIC        Unit
            st FooDesc       FooDesc
            st Record        Record
            st Tuple         Tuple
            st Unit          Unit
            un Union         Union
            ev TupleV(…)     TupleV(u32)
            bt u32           u32
            kw crate::
            kw false
            kw for
            kw if
            kw if let
            kw loop
            kw match
            kw mut
            kw return
            kw self::
            kw true
            kw unsafe
            kw while
            kw while let
        "#]],
    )
}

#[test]
fn completes_various_bindings() {
    check_empty(
        r#"
fn func(param0 @ (param1, param2): (i32, i32)) {
    let letlocal = 92;
    if let ifletlocal = 100 {
        match 0 {
            matcharm => 1 + $0,
            otherwise => (),
        }
    }
    let letlocal2 = 44;
}
"#,
        expect![[r#"
            fn func(…)    fn((i32, i32))
            lc ifletlocal i32
            lc letlocal   i32
            lc matcharm   i32
            lc param0     (i32, i32)
            lc param1     i32
            lc param2     i32
            bt u32        u32
            kw crate::
            kw false
            kw for
            kw if
            kw if let
            kw loop
            kw match
            kw return
            kw self::
            kw true
            kw unsafe
            kw while
            kw while let
            ex ifletlocal
            ex letlocal
            ex matcharm
            ex param1
            ex param2
        "#]],
    );
}

#[test]
fn completes_all_the_things_in_fn_body() {
    check(
        r#"
use non_existent::Unresolved;
mod qualified { pub enum Enum { Variant } }

impl Unit {
    fn foo<'lifetime, TypeParam, const CONST_PARAM: usize>(self) {
        fn local_func() {}
        $0
    }
}
"#,
        // `self` is in here twice, once as the module, once as the local
        expect![[r#"
            ct CONST        Unit
            cp CONST_PARAM
            en Enum         Enum
            fn function()   fn()
            fn local_func() fn()
            me self.foo()   fn(self)
            lc self         Unit
            ma makro!(…)    macro_rules! makro
            md module
            md qualified
            sp Self         Unit
            sc STATIC       Unit
            st Record       Record
            st Tuple        Tuple
            st Unit         Unit
            tp TypeParam
            un Union        Union
            ev TupleV(…)    TupleV(u32)
            bt u32          u32
            kw async
            kw const
            kw crate::
            kw enum
            kw extern
            kw false
            kw fn
            kw for
            kw if
            kw if let
            kw impl
            kw let
            kw loop
            kw match
            kw mod
            kw return
            kw self::
            kw static
            kw struct
            kw trait
            kw true
            kw type
            kw union
            kw unsafe
            kw use
            kw while
            kw while let
            sn macro_rules
            sn pd
            sn ppd
            ?? Unresolved
        "#]],
    );
    check(
        r#"
use non_existent::Unresolved;
mod qualified { pub enum Enum { Variant } }

impl Unit {
    fn foo<'lifetime, TypeParam, const CONST_PARAM: usize>(self) {
        fn local_func() {}
        self::$0
    }
}
"#,
        expect![[r#"
            ct CONST      Unit
            en Enum       Enum
            fn function() fn()
            ma makro!(…)  macro_rules! makro
            md module
            md qualified
            sc STATIC     Unit
            st Record     Record
            st Tuple      Tuple
            st Unit       Unit
            tt Trait
            un Union      Union
            ev TupleV(…)  TupleV(u32)
            ?? Unresolved
        "#]],
    );
}

#[test]
fn complete_in_block() {
    check_empty(
        r#"
    fn foo() {
        if true {
            $0
        }
    }
"#,
        expect![[r#"
            fn foo()       fn()
            bt u32         u32
            kw async
            kw const
            kw crate::
            kw enum
            kw extern
            kw false
            kw fn
            kw for
            kw if
            kw if let
            kw impl
            kw let
            kw loop
            kw match
            kw mod
            kw return
            kw self::
            kw static
            kw struct
            kw trait
            kw true
            kw type
            kw union
            kw unsafe
            kw use
            kw while
            kw while let
            sn macro_rules
            sn pd
            sn ppd
            ex false
            ex true
        "#]],
    )
}

#[test]
fn complete_after_if_expr() {
    check_empty(
        r#"
    fn foo() {
        if true {}
        $0
    }
"#,
        expect![[r#"
            fn foo()       fn()
            bt u32         u32
            kw async
            kw const
            kw crate::
            kw else
            kw else if
            kw enum
            kw extern
            kw false
            kw fn
            kw for
            kw if
            kw if let
            kw impl
            kw let
            kw loop
            kw match
            kw mod
            kw return
            kw self::
            kw static
            kw struct
            kw trait
            kw true
            kw type
            kw union
            kw unsafe
            kw use
            kw while
            kw while let
            sn macro_rules
            sn pd
            sn ppd
        "#]],
    )
}

#[test]
fn complete_in_match_arm() {
    check_empty(
        r#"
    fn foo() {
        match () {
            () => $0
        }
    }
"#,
        expect![[r#"
            fn foo()     fn()
            bt u32       u32
            kw crate::
            kw false
            kw for
            kw if
            kw if let
            kw loop
            kw match
            kw return
            kw self::
            kw true
            kw unsafe
            kw while
            kw while let
        "#]],
    )
}

#[test]
fn completes_in_loop_ctx() {
    check_empty(
        r"fn my() { loop { $0 } }",
        expect![[r#"
            fn my()        fn()
            bt u32         u32
            kw async
            kw break
            kw const
            kw continue
            kw crate::
            kw enum
            kw extern
            kw false
            kw fn
            kw for
            kw if
            kw if let
            kw impl
            kw let
            kw loop
            kw match
            kw mod
            kw return
            kw self::
            kw static
            kw struct
            kw trait
            kw true
            kw type
            kw union
            kw unsafe
            kw use
            kw while
            kw while let
            sn macro_rules
            sn pd
            sn ppd
        "#]],
    );
    check_empty(
        r"fn my() { loop { foo.$0 } }",
        expect![[r#"
            sn box    Box::new(expr)
            sn break  break expr
            sn call   function(expr)
            sn dbg    dbg!(expr)
            sn dbgr   dbg!(&expr)
            sn deref  *expr
            sn if     if expr {}
            sn let    let
            sn letm   let mut
            sn match  match expr {}
            sn not    !expr
            sn ref    &expr
            sn refm   &mut expr
            sn return return expr
            sn unsafe unsafe {}
            sn while  while expr {}
        "#]],
    );
}

#[test]
fn completes_in_let_initializer() {
    check_empty(
        r#"fn main() { let _ = $0 }"#,
        expect![[r#"
            fn main()    fn()
            bt u32       u32
            kw crate::
            kw false
            kw for
            kw if
            kw if let
            kw loop
            kw match
            kw return
            kw self::
            kw true
            kw unsafe
            kw while
            kw while let
        "#]],
    )
}

#[test]
fn struct_initializer_field_expr() {
    check_empty(
        r#"
struct Foo {
    pub f: i32,
}
fn foo() {
    Foo {
        f: $0
    }
}
"#,
        expect![[r#"
            fn foo()     fn()
            st Foo       Foo
            bt u32       u32
            kw crate::
            kw false
            kw for
            kw if
            kw if let
            kw loop
            kw match
            kw return
            kw self::
            kw true
            kw unsafe
            kw while
            kw while let
        "#]],
    );
}

#[test]
fn shadowing_shows_single_completion() {
    cov_mark::check!(shadowing_shows_single_completion);

    check_empty(
        r#"
fn foo() {
    let bar = 92;
    {
        let bar = 62;
        drop($0)
    }
}
"#,
        expect![[r#"
            fn foo()     fn()
            lc bar       i32
            bt u32       u32
            kw crate::
            kw false
            kw for
            kw if
            kw if let
            kw loop
            kw match
            kw return
            kw self::
            kw true
            kw unsafe
            kw while
            kw while let
        "#]],
    );
}

#[test]
fn in_macro_expr_frag() {
    check_empty(
        r#"
macro_rules! m { ($e:expr) => { $e } }
fn quux(x: i32) {
    m!($0);
}
"#,
        expect![[r#"
            fn quux(…)   fn(i32)
            lc x         i32
            ma m!(…)     macro_rules! m
            bt u32       u32
            kw crate::
            kw false
            kw for
            kw if
            kw if let
            kw loop
            kw match
            kw return
            kw self::
            kw true
            kw unsafe
            kw while
            kw while let
        "#]],
    );
    check_empty(
        r"
macro_rules! m { ($e:expr) => { $e } }
fn quux(x: i32) {
    m!(x$0);
}
",
        expect![[r#"
            fn quux(…)   fn(i32)
            lc x         i32
            ma m!(…)     macro_rules! m
            bt u32       u32
            kw crate::
            kw false
            kw for
            kw if
            kw if let
            kw loop
            kw match
            kw return
            kw self::
            kw true
            kw unsafe
            kw while
            kw while let
        "#]],
    );
    check_empty(
        r#"
macro_rules! m { ($e:expr) => { $e } }
fn quux(x: i32) {
    let y = 92;
    m!(x$0
}
"#,
        expect![[r#"
            fn quux(…)   fn(i32)
            lc x         i32
            lc y         i32
            ma m!(…)     macro_rules! m
            bt u32       u32
            kw crate::
            kw false
            kw for
            kw if
            kw if let
            kw loop
            kw match
            kw return
            kw self::
            kw true
            kw unsafe
            kw while
            kw while let
        "#]],
    );
}

#[test]
fn enum_qualified() {
    check(
        r#"
impl Enum {
    type AssocType = ();
    const ASSOC_CONST: () = ();
    fn assoc_fn() {}
}
fn func() {
    Enum::$0
}
"#,
        expect![[r#"
            ct ASSOC_CONST const ASSOC_CONST: ()
            fn assoc_fn()  fn()
            ta AssocType   type AssocType = ()
            ev RecordV {…} RecordV { field: u32 }
            ev TupleV(…)   TupleV(u32)
            ev UnitV       UnitV
        "#]],
    );
}

#[test]
fn ty_qualified_no_drop() {
    check_empty(
        r#"
//- minicore: drop
struct Foo;
impl Drop for Foo {
    fn drop(&mut self) {}
}
fn func() {
    Foo::$0
}
"#,
        expect![[r#""#]],
    );
}

#[test]
fn with_parens() {
    check_empty(
        r#"
enum Enum {
    Variant()
}
impl Enum {
    fn variant() -> Self { Enum::Variant() }
}
fn func() {
    Enum::$0()
}
"#,
        expect![[r#"
            fn variant fn() -> Enum
            ev Variant Variant
        "#]],
    );
}

#[test]
fn detail_impl_trait_in_return_position() {
    check_empty(
        r"
//- minicore: sized
trait Trait<T> {}
fn foo<U>() -> impl Trait<U> {}
fn main() {
    self::$0
}
",
        expect![[r#"
            fn foo()  fn() -> impl Trait<U>
            fn main() fn()
            tt Trait
        "#]],
    );
}

#[test]
fn detail_async_fn() {
    check_empty(
        r#"
//- minicore: future, sized
trait Trait<T> {}
async fn foo() -> u8 {}
async fn bar<U>() -> impl Trait<U> {}
fn main() {
    self::$0
}
"#,
        expect![[r#"
            fn bar()  async fn() -> impl Trait<U>
            fn foo()  async fn() -> u8
            fn main() fn()
            tt Trait
        "#]],
    );
}

#[test]
fn detail_impl_trait_in_argument_position() {
    check_empty(
        r"
//- minicore: sized
trait Trait<T> {}
struct Foo;
impl Foo {
    fn bar<U>(_: impl Trait<U>) {}
}
fn main() {
    Foo::$0
}
",
        expect![[r"
            fn bar(…) fn(impl Trait<U>)
        "]],
    );
}

#[test]
fn complete_record_expr_path() {
    check(
        r#"
struct Zulu;
impl Zulu {
    fn test() -> Self { }
}
fn boi(val: Zulu) { }
fn main() {
    boi(Zulu:: $0 {});
}
"#,
        expect![[r#"
            fn test()       fn() -> Zulu
            ex Zulu
            ex Zulu::test()
        "#]],
    );
}

#[test]
fn variant_with_struct() {
    check_empty(
        r#"
pub struct YoloVariant {
    pub f: usize
}

pub enum HH {
    Yolo(YoloVariant),
}

fn brr() {
    let t = HH::Yolo(Y$0);
}
"#,
        expect![[r#"
            en HH              HH
            fn brr()           fn()
            st YoloVariant     YoloVariant
            st YoloVariant {…} YoloVariant { f: usize }
            bt u32             u32
            kw crate::
            kw false
            kw for
            kw if
            kw if let
            kw loop
            kw match
            kw return
            kw self::
            kw true
            kw unsafe
            kw while
            kw while let
        "#]],
    );
}

#[test]
fn return_unit_block() {
    cov_mark::check!(return_unit_block);
    check_edit("return", r#"fn f() { if true { $0 } }"#, r#"fn f() { if true { return; } }"#);
}

#[test]
fn return_unit_no_block() {
    cov_mark::check!(return_unit_no_block);
    check_edit(
        "return",
        r#"fn f() { match () { () => $0 } }"#,
        r#"fn f() { match () { () => return } }"#,
    );
}

#[test]
fn return_value_block() {
    cov_mark::check!(return_value_block);
    check_edit(
        "return",
        r#"fn f() -> i32 { if true { $0 } }"#,
        r#"fn f() -> i32 { if true { return $0; } }"#,
    );
}

#[test]
fn return_value_no_block() {
    cov_mark::check!(return_value_no_block);
    check_edit(
        "return",
        r#"fn f() -> i32 { match () { () => $0 } }"#,
        r#"fn f() -> i32 { match () { () => return $0 } }"#,
    );
}

#[test]
fn else_completion_after_if() {
    check_empty(
        r#"
fn foo() { if foo {} $0 }
"#,
        expect![[r#"
            fn foo()       fn()
            bt u32         u32
            kw async
            kw const
            kw crate::
            kw else
            kw else if
            kw enum
            kw extern
            kw false
            kw fn
            kw for
            kw if
            kw if let
            kw impl
            kw let
            kw loop
            kw match
            kw mod
            kw return
            kw self::
            kw static
            kw struct
            kw trait
            kw true
            kw type
            kw union
            kw unsafe
            kw use
            kw while
            kw while let
            sn macro_rules
            sn pd
            sn ppd
        "#]],
    );
    check_empty(
        r#"
fn foo() { if foo {} el$0 }
"#,
        expect![[r#"
            fn foo()       fn()
            bt u32         u32
            kw async
            kw const
            kw crate::
            kw else
            kw else if
            kw enum
            kw extern
            kw false
            kw fn
            kw for
            kw if
            kw if let
            kw impl
            kw let
            kw loop
            kw match
            kw mod
            kw return
            kw self::
            kw static
            kw struct
            kw trait
            kw true
            kw type
            kw union
            kw unsafe
            kw use
            kw while
            kw while let
            sn macro_rules
            sn pd
            sn ppd
        "#]],
    );
    check_empty(
        r#"
fn foo() { bar(if foo {} $0) }
"#,
        expect![[r#"
            fn foo()     fn()
            bt u32       u32
            kw crate::
            kw else
            kw else if
            kw false
            kw for
            kw if
            kw if let
            kw loop
            kw match
            kw return
            kw self::
            kw true
            kw unsafe
            kw while
            kw while let
        "#]],
    );
    check_empty(
        r#"
fn foo() { bar(if foo {} el$0) }
"#,
        expect![[r#"
            fn foo()     fn()
            bt u32       u32
            kw crate::
            kw else
            kw else if
            kw false
            kw for
            kw if
            kw if let
            kw loop
            kw match
            kw return
            kw self::
            kw true
            kw unsafe
            kw while
            kw while let
        "#]],
    );
    check_empty(
        r#"
fn foo() { if foo {} $0 let x = 92; }
"#,
        expect![[r#"
            fn foo()       fn()
            bt u32         u32
            kw async
            kw const
            kw crate::
            kw else
            kw else if
            kw enum
            kw extern
            kw false
            kw fn
            kw for
            kw if
            kw if let
            kw impl
            kw let
            kw loop
            kw match
            kw mod
            kw return
            kw self::
            kw static
            kw struct
            kw trait
            kw true
            kw type
            kw union
            kw unsafe
            kw use
            kw while
            kw while let
            sn macro_rules
            sn pd
            sn ppd
        "#]],
    );
    check_empty(
        r#"
fn foo() { if foo {} el$0 let x = 92; }
"#,
        expect![[r#"
            fn foo()       fn()
            bt u32         u32
            kw async
            kw const
            kw crate::
            kw else
            kw else if
            kw enum
            kw extern
            kw false
            kw fn
            kw for
            kw if
            kw if let
            kw impl
            kw let
            kw loop
            kw match
            kw mod
            kw return
            kw self::
            kw static
            kw struct
            kw trait
            kw true
            kw type
            kw union
            kw unsafe
            kw use
            kw while
            kw while let
            sn macro_rules
            sn pd
            sn ppd
        "#]],
    );
    check_empty(
        r#"
fn foo() { if foo {} el$0 { let x = 92; } }
"#,
        expect![[r#"
            fn foo()       fn()
            bt u32         u32
            kw async
            kw const
            kw crate::
            kw else
            kw else if
            kw enum
            kw extern
            kw false
            kw fn
            kw for
            kw if
            kw if let
            kw impl
            kw let
            kw loop
            kw match
            kw mod
            kw return
            kw self::
            kw static
            kw struct
            kw trait
            kw true
            kw type
            kw union
            kw unsafe
            kw use
            kw while
            kw while let
            sn macro_rules
            sn pd
            sn ppd
        "#]],
    );
}

#[test]
fn expr_no_unstable_item_on_stable() {
    check_empty(
        r#"
//- /main.rs crate:main deps:std
use std::*;
fn main() {
    $0
}
//- /std.rs crate:std
#[unstable]
pub struct UnstableThisShouldNotBeListed;
"#,
        expect![[r#"
            fn main()      fn()
            md std
            bt u32         u32
            kw async
            kw const
            kw crate::
            kw enum
            kw extern
            kw false
            kw fn
            kw for
            kw if
            kw if let
            kw impl
            kw let
            kw loop
            kw match
            kw mod
            kw return
            kw self::
            kw static
            kw struct
            kw trait
            kw true
            kw type
            kw union
            kw unsafe
            kw use
            kw while
            kw while let
            sn macro_rules
            sn pd
            sn ppd
        "#]],
    );
}

#[test]
fn expr_unstable_item_on_nightly() {
    check_empty(
        r#"
//- toolchain:nightly
//- /main.rs crate:main deps:std
use std::*;
fn main() {
    $0
}
//- /std.rs crate:std
#[unstable]
pub struct UnstableButWeAreOnNightlyAnyway;
"#,
        expect![[r#"
            fn main()                 fn()
            md std
            st UnstableButWeAreOnNightlyAnyway UnstableButWeAreOnNightlyAnyway
            bt u32                    u32
            kw async
            kw const
            kw crate::
            kw enum
            kw extern
            kw false
            kw fn
            kw for
            kw if
            kw if let
            kw impl
            kw let
            kw loop
            kw match
            kw mod
            kw return
            kw self::
            kw static
            kw struct
            kw trait
            kw true
            kw type
            kw union
            kw unsafe
            kw use
            kw while
            kw while let
            sn macro_rules
            sn pd
            sn ppd
        "#]],
    );
}

#[test]
fn inside_format_args_completions_work() {
    check_empty(
        r#"
//- minicore: fmt
struct Foo;
impl Foo {
    fn foo(&self) {}
}

fn main() {
    format_args!("{}", Foo.$0);
}
"#,
        expect![[r#"
            me foo()  fn(&self)
            sn box    Box::new(expr)
            sn call   function(expr)
            sn dbg    dbg!(expr)
            sn dbgr   dbg!(&expr)
            sn deref  *expr
            sn match  match expr {}
            sn ref    &expr
            sn refm   &mut expr
            sn return return expr
            sn unsafe unsafe {}
        "#]],
    );
    check_empty(
        r#"
//- minicore: fmt
struct Foo;
impl Foo {
    fn foo(&self) {}
}

fn main() {
    format_args!("{}", Foo.f$0);
}
"#,
        expect![[r#"
            me foo()  fn(&self)
            sn box    Box::new(expr)
            sn call   function(expr)
            sn dbg    dbg!(expr)
            sn dbgr   dbg!(&expr)
            sn deref  *expr
            sn match  match expr {}
            sn ref    &expr
            sn refm   &mut expr
            sn return return expr
            sn unsafe unsafe {}
        "#]],
    );
}

#[test]
fn inside_faulty_format_args_completions_work() {
    check_empty(
        r#"
//- minicore: fmt
struct Foo;
impl Foo {
    fn foo(&self) {}
}

fn main() {
    format_args!("", Foo.$0);
}
"#,
        expect![[r#"
            me foo()  fn(&self)
            sn box    Box::new(expr)
            sn call   function(expr)
            sn dbg    dbg!(expr)
            sn dbgr   dbg!(&expr)
            sn deref  *expr
            sn match  match expr {}
            sn ref    &expr
            sn refm   &mut expr
            sn return return expr
            sn unsafe unsafe {}
        "#]],
    );
    check_empty(
        r#"
//- minicore: fmt
struct Foo;
impl Foo {
    fn foo(&self) {}
}

fn main() {
    format_args!("", Foo.f$0);
}
"#,
        expect![[r#"
            me foo()  fn(&self)
            sn box    Box::new(expr)
            sn call   function(expr)
            sn dbg    dbg!(expr)
            sn dbgr   dbg!(&expr)
            sn deref  *expr
            sn match  match expr {}
            sn ref    &expr
            sn refm   &mut expr
            sn return return expr
            sn unsafe unsafe {}
        "#]],
    );
    check_empty(
        r#"
//- minicore: fmt
struct Foo;
impl Foo {
    fn foo(&self) {}
}

fn main() {
    format_args!("{} {named} {captured} {named} {}", a, named = c, Foo.f$0);
}
"#,
        expect![[r#"
            me foo()  fn(&self)
            sn box    Box::new(expr)
            sn call   function(expr)
            sn dbg    dbg!(expr)
            sn dbgr   dbg!(&expr)
            sn deref  *expr
            sn match  match expr {}
            sn ref    &expr
            sn refm   &mut expr
            sn return return expr
            sn unsafe unsafe {}
        "#]],
    );
    check_empty(
        r#"
//- minicore: fmt
struct Foo;
impl Foo {
    fn foo(&self) {}
}

fn main() {
    format_args!("{", Foo.f$0);
}
"#,
        expect![[r#"
            sn box    Box::new(expr)
            sn call   function(expr)
            sn dbg    dbg!(expr)
            sn dbgr   dbg!(&expr)
            sn deref  *expr
            sn if     if expr {}
            sn match  match expr {}
            sn not    !expr
            sn ref    &expr
            sn refm   &mut expr
            sn return return expr
            sn unsafe unsafe {}
            sn while  while expr {}
        "#]],
    );
}
