]> git.lizzy.rs Git - rust.git/commitdiff
Add flyimport completion for trait assoc items
authorKirill Bulatov <mail4score@gmail.com>
Tue, 5 Jan 2021 08:34:03 +0000 (10:34 +0200)
committerKirill Bulatov <mail4score@gmail.com>
Sat, 16 Jan 2021 18:44:12 +0000 (20:44 +0200)
18 files changed:
crates/assists/src/handlers/auto_import.rs
crates/assists/src/handlers/qualify_path.rs
crates/completion/src/completions/flyimport.rs
crates/completion/src/config.rs
crates/completion/src/item.rs
crates/completion/src/lib.rs
crates/completion/src/render.rs
crates/completion/src/test_utils.rs
crates/hir/src/code_model.rs
crates/hir_def/src/import_map.rs
crates/ide/src/doc_links.rs
crates/ide/src/lib.rs
crates/ide_db/src/helpers/import_assets.rs
crates/ide_db/src/imports_locator.rs
crates/rust-analyzer/src/cli/analysis_bench.rs
crates/rust-analyzer/src/config.rs
crates/rust-analyzer/src/handlers.rs
crates/rust-analyzer/src/to_proto.rs

index 4e2a4fcd9657c169339ef21234a1f41bb6f3c931..e93901cb3101f2b8f294e564fcb301913e5a176e 100644 (file)
@@ -3,7 +3,7 @@
     insert_use::{insert_use, ImportScope},
     mod_path_to_ast,
 };
-use syntax::ast;
+use syntax::{ast, AstNode, SyntaxNode};
 
 use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
 
 // # pub mod std { pub mod collections { pub struct HashMap { } } }
 // ```
 pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let import_assets =
-        if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
-            ImportAssets::for_regular_path(path_under_caret, &ctx.sema)
-        } else if let Some(method_under_caret) =
-            ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
-        {
-            ImportAssets::for_method_call(method_under_caret, &ctx.sema)
-        } else {
-            None
-        }?;
-    let proposed_imports = import_assets.search_for_imports(&ctx.sema, &ctx.config.insert_use);
+    let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
+    let proposed_imports =
+        import_assets.search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind);
     if proposed_imports.is_empty() {
         return None;
     }
 
-    let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
+    let range = ctx.sema.original_range(&syntax_under_caret).range;
     let group = import_group_message(import_assets.import_candidate());
-    let scope =
-        ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), &ctx.sema)?;
+    let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?;
     for (import, _) in proposed_imports {
         acc.add_group(
             &group,
@@ -117,14 +108,28 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
     Some(())
 }
 
+pub(super) fn find_importable_node(ctx: &AssistContext) -> Option<(ImportAssets, SyntaxNode)> {
+    if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
+        ImportAssets::for_exact_path(&path_under_caret, &ctx.sema)
+            .zip(Some(path_under_caret.syntax().clone()))
+    } else if let Some(method_under_caret) =
+        ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
+    {
+        ImportAssets::for_method_call(&method_under_caret, &ctx.sema)
+            .zip(Some(method_under_caret.syntax().clone()))
+    } else {
+        None
+    }
+}
+
 fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel {
     let name = match import_candidate {
-        ImportCandidate::Path(candidate) => format!("Import {}", &candidate.name),
+        ImportCandidate::Path(candidate) => format!("Import {}", candidate.name.text()),
         ImportCandidate::TraitAssocItem(candidate) => {
-            format!("Import a trait for item {}", &candidate.name)
+            format!("Import a trait for item {}", candidate.name.text())
         }
         ImportCandidate::TraitMethod(candidate) => {
-            format!("Import a trait for method {}", &candidate.name)
+            format!("Import a trait for method {}", candidate.name.text())
         }
     };
     GroupLabel(name)
index a7d9fd4dc2f617a63a1208fd282d29d8f8d7330b..af8a11d03556cfed271a1d66d548d2c4a11fbbf3 100644 (file)
@@ -1,10 +1,7 @@
 use std::iter;
 
 use hir::AsName;
-use ide_db::helpers::{
-    import_assets::{ImportAssets, ImportCandidate},
-    mod_path_to_ast,
-};
+use ide_db::helpers::{import_assets::ImportCandidate, mod_path_to_ast};
 use ide_db::RootDatabase;
 use syntax::{
     ast,
@@ -18,6 +15,8 @@
     AssistId, AssistKind, GroupLabel,
 };
 
+use super::auto_import::find_importable_node;
+
 // Assist: qualify_path
 //
 // If the name is unresolved, provides all possible qualified paths for it.
 // # pub mod std { pub mod collections { pub struct HashMap { } } }
 // ```
 pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let import_assets =
-        if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
-            ImportAssets::for_regular_path(path_under_caret, &ctx.sema)
-        } else if let Some(method_under_caret) =
-            ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
-        {
-            ImportAssets::for_method_call(method_under_caret, &ctx.sema)
-        } else {
-            None
-        }?;
+    let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
     let proposed_imports = import_assets.search_for_relative_paths(&ctx.sema);
     if proposed_imports.is_empty() {
         return None;
     }
 
     let candidate = import_assets.import_candidate();
-    let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
+    let range = ctx.sema.original_range(&syntax_under_caret).range;
 
     let qualify_candidate = match candidate {
         ImportCandidate::Path(candidate) => {
             if candidate.qualifier.is_some() {
                 mark::hit!(qualify_path_qualifier_start);
-                let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
+                let path = ast::Path::cast(syntax_under_caret)?;
                 let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
                 QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list())
             } else {
                 mark::hit!(qualify_path_unqualified_name);
-                let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
+                let path = ast::Path::cast(syntax_under_caret)?;
                 let generics = path.segment()?.generic_arg_list();
                 QualifyCandidate::UnqualifiedName(generics)
             }
         }
         ImportCandidate::TraitAssocItem(_) => {
             mark::hit!(qualify_path_trait_assoc_item);
-            let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
+            let path = ast::Path::cast(syntax_under_caret)?;
             let (qualifier, segment) = (path.qualifier()?, path.segment()?);
             QualifyCandidate::TraitAssocItem(qualifier, segment)
         }
         ImportCandidate::TraitMethod(_) => {
             mark::hit!(qualify_path_trait_method);
-            let mcall_expr = ast::MethodCallExpr::cast(import_assets.syntax_under_caret().clone())?;
+            let mcall_expr = ast::MethodCallExpr::cast(syntax_under_caret)?;
             QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr)
         }
     };
@@ -140,7 +130,7 @@ fn qualify_trait_method(
         let generics =
             mcall_expr.generic_arg_list().as_ref().map_or_else(String::new, ToString::to_string);
         let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args());
-        let trait_ = item_as_trait(item)?;
+        let trait_ = item_as_trait(db, item)?;
         let method = find_trait_method(db, trait_, &trait_method_name)?;
         if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) {
             let receiver = match self_access {
@@ -179,11 +169,13 @@ fn find_trait_method(
     }
 }
 
-fn item_as_trait(item: hir::ItemInNs) -> Option<hir::Trait> {
-    if let hir::ModuleDef::Trait(trait_) = hir::ModuleDef::from(item.as_module_def_id()?) {
+fn item_as_trait(db: &RootDatabase, item: hir::ItemInNs) -> Option<hir::Trait> {
+    let item_module_def = hir::ModuleDef::from(item.as_module_def_id()?);
+
+    if let hir::ModuleDef::Trait(trait_) = item_module_def {
         Some(trait_)
     } else {
-        None
+        item_module_def.as_assoc_item(db)?.containing_trait(db)
     }
 }
 
@@ -191,7 +183,8 @@ fn group_label(candidate: &ImportCandidate) -> GroupLabel {
     let name = match candidate {
         ImportCandidate::Path(it) => &it.name,
         ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name,
-    };
+    }
+    .text();
     GroupLabel(format!("Qualify {}", name))
 }
 
index 22280963845cdf33725367bcf0d3680ec36f4bcf..9101e405c96f732dad7c10b50d1b0f7182757fa3 100644 (file)
@@ -45,9 +45,8 @@
 //! 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.
 
-use either::Either;
 use hir::{ModPath, ScopeDef};
-use ide_db::{helpers::insert_use::ImportScope, imports_locator};
+use ide_db::helpers::{import_assets::ImportAssets, insert_use::ImportScope};
 use syntax::AstNode;
 use test_utils::mark;
 
@@ -60,7 +59,7 @@
 use super::Completions;
 
 pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
-    if !ctx.config.enable_autoimport_completions {
+    if !ctx.config.enable_imports_on_the_fly {
         return None;
     }
     if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() {
@@ -72,46 +71,56 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
     }
     let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string());
 
-    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 import_scope =
+        ImportScope::find_insert_use_container(ctx.name_ref_syntax.as_ref()?.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))
-            }
+    let mut all_mod_paths = import_assets(ctx, potential_import_name)?
+        .search_for_relative_paths(&ctx.sema)
+        .into_iter()
+        .map(|(mod_path, item_in_ns)| {
+            let scope_item = match item_in_ns {
+                hir::ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()),
+                hir::ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()),
+                hir::ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()),
+            };
+            (mod_path, scope_item)
         })
-    })
-    .filter(|(mod_path, _)| mod_path.len() > 1)
-    .collect::<Vec<_>>();
-
+        .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,
-        )
+        let import_for_trait_assoc_item = match definition {
+            ScopeDef::ModuleDef(module_def) => module_def
+                .as_assoc_item(ctx.db)
+                .and_then(|assoc| assoc.containing_trait(ctx.db))
+                .is_some(),
+            _ => false,
+        };
+        let import_edit = ImportEdit {
+            import_path,
+            import_scope: import_scope.clone(),
+            import_for_trait_assoc_item,
+        };
+        render_resolution_with_import(RenderContext::new(ctx), import_edit, &definition)
     }));
     Some(())
 }
 
+fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAssets> {
+    let current_module = ctx.scope.module()?;
+    if let Some(dot_receiver) = &ctx.dot_receiver {
+        ImportAssets::for_fuzzy_method_call(
+            current_module,
+            ctx.sema.type_of_expr(dot_receiver)?,
+            fuzzy_name,
+        )
+    } else {
+        ImportAssets::for_fuzzy_path(current_module, ctx.path_qual.clone(), fuzzy_name, &ctx.sema)
+    }
+}
+
 fn compute_fuzzy_completion_order_key(
     proposed_mod_path: &ModPath,
     user_input_lowercased: &str,
@@ -258,6 +267,176 @@ fn main() {
         );
     }
 
+    #[test]
+    fn trait_function_fuzzy_completion() {
+        let fixture = r#"
+        //- /lib.rs crate:dep
+        pub mod test_mod {
+            pub trait TestTrait {
+                const SPECIAL_CONST: u8;
+                type HumbleType;
+                fn weird_function();
+                fn random_method(&self);
+            }
+            pub struct TestStruct {}
+            impl TestTrait for TestStruct {
+                const SPECIAL_CONST: u8 = 42;
+                type HumbleType = ();
+                fn weird_function() {}
+                fn random_method(&self) {}
+            }
+        }
+
+        //- /main.rs crate:main deps:dep
+        fn main() {
+            dep::test_mod::TestStruct::wei$0
+        }
+        "#;
+
+        check(
+            fixture,
+            expect![[r#"
+            fn weird_function() (dep::test_mod::TestTrait) fn weird_function()
+        "#]],
+        );
+
+        check_edit(
+            "weird_function",
+            fixture,
+            r#"
+use dep::test_mod::TestTrait;
+
+fn main() {
+    dep::test_mod::TestStruct::weird_function()$0
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn trait_const_fuzzy_completion() {
+        let fixture = r#"
+        //- /lib.rs crate:dep
+        pub mod test_mod {
+            pub trait TestTrait {
+                const SPECIAL_CONST: u8;
+                type HumbleType;
+                fn weird_function();
+                fn random_method(&self);
+            }
+            pub struct TestStruct {}
+            impl TestTrait for TestStruct {
+                const SPECIAL_CONST: u8 = 42;
+                type HumbleType = ();
+                fn weird_function() {}
+                fn random_method(&self) {}
+            }
+        }
+
+        //- /main.rs crate:main deps:dep
+        fn main() {
+            dep::test_mod::TestStruct::spe$0
+        }
+        "#;
+
+        check(
+            fixture,
+            expect![[r#"
+            ct SPECIAL_CONST (dep::test_mod::TestTrait)
+        "#]],
+        );
+
+        check_edit(
+            "SPECIAL_CONST",
+            fixture,
+            r#"
+use dep::test_mod::TestTrait;
+
+fn main() {
+    dep::test_mod::TestStruct::SPECIAL_CONST
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn trait_method_fuzzy_completion() {
+        let fixture = r#"
+        //- /lib.rs crate:dep
+        pub mod test_mod {
+            pub trait TestTrait {
+                const SPECIAL_CONST: u8;
+                type HumbleType;
+                fn weird_function();
+                fn random_method(&self);
+            }
+            pub struct TestStruct {}
+            impl TestTrait for TestStruct {
+                const SPECIAL_CONST: u8 = 42;
+                type HumbleType = ();
+                fn weird_function() {}
+                fn random_method(&self) {}
+            }
+        }
+
+        //- /main.rs crate:main deps:dep
+        fn main() {
+            let test_struct = dep::test_mod::TestStruct {};
+            test_struct.ran$0
+        }
+        "#;
+
+        check(
+            fixture,
+            expect![[r#"
+            me random_method() (dep::test_mod::TestTrait) fn random_method(&self)
+        "#]],
+        );
+
+        check_edit(
+            "random_method",
+            fixture,
+            r#"
+use dep::test_mod::TestTrait;
+
+fn main() {
+    let test_struct = dep::test_mod::TestStruct {};
+    test_struct.random_method()$0
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn no_trait_type_fuzzy_completion() {
+        check(
+            r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+    pub trait TestTrait {
+        const SPECIAL_CONST: u8;
+        type HumbleType;
+        fn weird_function();
+        fn random_method(&self);
+    }
+    pub struct TestStruct {}
+    impl TestTrait for TestStruct {
+        const SPECIAL_CONST: u8 = 42;
+        type HumbleType = ();
+        fn weird_function() {}
+        fn random_method(&self) {}
+    }
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+    dep::test_mod::TestStruct::hum$0
+}
+"#,
+            expect![[r#""#]],
+        );
+    }
+
     #[test]
     fn does_not_propose_names_in_scope() {
         check(
@@ -288,4 +467,61 @@ fn main() {
             expect![[r#""#]],
         );
     }
+
+    #[test]
+    fn does_not_propose_traits_in_scope() {
+        check(
+            r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+    pub trait TestTrait {
+        const SPECIAL_CONST: u8;
+        type HumbleType;
+        fn weird_function();
+        fn random_method(&self);
+    }
+    pub struct TestStruct {}
+    impl TestTrait for TestStruct {
+        const SPECIAL_CONST: u8 = 42;
+        type HumbleType = ();
+        fn weird_function() {}
+        fn random_method(&self) {}
+    }
+}
+
+//- /main.rs crate:main deps:dep
+use dep::test_mod::{TestStruct, TestTrait};
+fn main() {
+    dep::test_mod::TestStruct::hum$0
+}
+"#,
+            expect![[r#""#]],
+        );
+    }
+
+    #[test]
+    fn blanket_trait_impl_import() {
+        check(
+            r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+    pub struct TestStruct {}
+    pub trait TestTrait {
+        fn another_function();
+    }
+    impl<T> TestTrait for T {
+        fn another_function() {}
+    }
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+    dep::test_mod::TestStruct::ano$0
+}
+"#,
+            expect![[r#"
+                fn another_function() (dep::test_mod::TestTrait) fn another_function()
+            "#]],
+        );
+    }
 }
index 58fc700f33d5c00ce75567fc317b2b1285645b5f..d70ed6c1cde518b06424e335715e3e80111508a9 100644 (file)
@@ -9,7 +9,7 @@
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct CompletionConfig {
     pub enable_postfix_completions: bool,
-    pub enable_autoimport_completions: bool,
+    pub enable_imports_on_the_fly: bool,
     pub add_call_parenthesis: bool,
     pub add_call_argument_snippets: bool,
     pub snippet_cap: Option<SnippetCap>,
index 0134ff219b6003a4e1fe1e535f6fd3626ce27f5f..378bd2c70b54babc93ff2848642c8c195bdfc423 100644 (file)
@@ -270,6 +270,7 @@ pub fn import_to_add(&self) -> Option<&ImportEdit> {
 pub struct ImportEdit {
     pub import_path: ModPath,
     pub import_scope: ImportScope,
+    pub import_for_trait_assoc_item: bool,
 }
 
 impl ImportEdit {
@@ -321,17 +322,19 @@ pub(crate) fn build(self) -> CompletionItem {
         let mut insert_text = self.insert_text;
 
         if let Some(import_to_add) = self.import_to_add.as_ref() {
-            let mut import_path_without_last_segment = import_to_add.import_path.to_owned();
-            let _ = import_path_without_last_segment.segments.pop();
-
-            if !import_path_without_last_segment.segments.is_empty() {
-                if lookup.is_none() {
-                    lookup = Some(label.clone());
-                }
-                if insert_text.is_none() {
-                    insert_text = Some(label.clone());
+            if import_to_add.import_for_trait_assoc_item {
+                lookup = lookup.or_else(|| Some(label.clone()));
+                insert_text = insert_text.or_else(|| Some(label.clone()));
+                label = format!("{} ({})", label, import_to_add.import_path);
+            } else {
+                let mut import_path_without_last_segment = import_to_add.import_path.to_owned();
+                let _ = import_path_without_last_segment.segments.pop();
+
+                if !import_path_without_last_segment.segments.is_empty() {
+                    lookup = lookup.or_else(|| Some(label.clone()));
+                    insert_text = insert_text.or_else(|| Some(label.clone()));
+                    label = format!("{}::{}", import_path_without_last_segment, label);
                 }
-                label = format!("{}::{}", import_path_without_last_segment, label);
             }
         }
 
index ee1b822e7c04e9a78865b9f3b28d619a477e0ac5..56ec13e8cd8c9168957b30d3c38faaa980ec9f6c 100644 (file)
@@ -139,6 +139,7 @@ pub fn resolve_completion_edits(
     position: FilePosition,
     full_import_path: &str,
     imported_name: String,
+    import_for_trait_assoc_item: bool,
 ) -> Option<Vec<TextEdit>> {
     let ctx = CompletionContext::new(db, position, config)?;
     let anchor = ctx.name_ref_syntax.as_ref()?;
@@ -154,7 +155,7 @@ pub fn resolve_completion_edits(
         })
         .find(|mod_path| mod_path.to_string() == full_import_path)?;
 
-    ImportEdit { import_path, import_scope }
+    ImportEdit { import_path, import_scope, import_for_trait_assoc_item }
         .to_text_edit(config.insert_use.merge)
         .map(|edit| vec![edit])
 }
index 820dd01d1f7e7855d1b76b406e1500b84bdef020..4b3c9702a4440e5e232b194cd7583c9e04bcdc1e 100644 (file)
@@ -10,7 +10,7 @@
 
 mod builder_ext;
 
-use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type};
+use hir::{Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type};
 use ide_db::{helpers::SnippetCap, RootDatabase};
 use syntax::TextRange;
 use test_utils::mark;
@@ -51,16 +51,16 @@ pub(crate) fn render_resolution_with_import<'a>(
     import_edit: ImportEdit,
     resolution: &ScopeDef,
 ) -> Option<CompletionItem> {
-    Render::new(ctx)
-        .render_resolution(
-            import_edit.import_path.segments.last()?.to_string(),
-            Some(import_edit),
-            resolution,
-        )
-        .map(|mut item| {
-            item.completion_kind = CompletionKind::Magic;
-            item
-        })
+    let local_name = match resolution {
+        ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(),
+        ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(),
+        ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(),
+        _ => import_edit.import_path.segments.last()?.to_string(),
+    };
+    Render::new(ctx).render_resolution(local_name, Some(import_edit), resolution).map(|mut item| {
+        item.completion_kind = CompletionKind::Magic;
+        item
+    })
 }
 
 /// Interface for data and methods required for items rendering.
index 6ea6da9893c8bfb60bae3eb4f4dfa38ecc09a3a9..3faf861b9af024844ffb4ffceeb218f371ec57a4 100644 (file)
@@ -18,7 +18,7 @@
 
 pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
     enable_postfix_completions: true,
-    enable_autoimport_completions: true,
+    enable_imports_on_the_fly: true,
     add_call_parenthesis: true,
     add_call_argument_snippets: true,
     snippet_cap: SnippetCap::new(true),
index 6cbf5cecfad6e2f3ab5a396efeac6ca953050452..2950f08b8f0077e21034fa3b0eb204ef5b8a74f2 100644 (file)
@@ -272,6 +272,15 @@ pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
 
         hir_ty::diagnostics::validate_module_item(db, module.id.krate, id, sink)
     }
+
+    pub fn as_assoc_item(self, db: &dyn HirDatabase) -> Option<AssocItem> {
+        match self {
+            ModuleDef::Function(f) => f.as_assoc_item(db),
+            ModuleDef::Const(c) => c.as_assoc_item(db),
+            ModuleDef::TypeAlias(t) => t.as_assoc_item(db),
+            _ => None,
+        }
+    }
 }
 
 impl Module {
@@ -1091,6 +1100,13 @@ pub fn container(self, db: &dyn HirDatabase) -> AssocItemContainer {
             AssocContainerId::ContainerId(_) => panic!("invalid AssocItem"),
         }
     }
+
+    pub fn containing_trait(self, db: &dyn HirDatabase) -> Option<Trait> {
+        match self.container(db) {
+            AssocItemContainer::Trait(t) => Some(t),
+            _ => None,
+        }
+    }
 }
 
 impl HasVisibility for AssocItem {
index e5368b293cfee4f66dad49774875b1aca7ab96c3..fac0de90cd67ad604738a8334abee64f39a5b569 100644 (file)
@@ -263,6 +263,7 @@ pub enum ImportKind {
     Trait,
     TypeAlias,
     BuiltinType,
+    AssociatedItem,
 }
 
 /// A way to match import map contents against the search query.
@@ -282,6 +283,7 @@ pub struct Query {
     query: String,
     lowercased: String,
     name_only: bool,
+    assoc_items_only: bool,
     search_mode: SearchMode,
     case_sensitive: bool,
     limit: usize,
@@ -295,6 +297,7 @@ pub fn new(query: String) -> Self {
             query,
             lowercased,
             name_only: false,
+            assoc_items_only: false,
             search_mode: SearchMode::Contains,
             case_sensitive: false,
             limit: usize::max_value(),
@@ -309,6 +312,11 @@ pub fn name_only(self) -> Self {
         Self { name_only: true, ..self }
     }
 
+    /// Matches only the entries that are associated items, ignoring the rest.
+    pub fn assoc_items_only(self) -> Self {
+        Self { assoc_items_only: true, ..self }
+    }
+
     /// Specifies the way to search for the entries using the query.
     pub fn search_mode(self, search_mode: SearchMode) -> Self {
         Self { search_mode, ..self }
@@ -331,6 +339,14 @@ pub fn exclude_import_kind(mut self, import_kind: ImportKind) -> Self {
     }
 
     fn import_matches(&self, import: &ImportInfo, enforce_lowercase: bool) -> bool {
+        if import.is_trait_assoc_item {
+            if self.exclude_import_kinds.contains(&ImportKind::AssociatedItem) {
+                return false;
+            }
+        } else if self.assoc_items_only {
+            return false;
+        }
+
         let mut input = if import.is_trait_assoc_item || self.name_only {
             import.path.segments.last().unwrap().to_string()
         } else {
@@ -813,6 +829,56 @@ pub trait Display {
         );
     }
 
+    #[test]
+    fn assoc_items_filtering() {
+        let ra_fixture = r#"
+        //- /main.rs crate:main deps:dep
+        //- /dep.rs crate:dep
+        pub mod fmt {
+            pub trait Display {
+                type FmtTypeAlias;
+                const FMT_CONST: bool;
+
+                fn format_function();
+                fn format_method(&self);
+            }
+        }
+    "#;
+
+        check_search(
+            ra_fixture,
+            "main",
+            Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy).assoc_items_only(),
+            expect![[r#"
+            dep::fmt::Display::FMT_CONST (a)
+            dep::fmt::Display::format_function (a)
+            dep::fmt::Display::format_method (a)
+        "#]],
+        );
+
+        check_search(
+            ra_fixture,
+            "main",
+            Query::new("fmt".to_string())
+                .search_mode(SearchMode::Fuzzy)
+                .exclude_import_kind(ImportKind::AssociatedItem),
+            expect![[r#"
+            dep::fmt (t)
+            dep::fmt::Display (t)
+        "#]],
+        );
+
+        check_search(
+            ra_fixture,
+            "main",
+            Query::new("fmt".to_string())
+                .search_mode(SearchMode::Fuzzy)
+                .assoc_items_only()
+                .exclude_import_kind(ImportKind::AssociatedItem),
+            expect![[r#""#]],
+        );
+    }
+
     #[test]
     fn search_mode() {
         let ra_fixture = r#"
index de10406bc62807e394b08251aff696d9a45834b5..1f08d7810388c80517c3ecf52b33305422140f01 100644 (file)
@@ -438,10 +438,10 @@ fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem)
         FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)),
         FieldOrAssocItem::AssocItem(assoc) => match assoc {
             AssocItem::Function(function) => {
-                let is_trait_method = matches!(
-                    function.as_assoc_item(db).map(|assoc| assoc.container(db)),
-                    Some(AssocItemContainer::Trait(..))
-                );
+                let is_trait_method = function
+                    .as_assoc_item(db)
+                    .and_then(|assoc| assoc.containing_trait(db))
+                    .is_some();
                 // This distinction may get more complicated when specialization is available.
                 // Rustdoc makes this decision based on whether a method 'has defaultness'.
                 // Currently this is only the case for provided trait methods.
index f8d69382e03356f2ef38c95e70ff992dfd71529c..07f52613f52d2bd0c058d331315a32b8e88444d0 100644 (file)
@@ -481,6 +481,7 @@ pub fn resolve_completion_edits(
         position: FilePosition,
         full_import_path: &str,
         imported_name: String,
+        import_for_trait_assoc_item: bool,
     ) -> Cancelable<Vec<TextEdit>> {
         Ok(self
             .with_db(|db| {
@@ -490,6 +491,7 @@ pub fn resolve_completion_edits(
                     position,
                     full_import_path,
                     imported_name,
+                    import_for_trait_assoc_item,
                 )
             })?
             .unwrap_or_default())
index edc3da318e02611d021935a8e59b3e549fc1019a..e284220c1e51903fd7fb4c2282c7271c58f1942a 100644 (file)
@@ -1,12 +1,13 @@
 //! Look up accessible paths for items.
 use either::Either;
-use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics};
+use hir::{AsAssocItem, AssocItem, Module, ModuleDef, PrefixKind, Semantics};
 use rustc_hash::FxHashSet;
-use syntax::{ast, AstNode, SyntaxNode};
+use syntax::{ast, AstNode};
 
-use crate::{imports_locator, RootDatabase};
-
-use super::insert_use::InsertUseConfig;
+use crate::{
+    imports_locator::{self, AssocItemSearch},
+    RootDatabase,
+};
 
 #[derive(Debug)]
 pub enum ImportCandidate {
@@ -24,86 +25,141 @@ pub enum ImportCandidate {
 
 #[derive(Debug)]
 pub struct TraitImportCandidate {
-    pub ty: hir::Type,
-    pub name: ast::NameRef,
+    pub receiver_ty: hir::Type,
+    pub name: NameToImport,
 }
 
 #[derive(Debug)]
 pub struct PathImportCandidate {
     pub qualifier: Option<ast::Path>,
-    pub name: ast::NameRef,
+    pub name: NameToImport,
+}
+
+#[derive(Debug)]
+pub enum NameToImport {
+    Exact(String),
+    Fuzzy(String),
+}
+
+impl NameToImport {
+    pub fn text(&self) -> &str {
+        match self {
+            NameToImport::Exact(text) => text.as_str(),
+            NameToImport::Fuzzy(text) => text.as_str(),
+        }
+    }
 }
 
 #[derive(Debug)]
 pub struct ImportAssets {
     import_candidate: ImportCandidate,
-    module_with_name_to_import: hir::Module,
-    syntax_under_caret: SyntaxNode,
+    module_with_candidate: hir::Module,
 }
 
 impl ImportAssets {
     pub fn for_method_call(
-        method_call: ast::MethodCallExpr,
+        method_call: &ast::MethodCallExpr,
         sema: &Semantics<RootDatabase>,
     ) -> Option<Self> {
-        let syntax_under_caret = method_call.syntax().to_owned();
-        let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
         Some(Self {
-            import_candidate: ImportCandidate::for_method_call(sema, &method_call)?,
-            module_with_name_to_import,
-            syntax_under_caret,
+            import_candidate: ImportCandidate::for_method_call(sema, method_call)?,
+            module_with_candidate: sema.scope(method_call.syntax()).module()?,
         })
     }
 
-    pub fn for_regular_path(
-        path_under_caret: ast::Path,
+    pub fn for_exact_path(
+        fully_qualified_path: &ast::Path,
         sema: &Semantics<RootDatabase>,
     ) -> Option<Self> {
-        let syntax_under_caret = path_under_caret.syntax().to_owned();
+        let syntax_under_caret = fully_qualified_path.syntax();
         if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
             return None;
         }
-
-        let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
         Some(Self {
-            import_candidate: ImportCandidate::for_regular_path(sema, &path_under_caret)?,
-            module_with_name_to_import,
-            syntax_under_caret,
+            import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?,
+            module_with_candidate: sema.scope(syntax_under_caret).module()?,
+        })
+    }
+
+    pub fn for_fuzzy_path(
+        module_with_path: Module,
+        qualifier: Option<ast::Path>,
+        fuzzy_name: String,
+        sema: &Semantics<RootDatabase>,
+    ) -> Option<Self> {
+        Some(match qualifier {
+            Some(qualifier) => {
+                let qualifier_resolution = sema.resolve_path(&qualifier)?;
+                match qualifier_resolution {
+                    hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => Self {
+                        import_candidate: ImportCandidate::TraitAssocItem(TraitImportCandidate {
+                            receiver_ty: assoc_item_path.ty(sema.db),
+                            name: NameToImport::Fuzzy(fuzzy_name),
+                        }),
+                        module_with_candidate: module_with_path,
+                    },
+                    _ => Self {
+                        import_candidate: ImportCandidate::Path(PathImportCandidate {
+                            qualifier: Some(qualifier),
+                            name: NameToImport::Fuzzy(fuzzy_name),
+                        }),
+                        module_with_candidate: module_with_path,
+                    },
+                }
+            }
+            None => Self {
+                import_candidate: ImportCandidate::Path(PathImportCandidate {
+                    qualifier: None,
+                    name: NameToImport::Fuzzy(fuzzy_name),
+                }),
+                module_with_candidate: module_with_path,
+            },
         })
     }
 
-    pub fn syntax_under_caret(&self) -> &SyntaxNode {
-        &self.syntax_under_caret
+    pub fn for_fuzzy_method_call(
+        module_with_method_call: Module,
+        receiver_ty: hir::Type,
+        fuzzy_method_name: String,
+    ) -> Option<Self> {
+        Some(Self {
+            import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
+                receiver_ty,
+                name: NameToImport::Fuzzy(fuzzy_method_name),
+            }),
+            module_with_candidate: module_with_method_call,
+        })
     }
+}
 
+impl ImportAssets {
     pub fn import_candidate(&self) -> &ImportCandidate {
         &self.import_candidate
     }
 
-    fn get_search_query(&self) -> &str {
+    fn name_to_import(&self) -> &NameToImport {
         match &self.import_candidate {
-            ImportCandidate::Path(candidate) => candidate.name.text(),
+            ImportCandidate::Path(candidate) => &candidate.name,
             ImportCandidate::TraitAssocItem(candidate)
-            | ImportCandidate::TraitMethod(candidate) => candidate.name.text(),
+            | ImportCandidate::TraitMethod(candidate) => &candidate.name,
         }
     }
 
     pub fn search_for_imports(
         &self,
         sema: &Semantics<RootDatabase>,
-        config: &InsertUseConfig,
+        prefix_kind: PrefixKind,
     ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
-        let _p = profile::span("import_assists::search_for_imports");
-        self.search_for(sema, Some(config.prefix_kind))
+        let _p = profile::span("import_assets::search_for_imports");
+        self.search_for(sema, Some(prefix_kind))
     }
 
     /// This may return non-absolute paths if a part of the returned path is already imported into scope.
-    #[allow(dead_code)]
     pub fn search_for_relative_paths(
         &self,
         sema: &Semantics<RootDatabase>,
     ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
-        let _p = profile::span("import_assists::search_for_relative_paths");
+        let _p = profile::span("import_assets::search_for_relative_paths");
         self.search_for(sema, None)
     }
 
@@ -114,60 +170,56 @@ fn search_for(
     ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
         let db = sema.db;
         let mut trait_candidates = FxHashSet::default();
-        let current_crate = self.module_with_name_to_import.krate();
+        let current_crate = self.module_with_candidate.krate();
 
         let filter = |candidate: Either<hir::ModuleDef, hir::MacroDef>| {
             trait_candidates.clear();
             match &self.import_candidate {
                 ImportCandidate::TraitAssocItem(trait_candidate) => {
-                    let located_assoc_item = match candidate {
-                        Either::Left(ModuleDef::Function(located_function)) => {
-                            located_function.as_assoc_item(db)
-                        }
-                        Either::Left(ModuleDef::Const(located_const)) => {
-                            located_const.as_assoc_item(db)
-                        }
+                    let canidate_assoc_item = match candidate {
+                        Either::Left(module_def) => module_def.as_assoc_item(db),
                         _ => None,
-                    }
-                    .map(|assoc| assoc.container(db))
-                    .and_then(Self::assoc_to_trait)?;
-
-                    trait_candidates.insert(located_assoc_item.into());
+                    }?;
+                    trait_candidates.insert(canidate_assoc_item.containing_trait(db)?.into());
 
                     trait_candidate
-                        .ty
+                        .receiver_ty
                         .iterate_path_candidates(
                             db,
                             current_crate,
                             &trait_candidates,
                             None,
-                            |_, assoc| Self::assoc_to_trait(assoc.container(db)),
+                            |_, assoc| {
+                                if canidate_assoc_item == assoc {
+                                    Some(assoc_to_module_def(assoc))
+                                } else {
+                                    None
+                                }
+                            },
                         )
-                        .map(ModuleDef::from)
                         .map(Either::Left)
                 }
                 ImportCandidate::TraitMethod(trait_candidate) => {
-                    let located_assoc_item =
-                        if let Either::Left(ModuleDef::Function(located_function)) = candidate {
-                            located_function
-                                .as_assoc_item(db)
-                                .map(|assoc| assoc.container(db))
-                                .and_then(Self::assoc_to_trait)
-                        } else {
-                            None
-                        }?;
-
-                    trait_candidates.insert(located_assoc_item.into());
+                    let canidate_assoc_item = match candidate {
+                        Either::Left(module_def) => module_def.as_assoc_item(db),
+                        _ => None,
+                    }?;
+                    trait_candidates.insert(canidate_assoc_item.containing_trait(db)?.into());
 
                     trait_candidate
-                        .ty
+                        .receiver_ty
                         .iterate_method_candidates(
                             db,
                             current_crate,
                             &trait_candidates,
                             None,
                             |_, function| {
-                                Self::assoc_to_trait(function.as_assoc_item(db)?.container(db))
+                                let assoc = function.as_assoc_item(db)?;
+                                if canidate_assoc_item == assoc {
+                                    Some(assoc_to_module_def(assoc))
+                                } else {
+                                    None
+                                }
                             },
                         )
                         .map(ModuleDef::from)
@@ -177,34 +229,69 @@ fn search_for(
             }
         };
 
-        let mut res = imports_locator::find_exact_imports(
-            sema,
-            current_crate,
-            self.get_search_query().to_string(),
-        )
-        .filter_map(filter)
-        .filter_map(|candidate| {
-            let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
-            if let Some(prefix_kind) = prefixed {
-                self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind)
-            } else {
-                self.module_with_name_to_import.find_use_path(db, item)
+        let unfiltered_imports = match self.name_to_import() {
+            NameToImport::Exact(exact_name) => {
+                imports_locator::find_exact_imports(sema, current_crate, exact_name.clone())
             }
-            .map(|path| (path, item))
-        })
-        .filter(|(use_path, _)| use_path.len() > 1)
-        .take(20)
-        .collect::<Vec<_>>();
-        res.sort_by_key(|(path, _)| path.clone());
+            // FIXME: ideally, we should avoid using `fst` for seacrhing trait imports for assoc items:
+            // instead, we need to look up all trait impls for a certain struct and search through them only
+            // see https://github.com/rust-analyzer/rust-analyzer/pull/7293#issuecomment-761585032
+            // and https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Blanket.20trait.20impls.20lookup
+            // for the details
+            NameToImport::Fuzzy(fuzzy_name) => imports_locator::find_similar_imports(
+                sema,
+                current_crate,
+                fuzzy_name.clone(),
+                match self.import_candidate {
+                    ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => {
+                        AssocItemSearch::AssocItemsOnly
+                    }
+                    _ => AssocItemSearch::Exclude,
+                },
+            ),
+        };
+
+        let mut res = unfiltered_imports
+            .filter_map(filter)
+            .filter_map(|candidate| {
+                let item: hir::ItemInNs = candidate.clone().either(Into::into, Into::into);
+
+                let item_to_search = match self.import_candidate {
+                    ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => {
+                        let canidate_trait = match candidate {
+                            Either::Left(module_def) => {
+                                module_def.as_assoc_item(db)?.containing_trait(db)
+                            }
+                            _ => None,
+                        }?;
+                        ModuleDef::from(canidate_trait).into()
+                    }
+                    _ => item,
+                };
+
+                if let Some(prefix_kind) = prefixed {
+                    self.module_with_candidate.find_use_path_prefixed(
+                        db,
+                        item_to_search,
+                        prefix_kind,
+                    )
+                } else {
+                    self.module_with_candidate.find_use_path(db, item_to_search)
+                }
+                .map(|path| (path, item))
+            })
+            .filter(|(use_path, _)| use_path.len() > 1)
+            .collect::<Vec<_>>();
+        res.sort_by_cached_key(|(path, _)| path.clone());
         res
     }
+}
 
-    fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> {
-        if let AssocItemContainer::Trait(extracted_trait) = assoc {
-            Some(extracted_trait)
-        } else {
-            None
-        }
+fn assoc_to_module_def(assoc: AssocItem) -> ModuleDef {
+    match assoc {
+        AssocItem::Function(f) => f.into(),
+        AssocItem::Const(c) => c.into(),
+        AssocItem::TypeAlias(t) => t.into(),
     }
 }
 
@@ -216,22 +303,19 @@ fn for_method_call(
         match sema.resolve_method_call(method_call) {
             Some(_) => None,
             None => Some(Self::TraitMethod(TraitImportCandidate {
-                ty: sema.type_of_expr(&method_call.receiver()?)?,
-                name: method_call.name_ref()?,
+                receiver_ty: sema.type_of_expr(&method_call.receiver()?)?,
+                name: NameToImport::Exact(method_call.name_ref()?.to_string()),
             })),
         }
     }
 
-    fn for_regular_path(
-        sema: &Semantics<RootDatabase>,
-        path_under_caret: &ast::Path,
-    ) -> Option<Self> {
-        if sema.resolve_path(path_under_caret).is_some() {
+    fn for_regular_path(sema: &Semantics<RootDatabase>, path: &ast::Path) -> Option<Self> {
+        if sema.resolve_path(path).is_some() {
             return None;
         }
 
-        let segment = path_under_caret.segment()?;
-        let candidate = if let Some(qualifier) = path_under_caret.qualifier() {
+        let segment = path.segment()?;
+        let candidate = if let Some(qualifier) = path.qualifier() {
             let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
             let qualifier_start_path =
                 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
@@ -244,8 +328,8 @@ fn for_regular_path(
                 match qualifier_resolution {
                     hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
                         ImportCandidate::TraitAssocItem(TraitImportCandidate {
-                            ty: assoc_item_path.ty(sema.db),
-                            name: segment.name_ref()?,
+                            receiver_ty: assoc_item_path.ty(sema.db),
+                            name: NameToImport::Exact(segment.name_ref()?.to_string()),
                         })
                     }
                     _ => return None,
@@ -253,13 +337,15 @@ fn for_regular_path(
             } else {
                 ImportCandidate::Path(PathImportCandidate {
                     qualifier: Some(qualifier),
-                    name: qualifier_start,
+                    name: NameToImport::Exact(qualifier_start.to_string()),
                 })
             }
         } else {
             ImportCandidate::Path(PathImportCandidate {
                 qualifier: None,
-                name: segment.syntax().descendants().find_map(ast::NameRef::cast)?,
+                name: NameToImport::Exact(
+                    segment.syntax().descendants().find_map(ast::NameRef::cast)?.to_string(),
+                ),
             })
         };
         Some(candidate)
index d111fba9257f13c5da3c5321bda9dee2f955a228..d69e6596081be099dfd90650d17bb5e387fae5aa 100644 (file)
@@ -1,7 +1,10 @@
 //! This module contains an import search functionality that is provided to the assists module.
 //! Later, this should be moved away to a separate crate that is accessible from the assists module.
 
-use hir::{import_map, AsAssocItem, Crate, MacroDef, ModuleDef, Semantics};
+use hir::{
+    import_map::{self, ImportKind},
+    AsAssocItem, Crate, MacroDef, ModuleDef, Semantics,
+};
 use syntax::{ast, AstNode, SyntaxKind::NAME};
 
 use crate::{
@@ -18,9 +21,9 @@ pub fn find_exact_imports<'a>(
     sema: &Semantics<'a, RootDatabase>,
     krate: Crate,
     name_to_import: String,
-) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
+) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>>> {
     let _p = profile::span("find_exact_imports");
-    find_imports(
+    Box::new(find_imports(
         sema,
         krate,
         {
@@ -34,47 +37,58 @@ pub fn find_exact_imports<'a>(
             .name_only()
             .search_mode(import_map::SearchMode::Equals)
             .case_sensitive(),
-    )
+    ))
+}
+
+pub enum AssocItemSearch {
+    Include,
+    Exclude,
+    AssocItemsOnly,
 }
 
 pub fn find_similar_imports<'a>(
     sema: &Semantics<'a, RootDatabase>,
     krate: Crate,
-    limit: Option<usize>,
     fuzzy_search_string: String,
-    ignore_assoc_items: bool,
-    name_only: bool,
-) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> + 'a {
+    assoc_item_search: AssocItemSearch,
+) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a> {
     let _p = profile::span("find_similar_imports");
 
     let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
-        .search_mode(import_map::SearchMode::Fuzzy);
-    if name_only {
-        external_query = external_query.name_only();
+        .search_mode(import_map::SearchMode::Fuzzy)
+        .name_only()
+        .limit(QUERY_SEARCH_LIMIT);
+
+    match assoc_item_search {
+        AssocItemSearch::Include => {}
+        AssocItemSearch::Exclude => {
+            external_query = external_query.exclude_import_kind(ImportKind::AssociatedItem);
+        }
+        AssocItemSearch::AssocItemsOnly => {
+            external_query = external_query.assoc_items_only();
+        }
     }
 
     let mut local_query = symbol_index::Query::new(fuzzy_search_string);
-
-    if let Some(limit) = limit {
-        local_query.limit(limit);
-        external_query = external_query.limit(limit);
-    }
+    local_query.limit(QUERY_SEARCH_LIMIT);
 
     let db = sema.db;
-    find_imports(sema, krate, local_query, external_query).filter(move |import_candidate| {
-        if ignore_assoc_items {
-            match import_candidate {
-                Either::Left(ModuleDef::Function(function)) => function.as_assoc_item(db).is_none(),
-                Either::Left(ModuleDef::Const(const_)) => const_.as_assoc_item(db).is_none(),
-                Either::Left(ModuleDef::TypeAlias(type_alias)) => {
-                    type_alias.as_assoc_item(db).is_none()
-                }
-                _ => true,
-            }
-        } else {
-            true
-        }
-    })
+    Box::new(find_imports(sema, krate, local_query, external_query).filter(
+        move |import_candidate| match assoc_item_search {
+            AssocItemSearch::Include => true,
+            AssocItemSearch::Exclude => !is_assoc_item(import_candidate, db),
+            AssocItemSearch::AssocItemsOnly => is_assoc_item(import_candidate, db),
+        },
+    ))
+}
+
+fn is_assoc_item(import_candidate: &Either<ModuleDef, MacroDef>, db: &RootDatabase) -> bool {
+    match import_candidate {
+        Either::Left(ModuleDef::Function(function)) => function.as_assoc_item(db).is_some(),
+        Either::Left(ModuleDef::Const(const_)) => const_.as_assoc_item(db).is_some(),
+        Either::Left(ModuleDef::TypeAlias(type_alias)) => type_alias.as_assoc_item(db).is_some(),
+        _ => false,
+    }
 }
 
 fn find_imports<'a>(
index a02c8327fe2f77fb4befda9e85addbabbba5a045..a01b49822922a91ade53c08072366b6e3308e1b2 100644 (file)
@@ -93,7 +93,7 @@ pub fn run(self, verbosity: Verbosity) -> Result<()> {
                 if is_completion {
                     let options = CompletionConfig {
                         enable_postfix_completions: true,
-                        enable_autoimport_completions: true,
+                        enable_imports_on_the_fly: true,
                         add_call_parenthesis: true,
                         add_call_argument_snippets: true,
                         snippet_cap: SnippetCap::new(true),
index ce9655818c6821bb122d140135b6e22526ccf68d..3ddb9e19afcd83302dceb2dca03d7d9c3601e088 100644 (file)
@@ -559,7 +559,7 @@ fn insert_use_config(&self) -> InsertUseConfig {
     pub fn completion(&self) -> CompletionConfig {
         CompletionConfig {
             enable_postfix_completions: self.data.completion_postfix_enable,
-            enable_autoimport_completions: self.data.completion_autoimport_enable
+            enable_imports_on_the_fly: self.data.completion_autoimport_enable
                 && completion_item_edit_resolve(&self.caps),
             add_call_parenthesis: self.data.completion_addCallParenthesis,
             add_call_argument_snippets: self.data.completion_addCallArgumentSnippets,
@@ -581,18 +581,7 @@ pub fn assist(&self) -> AssistConfig {
         AssistConfig {
             snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
             allowed: None,
-            insert_use: InsertUseConfig {
-                merge: match self.data.assist_importMergeBehavior {
-                    MergeBehaviorDef::None => None,
-                    MergeBehaviorDef::Full => Some(MergeBehavior::Full),
-                    MergeBehaviorDef::Last => Some(MergeBehavior::Last),
-                },
-                prefix_kind: match self.data.assist_importPrefix {
-                    ImportPrefixDef::Plain => PrefixKind::Plain,
-                    ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
-                    ImportPrefixDef::BySelf => PrefixKind::BySelf,
-                },
-            },
+            insert_use: self.insert_use_config(),
         }
     }
     pub fn call_info_full(&self) -> bool {
index 1a4e0dd3283d8cc6612ee1449b4ae9a4bbd10a2f..a19e9e7dc1b9b2a442756990e18ff25babdf8a35 100644 (file)
@@ -653,7 +653,7 @@ pub(crate) fn handle_completion(
             let mut new_completion_items =
                 to_proto::completion_item(&line_index, line_endings, item.clone());
 
-            if completion_config.enable_autoimport_completions {
+            if completion_config.enable_imports_on_the_fly {
                 for new_item in &mut new_completion_items {
                     fill_resolve_data(&mut new_item.data, &item, &text_document_position);
                 }
@@ -703,6 +703,7 @@ pub(crate) fn handle_completion_resolve(
             FilePosition { file_id, offset },
             &resolve_data.full_import_path,
             resolve_data.imported_name,
+            resolve_data.import_for_trait_assoc_item,
         )?
         .into_iter()
         .flat_map(|edit| {
@@ -1694,6 +1695,7 @@ struct CompletionResolveData {
     position: lsp_types::TextDocumentPositionParams,
     full_import_path: String,
     imported_name: String,
+    import_for_trait_assoc_item: bool,
 }
 
 fn fill_resolve_data(
@@ -1710,6 +1712,7 @@ fn fill_resolve_data(
             position: position.to_owned(),
             full_import_path,
             imported_name,
+            import_for_trait_assoc_item: import_edit.import_for_trait_assoc_item,
         })
         .unwrap(),
     );
index 1ff2d3fea659a76ca95a289c0a7981d3a1a42b3d..0e35500027ccd9baa06a0f4154fc5a8dae225912 100644 (file)
@@ -884,7 +884,7 @@ fn main() {
             .completions(
                 &ide::CompletionConfig {
                     enable_postfix_completions: true,
-                    enable_autoimport_completions: true,
+                    enable_imports_on_the_fly: true,
                     add_call_parenthesis: true,
                     add_call_argument_snippets: true,
                     snippet_cap: SnippetCap::new(true),