<commit_before>//! Completion of names from the current scope, e.g. locals and imported items.

use std::iter;

use either::Either;
use hir::{Adt, ModPath, ModuleDef, ScopeDef, Type};
use ide_db::helpers::insert_use::ImportScope;
use ide_db::imports_locator;
use syntax::AstNode;
use test_utils::mark;

use crate::{
    render::{render_resolution_with_import, RenderContext},
    CompletionContext, Completions, ImportEdit,
};

pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
    if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) {
        return;
    }
    if ctx.record_lit_syntax.is_some()
        || ctx.record_pat_syntax.is_some()
        || ctx.attribute_under_caret.is_some()
        || ctx.mod_declaration_under_caret.is_some()
    {
        return;
    }

    if let Some(ty) = &ctx.expected_type {
        complete_enum_variants(acc, ctx, ty);
    }

    if ctx.is_pat_binding_or_const {
        return;
    }

    ctx.scope.process_all_names(&mut |name, res| {
        if ctx.use_item_syntax.is_some() {
            if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) {
                if name_ref.syntax().text() == name.to_string().as_str() {
                    mark::hit!(self_fulfilling_completion);
                    return;
                }
            }
        }
        acc.add_resolution(ctx, name.to_string(), &res)
    });

    if ctx.config.enable_autoimport_completions {
        fuzzy_completion(acc, ctx);
    }
}

fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) {
    if let Some(Adt::Enum(enum_data)) =
        iter::successors(Some(ty.clone()), |ty| ty.remove_ref()).last().and_then(|ty| ty.as_adt())
    {
        let variants = enum_data.variants(ctx.db);

        let module = if let Some(module) = ctx.scope.module() {
            // Compute path from the completion site if available.
            module
        } else {
            // Otherwise fall back to the enum's definition site.
            enum_data.module(ctx.db)
        };

        for variant in variants {
            if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) {
                // Variants with trivial paths are already added by the existing completion logic,
                // so we should avoid adding these twice
                if path.segments.len() > 1 {
                    acc.add_qualified_enum_variant(ctx, variant, path);
                }
            }
        }
    }
}

// Feature: Fuzzy Completion and Autoimports
//
// When completing names in the current scope, proposes additional imports from other modules or crates,
// if they can be qualified in the scope and their name contains all symbols from the completion input
// (case-insensitive, in any order or places).
//
// ```
// fn main() {
//     pda$0
// }
// # pub mod std { pub mod marker { pub struct PhantomData { } } }
// ```
// ->
// ```
// use std::marker::PhantomData;
//
// fn main() {
//     PhantomData
// }
// # pub mod std { pub mod marker { pub struct PhantomData { } } }
// ```
//
// .Fuzzy search details
//
// To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
// (i.e. in `HashMap` in the `std::collections::HashMap` path).
// For the same reasons, avoids searching for any imports for inputs with their length less that 2 symbols.
//
// .Merge Behavior
//
// It is possible to configure how use-trees are merged with the `importMergeBehavior` setting.
// Mimics the corresponding behavior of the `Auto Import` feature.
//
// .LSP and performance implications
//
// The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
// (case sensitive) resolve client capability in its client capabilities.
// This way the server is able to defer the costly computations, doing them for a selected completion item only.
// For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
// which might be slow ergo the feature is automatically disabled.
//
// .Feature toggle
//
// The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.enableAutoimportCompletions` flag.
// Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
// capability enabled.
fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
    let potential_import_name = ctx.token.to_string();
    let _p = profile::span("fuzzy_completion").detail(|| potential_import_name.clone());

    if potential_import_name.len() < 2 {
        return None;
    }

    let current_module = ctx.scope.module()?;
    let anchor = ctx.name_ref_syntax.as_ref()?;
    let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;

    let user_input_lowercased = potential_import_name.to_lowercase();
    let mut all_mod_paths = imports_locator::find_similar_imports(
        &ctx.sema,
        ctx.krate?,
        Some(40),
        potential_import_name,
        true,
        true,
    )
    .filter_map(|import_candidate| {
        Some(match import_candidate {
            Either::Left(module_def) => {
                (current_module.find_use_path(ctx.db, module_def)?, ScopeDef::ModuleDef(module_def))
            }
            Either::Right(macro_def) => {
                (current_module.find_use_path(ctx.db, macro_def)?, ScopeDef::MacroDef(macro_def))
            }
        })
    })
    .filter(|(mod_path, _)| mod_path.len() > 1)
    .collect::<Vec<_>>();

    all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
        compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
    });

    acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
        render_resolution_with_import(
            RenderContext::new(ctx),
            ImportEdit { import_path, import_scope: import_scope.clone() },
            &definition,
        )
    }));
    Some(())
}

fn compute_fuzzy_completion_order_key(
    proposed_mod_path: &ModPath,
    user_input_lowercased: &str,
) -> usize {
    mark::hit!(certain_fuzzy_order_test);
    let proposed_import_name = match proposed_mod_path.segments.last() {
        Some(name) => name.to_string().to_lowercase(),
        None => return usize::MAX,
    };
    match proposed_import_name.match_indices(user_input_lowercased).next() {
        Some((first_matching_index, _)) => first_matching_index,
        None => usize::MAX,
    }
}

#[cfg(test)]
mod tests {
    use expect_test::{expect, Expect};
    use test_utils::mark;

    use crate::{
        test_utils::{
            check_edit, check_edit_with_config, completion_list_with_config, TEST_CONFIG,
        },
        CompletionConfig, CompletionKind,
    };

    fn check(ra_fixture: &str, expect: Expect) {
        check_with_config(TEST_CONFIG, ra_fixture, expect);
    }

    fn check_with_config(config: CompletionConfig, ra_fixture: &str, expect: Expect) {
        let actual = completion_list_with_config(config, ra_fixture, CompletionKind::Reference);
        expect.assert_eq(&actual)
    }

    #[test]
    fn self_fulfilling_completion() {
        mark::check!(self_fulfilling_completion);
        check(
            r#"
use foo$0
use std::collections;
"#,
            expect![[r#"
                ?? collections
            "#]],
        );
    }

    #[test]
    fn bind_pat_and_path_ignore_at() {
        check(
            r#"
enum Enum { A, B }
fn quux(x: Option<Enum>) {
    match x {
        None => (),
        Some(en$0 @ Enum::A) => (),
    }
}
"#,
            expect![[""]],
        );
    }

    #[test]
    fn bind_pat_and_path_ignore_ref() {
        check(
            r#"
enum Enum { A, B }
fn quux(x: Option<Enum>) {
    match x {
        None => (),
        Some(ref en$0) => (),
    }
}
"#,
            expect![[""]],
        );
    }

    #[test]
    fn bind_pat_and_path() {
        check(
            r#"
enum Enum { A, B }
fn quux(x: Option<Enum>) {
    match x {
        None => (),
        Some(En$0) => (),
    }
}
"#,
            expect![[r#"
                en Enum
            "#]],
        );
    }

    #[test]
    fn completes_bindings_from_let() {
        check(
            r#"
fn quux(x: i32) {
    let y = 92;
    1 + $0;
    let z = ();
}
"#,
            expect![[r#"
                bn y       i32
                bn x       i32
                fn quux(…) fn quux(x: i32)
            "#]],
        );
    }

    #[test]
    fn completes_bindings_from_if_let() {
        check(
            r#"
fn quux() {
    if let Some(x) = foo() {
        let y = 92;
    };
    if let Some(a) = bar() {
        let b = 62;
        1 + $0
    }
}
"#,
            expect![[r#"
                bn b      i32
                bn a
                fn quux() fn quux()
            "#]],
        );
    }

    #[test]
    fn completes_bindings_from_for() {
        check(
            r#"
fn quux() {
    for x in &[1, 2, 3] { $0 }
}
"#,
            expect![[r#"
                bn x
                fn quux() fn quux()
            "#]],
        );
    }

    #[test]
    fn completes_if_prefix_is_keyword() {
        mark::check!(completes_if_prefix_is_keyword);
        check_edit(
            "wherewolf",
            r#"
fn main() {
    let wherewolf = 92;
    drop(where$0)
}
"#,
            r#"
fn main() {
    let wherewolf = 92;
    drop(wherewolf)
}
"#,
        )
    }

    #[test]
    fn completes_generic_params() {
        check(
            r#"fn quux<T>() { $0 }"#,
            expect![[r#"
                tp T
                fn quux() fn quux<T>()
            "#]],
        );
    }

    #[test]
    fn completes_generic_params_in_struct() {
        check(
            r#"struct S<T> { x: $0}"#,
            expect![[r#"
                tp Self
                tp T
                st S<…>
            "#]],
        );
    }

    #[test]
    fn completes_self_in_enum() {
        check(
            r#"enum X { Y($0) }"#,
            expect![[r#"
                tp Self
                en X
            "#]],
        );
    }

    #[test]
    fn completes_module_items() {
        check(
            r#"
struct S;
enum E {}
fn quux() { $0 }
"#,
            expect![[r#"
                st S
                fn quux() fn quux()
                en E
            "#]],
        );
    }

    /// Regression test for issue #6091.
    #[test]
    fn correctly_completes_module_items_prefixed_with_underscore() {
        check_edit(
            "_alpha",
            r#"
fn main() {
    _$0
}
fn _alpha() {}
"#,
            r#"
fn main() {
    _alpha()$0
}
fn _alpha() {}
"#,
        )
    }

    #[test]
    fn completes_extern_prelude() {
        check(
            r#"
//- /lib.rs crate:main deps:other_crate
use $0;

//- /other_crate/lib.rs crate:other_crate
// nothing here
"#,
            expect![[r#"
                md other_crate
            "#]],
        );
    }

    #[test]
    fn completes_module_items_in_nested_modules() {
        check(
            r#"
struct Foo;
mod m {
    struct Bar;
    fn quux() { $0 }
}
"#,
            expect![[r#"
                fn quux() fn quux()
                st Bar
            "#]],
        );
    }

    #[test]
    fn completes_return_type() {
        check(
            r#"
struct Foo;
fn x() -> $0
"#,
            expect![[r#"
                st Foo
                fn x() fn x()
            "#]],
        );
    }

    #[test]
    fn dont_show_both_completions_for_shadowing() {
        check(
            r#"
fn foo() {
    let bar = 92;
    {
        let bar = 62;
        drop($0)
    }
}
"#,
            // FIXME: should be only one bar here
            expect![[r#"
                bn bar   i32
                bn bar   i32
                fn foo() fn foo()
            "#]],
        );
    }

    #[test]
    fn completes_self_in_methods() {
        check(
            r#"impl S { fn foo(&self) { $0 } }"#,
            expect![[r#"
                bn self &{unknown}
                tp Self
            "#]],
        );
    }

    #[test]
    fn completes_prelude() {
        check(
            r#"
//- /main.rs crate:main deps:std
fn foo() { let x: $0 }

//- /std/lib.rs crate:std
#[prelude_import]
use prelude::*;

mod prelude { struct Option; }
"#,
            expect![[r#"
                fn foo()  fn foo()
                md std
                st Option
            "#]],
        );
    }

    #[test]
    fn completes_std_prelude_if_core_is_defined() {
        check(
            r#"
//- /main.rs crate:main deps:core,std
fn foo() { let x: $0 }

//- /core/lib.rs crate:core
#[prelude_import]
use prelude::*;

mod prelude { struct Option; }

//- /std/lib.rs crate:std deps:core
#[prelude_import]
use prelude::*;

mod prelude { struct String; }
"#,
            expect![[r#"
                fn foo()  fn foo()
                md std
                md core<commit_msg>Add a test for #7110<commit_after>//! Completion of names from the current scope, e.g. locals and imported items.

use std::iter;

use either::Either;
use hir::{Adt, ModPath, ModuleDef, ScopeDef, Type};
use ide_db::helpers::insert_use::ImportScope;
use ide_db::imports_locator;
use syntax::AstNode;
use test_utils::mark;

use crate::{
    render::{render_resolution_with_import, RenderContext},
    CompletionContext, Completions, ImportEdit,
};

pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
    if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) {
        return;
    }
    if ctx.record_lit_syntax.is_some()
        || ctx.record_pat_syntax.is_some()
        || ctx.attribute_under_caret.is_some()
        || ctx.mod_declaration_under_caret.is_some()
    {
        return;
    }

    if let Some(ty) = &ctx.expected_type {
        complete_enum_variants(acc, ctx, ty);
    }

    if ctx.is_pat_binding_or_const {
        return;
    }

    ctx.scope.process_all_names(&mut |name, res| {
        if ctx.use_item_syntax.is_some() {
            if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) {
                if name_ref.syntax().text() == name.to_string().as_str() {
                    mark::hit!(self_fulfilling_completion);
                    return;
                }
            }
        }
        acc.add_resolution(ctx, name.to_string(), &res)
    });

    if ctx.config.enable_autoimport_completions {
        fuzzy_completion(acc, ctx);
    }
}

fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) {
    if let Some(Adt::Enum(enum_data)) =
        iter::successors(Some(ty.clone()), |ty| ty.remove_ref()).last().and_then(|ty| ty.as_adt())
    {
        let variants = enum_data.variants(ctx.db);

        let module = if let Some(module) = ctx.scope.module() {
            // Compute path from the completion site if available.
            module
        } else {
            // Otherwise fall back to the enum's definition site.
            enum_data.module(ctx.db)
        };

        for variant in variants {
            if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) {
                // Variants with trivial paths are already added by the existing completion logic,
                // so we should avoid adding these twice
                if path.segments.len() > 1 {
                    acc.add_qualified_enum_variant(ctx, variant, path);
                }
            }
        }
    }
}

// Feature: Fuzzy Completion and Autoimports
//
// When completing names in the current scope, proposes additional imports from other modules or crates,
// if they can be qualified in the scope and their name contains all symbols from the completion input
// (case-insensitive, in any order or places).
//
// ```
// fn main() {
//     pda$0
// }
// # pub mod std { pub mod marker { pub struct PhantomData { } } }
// ```
// ->
// ```
// use std::marker::PhantomData;
//
// fn main() {
//     PhantomData
// }
// # pub mod std { pub mod marker { pub struct PhantomData { } } }
// ```
//
// .Fuzzy search details
//
// To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
// (i.e. in `HashMap` in the `std::collections::HashMap` path).
// For the same reasons, avoids searching for any imports for inputs with their length less that 2 symbols.
//
// .Merge Behavior
//
// It is possible to configure how use-trees are merged with the `importMergeBehavior` setting.
// Mimics the corresponding behavior of the `Auto Import` feature.
//
// .LSP and performance implications
//
// The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
// (case sensitive) resolve client capability in its client capabilities.
// This way the server is able to defer the costly computations, doing them for a selected completion item only.
// For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
// which might be slow ergo the feature is automatically disabled.
//
// .Feature toggle
//
// The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.enableAutoimportCompletions` flag.
// Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
// capability enabled.
fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
    let potential_import_name = ctx.token.to_string();
    let _p = profile::span("fuzzy_completion").detail(|| potential_import_name.clone());

    if potential_import_name.len() < 2 {
        return None;
    }

    let current_module = ctx.scope.module()?;
    let anchor = ctx.name_ref_syntax.as_ref()?;
    let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;

    let user_input_lowercased = potential_import_name.to_lowercase();
    let mut all_mod_paths = imports_locator::find_similar_imports(
        &ctx.sema,
        ctx.krate?,
        Some(40),
        potential_import_name,
        true,
        true,
    )
    .filter_map(|import_candidate| {
        Some(match import_candidate {
            Either::Left(module_def) => {
                (current_module.find_use_path(ctx.db, module_def)?, ScopeDef::ModuleDef(module_def))
            }
            Either::Right(macro_def) => {
                (current_module.find_use_path(ctx.db, macro_def)?, ScopeDef::MacroDef(macro_def))
            }
        })
    })
    .filter(|(mod_path, _)| mod_path.len() > 1)
    .collect::<Vec<_>>();

    all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
        compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
    });

    acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
        render_resolution_with_import(
            RenderContext::new(ctx),
            ImportEdit { import_path, import_scope: import_scope.clone() },
            &definition,
        )
    }));
    Some(())
}

fn compute_fuzzy_completion_order_key(
    proposed_mod_path: &ModPath,
    user_input_lowercased: &str,
) -> usize {
    mark::hit!(certain_fuzzy_order_test);
    let proposed_import_name = match proposed_mod_path.segments.last() {
        Some(name) => name.to_string().to_lowercase(),
        None => return usize::MAX,
    };
    match proposed_import_name.match_indices(user_input_lowercased).next() {
        Some((first_matching_index, _)) => first_matching_index,
        None => usize::MAX,
    }
}

#[cfg(test)]
mod tests {
    use expect_test::{expect, Expect};
    use test_utils::mark;

    use crate::{
        test_utils::{
            check_edit, check_edit_with_config, completion_list_with_config, TEST_CONFIG,
        },
        CompletionConfig, CompletionKind,
    };

    fn check(ra_fixture: &str, expect: Expect) {
        check_with_config(TEST_CONFIG, ra_fixture, expect);
    }

    fn check_with_config(config: CompletionConfig, ra_fixture: &str, expect: Expect) {
        let actual = completion_list_with_config(config, ra_fixture, CompletionKind::Reference);
        expect.assert_eq(&actual)
    }

    #[test]
    fn self_fulfilling_completion() {
        mark::check!(self_fulfilling_completion);
        check(
            r#"
use foo$0
use std::collections;
"#,
            expect![[r#"
                ?? collections
            "#]],
        );
    }

    #[test]
    fn bind_pat_and_path_ignore_at() {
        check(
            r#"
enum Enum { A, B }
fn quux(x: Option<Enum>) {
    match x {
        None => (),
        Some(en$0 @ Enum::A) => (),
    }
}
"#,
            expect![[""]],
        );
    }

    #[test]
    fn bind_pat_and_path_ignore_ref() {
        check(
            r#"
enum Enum { A, B }
fn quux(x: Option<Enum>) {
    match x {
        None => (),
        Some(ref en$0) => (),
    }
}
"#,
            expect![[""]],
        );
    }

    #[test]
    fn bind_pat_and_path() {
        check(
            r#"
enum Enum { A, B }
fn quux(x: Option<Enum>) {
    match x {
        None => (),
        Some(En$0) => (),
    }
}
"#,
            expect![[r#"
                en Enum
            "#]],
        );
    }

    #[test]
    fn completes_bindings_from_let() {
        check(
            r#"
fn quux(x: i32) {
    let y = 92;
    1 + $0;
    let z = ();
}
"#,
            expect![[r#"
                bn y       i32
                bn x       i32
                fn quux(…) fn quux(x: i32)
            "#]],
        );
    }

    #[test]
    fn completes_bindings_from_if_let() {
        check(
            r#"
fn quux() {
    if let Some(x) = foo() {
        let y = 92;
    };
    if let Some(a) = bar() {
        let b = 62;
        1 + $0
    }
}
"#,
            expect![[r#"
                bn b      i32
                bn a
                fn quux() fn quux()
            "#]],
        );
    }

    #[test]
    fn completes_bindings_from_for() {
        check(
            r#"
fn quux() {
    for x in &[1, 2, 3] { $0 }
}
"#,
            expect![[r#"
                bn x
                fn quux() fn quux()
            "#]],
        );
    }

    #[test]
    fn completes_if_prefix_is_keyword() {
        mark::check!(completes_if_prefix_is_keyword);
        check_edit(
            "wherewolf",
            r#"
fn main() {
    let wherewolf = 92;
    drop(where$0)
}
"#,
            r#"
fn main() {
    let wherewolf = 92;
    drop(wherewolf)
}
"#,
        )
    }

    #[test]
    fn completes_generic_params() {
        check(
            r#"fn quux<T>() { $0 }"#,
            expect![[r#"
                tp T
                fn quux() fn quux<T>()
            "#]],
        );
    }

    #[test]
    fn completes_generic_params_in_struct() {
        check(
            r#"struct S<T> { x: $0}"#,
            expect![[r#"
                tp Self
                tp T
                st S<…>
            "#]],
        );
    }

    #[test]
    fn completes_self_in_enum() {
        check(
            r#"enum X { Y($0) }"#,
            expect![[r#"
                tp Self
                en X
            "#]],
        );
    }

    #[test]
    fn completes_module_items() {
        check(
            r#"
struct S;
enum E {}
fn quux() { $0 }
"#,
            expect![[r#"
                st S
                fn quux() fn quux()
                en E
            "#]],
        );
    }

    /// Regression test for issue #6091.
    #[test]
    fn correctly_completes_module_items_prefixed_with_underscore() {
        check_edit(
            "_alpha",
            r#"
fn main() {
    _$0
}
fn _alpha() {}
"#,
            r#"
fn main() {
    _alpha()$0
}
fn _alpha() {}
"#,
        )
    }

    #[test]
    fn completes_extern_prelude() {
        check(
            r#"
//- /lib.rs crate:main deps:other_crate
use $0;

//- /other_crate/lib.rs crate:other_crate
// nothing here
"#,
            expect![[r#"
                md other_crate
            "#]],
        );
    }

    #[test]
    fn completes_module_items_in_nested_modules() {
        check(
            r#"
struct Foo;
mod m {
    struct Bar;
    fn quux() { $0 }
}
"#,
            expect![[r#"
                fn quux() fn quux()
                st Bar
            "#]],
        );
    }

    #[test]
    fn completes_return_type() {
        check(
            r#"
struct Foo;
fn x() -> $0
"#,
            expect![[r#"
                st Foo
                fn x() fn x()
            "#]],
        );
    }

    #[test]
    fn dont_show_both_completions_for_shadowing() {
        check(
            r#"
fn foo() {
    let bar = 92;
    {
        let bar = 62;
        drop($0)
    }
}
"#,
            // FIXME: should be only one bar here
            expect![[r#"
                bn bar   i32
                bn bar   i32
                fn foo() fn foo()
            "#]],
        );
    }

    #[test]
    fn completes_self_in_methods() {
        check(
            r#"impl S { fn foo(&self) { $0 } }"#,
            expect![[r#"
                bn self &{unknown}
                tp Self
            "#]],
        );
    }

    #[test]
    fn completes_prelude() {
        check(
            r#"
//- /main.rs crate:main deps:std
fn foo() { let x: $0 }

//- /std/lib.rs crate:std
#[prelude_import]
use prelude::*;

mod prelude { struct Option; }
"#,
            expect![[r#"
                fn foo()  fn foo()
                md std
                st Option
            "#]],
        );
    }

    #[test]
    fn completes_prelude_macros() {
        check(
            r#"
//- /main.rs crate:main deps:std
fn f() {$0}

//- /std/lib.rs crate:std
#[prelude_import]
pub use prelude::*;

#[macro_use]
mod prelude {
    pub use crate::concat;
}

mod macros {
    #[rustc_builtin_macro]
    #[macro_export]
    macro_rules! concat { }
}
"#,
            expect![[r##"
                fn f()        fn f()
                ma concat!(…) #[macro_export]
                macro_rules! concat
                md std
            "##]],
        );
    }

    #[test]
    fn completes_std_prelude_if_core_is_defined() {
        check(
            r#"
//- /main.rs crate:main deps:core,std
fn foo() { let x: $0 }

//- /core/lib.rs crate:core
#[prelude_import]
use prelude::*;

mod prelude { struct Option; }

//- /std/lib.rs crate:std deps:core
#[prelude_import]
use prelude::*;

mod prelude { struct String; }
"#,
            expect![[r#"
                fn foo()  fn foo()
                md std
                md core
{'fraction_non_alphanumeric': 0.13263584205197435, 'fraction_numerical': 0.005287433907076162, 'mean_word_length': 1.8104120560649173, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5391139', 'n_tokens_mistral': 8867, 'n_tokens_neox': 8521, 'n_words': 2016}