7297: Propose trait associated items and autoimport traits on completion r=matklad a=SomeoneToIgnore

![trait_imports](https://user-images.githubusercontent.com/2690773/104819998-6faeb480-583a-11eb-8b45-b7351b51b90e.gif)

Closes #7248

7338: Parse `impl const Trait` r=Veykril a=Veykril

Closes #7313

bors r+

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
bors[bot]
2021-01-18 19:40:11 +00:00
committed by GitHub
22 changed files with 885 additions and 288 deletions

4
Cargo.lock generated
View File

@@ -1904,9 +1904,9 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]] [[package]]
name = "ungrammar" name = "ungrammar"
version = "1.9.2" version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58a02e2041a872d56354e843e8e86e6b946fc8e7dc32982fcdc335e29eb4cc8b" checksum = "f5901372c0f3a6a1a9d880aef134c8eaf5e54409343637508c0a344270b42d7b"
[[package]] [[package]]
name = "unicase" name = "unicase"

View File

@@ -3,7 +3,7 @@ use ide_db::helpers::{
insert_use::{insert_use, ImportScope}, insert_use::{insert_use, ImportScope},
mod_path_to_ast, mod_path_to_ast,
}; };
use syntax::ast; use syntax::{ast, AstNode, SyntaxNode};
use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
@@ -82,25 +82,16 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
// # pub mod std { pub mod collections { pub struct HashMap { } } } // # pub mod std { pub mod collections { pub struct HashMap { } } }
// ``` // ```
pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let import_assets = let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() { let proposed_imports =
ImportAssets::for_regular_path(path_under_caret, &ctx.sema) import_assets.search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind);
} 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);
if proposed_imports.is_empty() { if proposed_imports.is_empty() {
return None; 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 group = import_group_message(import_assets.import_candidate());
let scope = let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?;
ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), &ctx.sema)?;
for (import, _) in proposed_imports { for (import, _) in proposed_imports {
acc.add_group( acc.add_group(
&group, &group,
@@ -117,14 +108,28 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
Some(()) 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 { fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel {
let name = match import_candidate { let name = match import_candidate {
ImportCandidate::Path(candidate) => format!("Import {}", &candidate.name), ImportCandidate::Path(candidate) => format!("Import {}", candidate.name.text()),
ImportCandidate::TraitAssocItem(candidate) => { ImportCandidate::TraitAssocItem(candidate) => {
format!("Import a trait for item {}", &candidate.name) format!("Import a trait for item {}", candidate.name.text())
} }
ImportCandidate::TraitMethod(candidate) => { ImportCandidate::TraitMethod(candidate) => {
format!("Import a trait for method {}", &candidate.name) format!("Import a trait for method {}", candidate.name.text())
} }
}; };
GroupLabel(name) GroupLabel(name)

View File

@@ -1,10 +1,7 @@
use std::iter; use std::iter;
use hir::AsName; use hir::AsName;
use ide_db::helpers::{ use ide_db::helpers::{import_assets::ImportCandidate, mod_path_to_ast};
import_assets::{ImportAssets, ImportCandidate},
mod_path_to_ast,
};
use ide_db::RootDatabase; use ide_db::RootDatabase;
use syntax::{ use syntax::{
ast, ast,
@@ -18,6 +15,8 @@ use crate::{
AssistId, AssistKind, GroupLabel, AssistId, AssistKind, GroupLabel,
}; };
use super::auto_import::find_importable_node;
// Assist: qualify_path // Assist: qualify_path
// //
// If the name is unresolved, provides all possible qualified paths for it. // If the name is unresolved, provides all possible qualified paths for it.
@@ -36,47 +35,38 @@ use crate::{
// # pub mod std { pub mod collections { pub struct HashMap { } } } // # pub mod std { pub mod collections { pub struct HashMap { } } }
// ``` // ```
pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let import_assets = let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
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_relative_paths(&ctx.sema); let proposed_imports = import_assets.search_for_relative_paths(&ctx.sema);
if proposed_imports.is_empty() { if proposed_imports.is_empty() {
return None; return None;
} }
let candidate = import_assets.import_candidate(); 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 { let qualify_candidate = match candidate {
ImportCandidate::Path(candidate) => { ImportCandidate::Path(candidate) => {
if candidate.qualifier.is_some() { if candidate.qualifier.is_some() {
mark::hit!(qualify_path_qualifier_start); 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()?); let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list()) QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list())
} else { } else {
mark::hit!(qualify_path_unqualified_name); 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(); let generics = path.segment()?.generic_arg_list();
QualifyCandidate::UnqualifiedName(generics) QualifyCandidate::UnqualifiedName(generics)
} }
} }
ImportCandidate::TraitAssocItem(_) => { ImportCandidate::TraitAssocItem(_) => {
mark::hit!(qualify_path_trait_assoc_item); 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()?); let (qualifier, segment) = (path.qualifier()?, path.segment()?);
QualifyCandidate::TraitAssocItem(qualifier, segment) QualifyCandidate::TraitAssocItem(qualifier, segment)
} }
ImportCandidate::TraitMethod(_) => { ImportCandidate::TraitMethod(_) => {
mark::hit!(qualify_path_trait_method); 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) QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr)
} }
}; };
@@ -140,7 +130,7 @@ impl QualifyCandidate<'_> {
let generics = let generics =
mcall_expr.generic_arg_list().as_ref().map_or_else(String::new, ToString::to_string); 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 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)?; let method = find_trait_method(db, trait_, &trait_method_name)?;
if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) { if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) {
let receiver = match self_access { let receiver = match self_access {
@@ -179,11 +169,13 @@ fn find_trait_method(
} }
} }
fn item_as_trait(item: hir::ItemInNs) -> Option<hir::Trait> { fn item_as_trait(db: &RootDatabase, item: hir::ItemInNs) -> Option<hir::Trait> {
if let hir::ModuleDef::Trait(trait_) = hir::ModuleDef::from(item.as_module_def_id()?) { let item_module_def = hir::ModuleDef::from(item.as_module_def_id()?);
if let hir::ModuleDef::Trait(trait_) = item_module_def {
Some(trait_) Some(trait_)
} else { } 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 { let name = match candidate {
ImportCandidate::Path(it) => &it.name, ImportCandidate::Path(it) => &it.name,
ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name, ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name,
}; }
.text();
GroupLabel(format!("Qualify {}", name)) GroupLabel(format!("Qualify {}", name))
} }

View File

@@ -20,11 +20,14 @@
//! # pub mod std { pub mod marker { pub struct PhantomData { } } } //! # pub mod std { pub mod marker { pub struct PhantomData { } } }
//! ``` //! ```
//! //!
//! Also completes associated items, that require trait imports.
//!
//! .Fuzzy search details //! .Fuzzy search details
//! //!
//! To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only //! 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). //! (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. //! For the same reasons, avoids searching for any path imports for inputs with their length less that 2 symbols
//! (but shows all associated items for any input length).
//! //!
//! .Import configuration //! .Import configuration
//! //!
@@ -45,10 +48,12 @@
//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding //! 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. //! capability enabled.
use either::Either;
use hir::{ModPath, ScopeDef}; use hir::{ModPath, ScopeDef};
use ide_db::{helpers::insert_use::ImportScope, imports_locator}; use ide_db::helpers::{
use syntax::AstNode; import_assets::{ImportAssets, ImportCandidate},
insert_use::ImportScope,
};
use syntax::{AstNode, SyntaxNode, T};
use test_utils::mark; use test_utils::mark;
use crate::{ use crate::{
@@ -60,58 +65,108 @@ use crate::{
use super::Completions; use super::Completions;
pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { 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; return None;
} }
if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() { if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() {
return None; return None;
} }
let potential_import_name = ctx.token.to_string(); let potential_import_name = {
if potential_import_name.len() < 2 { let token_kind = ctx.token.kind();
return None; if matches!(token_kind, T![.] | T![::]) {
} String::new()
} else {
ctx.token.to_string()
}
};
let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string()); 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 user_input_lowercased = potential_import_name.to_lowercase(); let user_input_lowercased = potential_import_name.to_lowercase();
let mut all_mod_paths = imports_locator::find_similar_imports( let import_assets = import_assets(ctx, potential_import_name)?;
let import_scope = ImportScope::find_insert_use_container(
position_for_import(ctx, Some(import_assets.import_candidate()))?,
&ctx.sema, &ctx.sema,
ctx.krate?, )?;
Some(40), let mut all_mod_paths = import_assets
potential_import_name, .search_for_relative_paths(&ctx.sema)
true, .into_iter()
true, .map(|(mod_path, item_in_ns)| {
) let scope_item = match item_in_ns {
.filter_map(|import_candidate| { hir::ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()),
Some(match import_candidate { hir::ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()),
Either::Left(module_def) => { hir::ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()),
(current_module.find_use_path(ctx.db, module_def)?, ScopeDef::ModuleDef(module_def)) };
} (mod_path, scope_item)
Either::Right(macro_def) => {
(current_module.find_use_path(ctx.db, macro_def)?, ScopeDef::MacroDef(macro_def))
}
}) })
}) .collect::<Vec<_>>();
.filter(|(mod_path, _)| mod_path.len() > 1)
.collect::<Vec<_>>();
all_mod_paths.sort_by_cached_key(|(mod_path, _)| { all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased) compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
}); });
acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| { acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
render_resolution_with_import( let import_for_trait_assoc_item = match definition {
RenderContext::new(ctx), ScopeDef::ModuleDef(module_def) => module_def
ImportEdit { import_path, import_scope: import_scope.clone() }, .as_assoc_item(ctx.db)
&definition, .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(()) Some(())
} }
pub(crate) fn position_for_import<'a>(
ctx: &'a CompletionContext,
import_candidate: Option<&ImportCandidate>,
) -> Option<&'a SyntaxNode> {
Some(match import_candidate {
Some(ImportCandidate::Path(_)) => ctx.name_ref_syntax.as_ref()?.syntax(),
Some(ImportCandidate::TraitAssocItem(_)) => ctx.path_qual.as_ref()?.syntax(),
Some(ImportCandidate::TraitMethod(_)) => ctx.dot_receiver.as_ref()?.syntax(),
None => ctx
.name_ref_syntax
.as_ref()
.map(|name_ref| name_ref.syntax())
.or_else(|| ctx.path_qual.as_ref().map(|path| path.syntax()))
.or_else(|| ctx.dot_receiver.as_ref().map(|expr| expr.syntax()))?,
})
}
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 {
let fuzzy_name_length = fuzzy_name.len();
let assets_for_path = ImportAssets::for_fuzzy_path(
current_module,
ctx.path_qual.clone(),
fuzzy_name,
&ctx.sema,
);
if matches!(assets_for_path.as_ref()?.import_candidate(), ImportCandidate::Path(_))
&& fuzzy_name_length < 2
{
mark::hit!(ignore_short_input_for_path);
None
} else {
assets_for_path
}
}
}
fn compute_fuzzy_completion_order_key( fn compute_fuzzy_completion_order_key(
proposed_mod_path: &ModPath, proposed_mod_path: &ModPath,
user_input_lowercased: &str, user_input_lowercased: &str,
@@ -223,6 +278,30 @@ fn main() {
); );
} }
#[test]
fn short_paths_are_ignored() {
mark::check!(ignore_short_input_for_path);
check(
r#"
//- /lib.rs crate:dep
pub struct FirstStruct;
pub mod some_module {
pub struct SecondStruct;
pub struct ThirdStruct;
}
//- /main.rs crate:main deps:dep
use dep::{FirstStruct, some_module::SecondStruct};
fn main() {
t$0
}
"#,
expect![[r#""#]],
);
}
#[test] #[test]
fn fuzzy_completions_come_in_specific_order() { fn fuzzy_completions_come_in_specific_order() {
mark::check!(certain_fuzzy_order_test); mark::check!(certain_fuzzy_order_test);
@@ -258,6 +337,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] #[test]
fn does_not_propose_names_in_scope() { fn does_not_propose_names_in_scope() {
check( check(
@@ -288,4 +537,129 @@ fn main() {
expect![[r#""#]], 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_edit(
"another_function",
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
}
"#,
r#"
use dep::test_mod::TestTrait;
fn main() {
dep::test_mod::TestStruct::another_function()$0
}
"#,
);
}
#[test]
fn zero_input_assoc_item_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() {
let test_struct = dep::test_mod::TestStruct {};
test_struct.$0
}
"#,
expect![[r#"
me random_method() (dep::test_mod::TestTrait) fn random_method(&self)
"#]],
);
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::$0
}
"#,
expect![[r#"
ct SPECIAL_CONST (dep::test_mod::TestTrait)
fn weird_function() (dep::test_mod::TestTrait) fn weird_function()
"#]],
);
}
} }

View File

@@ -9,7 +9,7 @@ use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct CompletionConfig { pub struct CompletionConfig {
pub enable_postfix_completions: bool, 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_parenthesis: bool,
pub add_call_argument_snippets: bool, pub add_call_argument_snippets: bool,
pub snippet_cap: Option<SnippetCap>, pub snippet_cap: Option<SnippetCap>,

View File

@@ -270,6 +270,7 @@ impl CompletionItem {
pub struct ImportEdit { pub struct ImportEdit {
pub import_path: ModPath, pub import_path: ModPath,
pub import_scope: ImportScope, pub import_scope: ImportScope,
pub import_for_trait_assoc_item: bool,
} }
impl ImportEdit { impl ImportEdit {
@@ -321,17 +322,19 @@ impl Builder {
let mut insert_text = self.insert_text; let mut insert_text = self.insert_text;
if let Some(import_to_add) = self.import_to_add.as_ref() { 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(); if import_to_add.import_for_trait_assoc_item {
let _ = import_path_without_last_segment.segments.pop(); 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() { if !import_path_without_last_segment.segments.is_empty() {
if lookup.is_none() { lookup = lookup.or_else(|| Some(label.clone()));
lookup = Some(label.clone()); insert_text = insert_text.or_else(|| Some(label.clone()));
label = format!("{}::{}", import_path_without_last_segment, label);
} }
if insert_text.is_none() {
insert_text = Some(label.clone());
}
label = format!("{}::{}", import_path_without_last_segment, label);
} }
} }

View File

@@ -11,10 +11,10 @@ mod render;
mod completions; mod completions;
use completions::flyimport::position_for_import;
use ide_db::{ use ide_db::{
base_db::FilePosition, helpers::insert_use::ImportScope, imports_locator, RootDatabase, base_db::FilePosition, helpers::insert_use::ImportScope, imports_locator, RootDatabase,
}; };
use syntax::AstNode;
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{completions::Completions, context::CompletionContext, item::CompletionKind}; use crate::{completions::Completions, context::CompletionContext, item::CompletionKind};
@@ -139,12 +139,13 @@ pub fn resolve_completion_edits(
position: FilePosition, position: FilePosition,
full_import_path: &str, full_import_path: &str,
imported_name: String, imported_name: String,
import_for_trait_assoc_item: bool,
) -> Option<Vec<TextEdit>> { ) -> Option<Vec<TextEdit>> {
let ctx = CompletionContext::new(db, position, config)?; let ctx = CompletionContext::new(db, position, config)?;
let anchor = ctx.name_ref_syntax.as_ref()?; let position_for_import = position_for_import(&ctx, None)?;
let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; let import_scope = ImportScope::find_insert_use_container(position_for_import, &ctx.sema)?;
let current_module = ctx.sema.scope(anchor.syntax()).module()?; let current_module = ctx.sema.scope(position_for_import).module()?;
let current_crate = current_module.krate(); let current_crate = current_module.krate();
let import_path = imports_locator::find_exact_imports(&ctx.sema, current_crate, imported_name) let import_path = imports_locator::find_exact_imports(&ctx.sema, current_crate, imported_name)
@@ -154,7 +155,7 @@ pub fn resolve_completion_edits(
}) })
.find(|mod_path| mod_path.to_string() == full_import_path)?; .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) .to_text_edit(config.insert_use.merge)
.map(|edit| vec![edit]) .map(|edit| vec![edit])
} }

View File

@@ -10,7 +10,7 @@ pub(crate) mod type_alias;
mod builder_ext; 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 ide_db::{helpers::SnippetCap, RootDatabase};
use syntax::TextRange; use syntax::TextRange;
use test_utils::mark; use test_utils::mark;
@@ -51,16 +51,16 @@ pub(crate) fn render_resolution_with_import<'a>(
import_edit: ImportEdit, import_edit: ImportEdit,
resolution: &ScopeDef, resolution: &ScopeDef,
) -> Option<CompletionItem> { ) -> Option<CompletionItem> {
Render::new(ctx) let local_name = match resolution {
.render_resolution( ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(),
import_edit.import_path.segments.last()?.to_string(), ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(),
Some(import_edit), ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(),
resolution, _ => import_edit.import_path.segments.last()?.to_string(),
) };
.map(|mut item| { Render::new(ctx).render_resolution(local_name, Some(import_edit), resolution).map(|mut item| {
item.completion_kind = CompletionKind::Magic; item.completion_kind = CompletionKind::Magic;
item item
}) })
} }
/// Interface for data and methods required for items rendering. /// Interface for data and methods required for items rendering.

View File

@@ -18,7 +18,7 @@ use crate::{item::CompletionKind, CompletionConfig, CompletionItem};
pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
enable_postfix_completions: true, enable_postfix_completions: true,
enable_autoimport_completions: true, enable_imports_on_the_fly: true,
add_call_parenthesis: true, add_call_parenthesis: true,
add_call_argument_snippets: true, add_call_argument_snippets: true,
snippet_cap: SnippetCap::new(true), snippet_cap: SnippetCap::new(true),

View File

@@ -272,6 +272,15 @@ impl ModuleDef {
hir_ty::diagnostics::validate_module_item(db, module.id.krate, id, sink) 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 { impl Module {
@@ -1091,6 +1100,13 @@ impl AssocItem {
AssocContainerId::ContainerId(_) => panic!("invalid AssocItem"), 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 { impl HasVisibility for AssocItem {

View File

@@ -263,6 +263,7 @@ pub enum ImportKind {
Trait, Trait,
TypeAlias, TypeAlias,
BuiltinType, BuiltinType,
AssociatedItem,
} }
/// A way to match import map contents against the search query. /// A way to match import map contents against the search query.
@@ -282,6 +283,7 @@ pub struct Query {
query: String, query: String,
lowercased: String, lowercased: String,
name_only: bool, name_only: bool,
assoc_items_only: bool,
search_mode: SearchMode, search_mode: SearchMode,
case_sensitive: bool, case_sensitive: bool,
limit: usize, limit: usize,
@@ -295,6 +297,7 @@ impl Query {
query, query,
lowercased, lowercased,
name_only: false, name_only: false,
assoc_items_only: false,
search_mode: SearchMode::Contains, search_mode: SearchMode::Contains,
case_sensitive: false, case_sensitive: false,
limit: usize::max_value(), limit: usize::max_value(),
@@ -309,6 +312,11 @@ impl Query {
Self { name_only: true, ..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. /// Specifies the way to search for the entries using the query.
pub fn search_mode(self, search_mode: SearchMode) -> Self { pub fn search_mode(self, search_mode: SearchMode) -> Self {
Self { search_mode, ..self } Self { search_mode, ..self }
@@ -331,6 +339,14 @@ impl Query {
} }
fn import_matches(&self, import: &ImportInfo, enforce_lowercase: bool) -> bool { 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 { let mut input = if import.is_trait_assoc_item || self.name_only {
import.path.segments.last().unwrap().to_string() import.path.segments.last().unwrap().to_string()
} else { } else {
@@ -813,6 +829,56 @@ mod tests {
); );
} }
#[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] #[test]
fn search_mode() { fn search_mode() {
let ra_fixture = r#" let ra_fixture = r#"

View 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::Field(field) => format!("#structfield.{}", field.name(db)),
FieldOrAssocItem::AssocItem(assoc) => match assoc { FieldOrAssocItem::AssocItem(assoc) => match assoc {
AssocItem::Function(function) => { AssocItem::Function(function) => {
let is_trait_method = matches!( let is_trait_method = function
function.as_assoc_item(db).map(|assoc| assoc.container(db)), .as_assoc_item(db)
Some(AssocItemContainer::Trait(..)) .and_then(|assoc| assoc.containing_trait(db))
); .is_some();
// This distinction may get more complicated when specialization is available. // This distinction may get more complicated when specialization is available.
// Rustdoc makes this decision based on whether a method 'has defaultness'. // Rustdoc makes this decision based on whether a method 'has defaultness'.
// Currently this is only the case for provided trait methods. // Currently this is only the case for provided trait methods.

View File

@@ -478,6 +478,7 @@ impl Analysis {
position: FilePosition, position: FilePosition,
full_import_path: &str, full_import_path: &str,
imported_name: String, imported_name: String,
import_for_trait_assoc_item: bool,
) -> Cancelable<Vec<TextEdit>> { ) -> Cancelable<Vec<TextEdit>> {
Ok(self Ok(self
.with_db(|db| { .with_db(|db| {
@@ -487,6 +488,7 @@ impl Analysis {
position, position,
full_import_path, full_import_path,
imported_name, imported_name,
import_for_trait_assoc_item,
) )
})? })?
.unwrap_or_default()) .unwrap_or_default())

View File

@@ -1,12 +1,13 @@
//! Look up accessible paths for items. //! Look up accessible paths for items.
use either::Either; use either::Either;
use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics}; use hir::{AsAssocItem, AssocItem, Crate, MacroDef, Module, ModuleDef, PrefixKind, Semantics};
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use syntax::{ast, AstNode, SyntaxNode}; use syntax::{ast, AstNode};
use crate::{imports_locator, RootDatabase}; use crate::{
imports_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT},
use super::insert_use::InsertUseConfig; RootDatabase,
};
#[derive(Debug)] #[derive(Debug)]
pub enum ImportCandidate { pub enum ImportCandidate {
@@ -24,86 +25,141 @@ pub enum ImportCandidate {
#[derive(Debug)] #[derive(Debug)]
pub struct TraitImportCandidate { pub struct TraitImportCandidate {
pub ty: hir::Type, pub receiver_ty: hir::Type,
pub name: ast::NameRef, pub name: NameToImport,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct PathImportCandidate { pub struct PathImportCandidate {
pub qualifier: Option<ast::Path>, 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)] #[derive(Debug)]
pub struct ImportAssets { pub struct ImportAssets {
import_candidate: ImportCandidate, import_candidate: ImportCandidate,
module_with_name_to_import: hir::Module, module_with_candidate: hir::Module,
syntax_under_caret: SyntaxNode,
} }
impl ImportAssets { impl ImportAssets {
pub fn for_method_call( pub fn for_method_call(
method_call: ast::MethodCallExpr, method_call: &ast::MethodCallExpr,
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
) -> Option<Self> { ) -> 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 { Some(Self {
import_candidate: ImportCandidate::for_method_call(sema, &method_call)?, import_candidate: ImportCandidate::for_method_call(sema, method_call)?,
module_with_name_to_import, module_with_candidate: sema.scope(method_call.syntax()).module()?,
syntax_under_caret,
}) })
} }
pub fn for_regular_path( pub fn for_exact_path(
path_under_caret: ast::Path, fully_qualified_path: &ast::Path,
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
) -> Option<Self> { ) -> 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() { if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
return None; return None;
} }
let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
Some(Self { Some(Self {
import_candidate: ImportCandidate::for_regular_path(sema, &path_under_caret)?, import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?,
module_with_name_to_import, module_with_candidate: sema.scope(syntax_under_caret).module()?,
syntax_under_caret,
}) })
} }
pub fn syntax_under_caret(&self) -> &SyntaxNode { pub fn for_fuzzy_path(
&self.syntax_under_caret 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 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 { pub fn import_candidate(&self) -> &ImportCandidate {
&self.import_candidate &self.import_candidate
} }
fn get_search_query(&self) -> &str { fn name_to_import(&self) -> &NameToImport {
match &self.import_candidate { match &self.import_candidate {
ImportCandidate::Path(candidate) => candidate.name.text(), ImportCandidate::Path(candidate) => &candidate.name,
ImportCandidate::TraitAssocItem(candidate) ImportCandidate::TraitAssocItem(candidate)
| ImportCandidate::TraitMethod(candidate) => candidate.name.text(), | ImportCandidate::TraitMethod(candidate) => &candidate.name,
} }
} }
pub fn search_for_imports( pub fn search_for_imports(
&self, &self,
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
config: &InsertUseConfig, prefix_kind: PrefixKind,
) -> Vec<(hir::ModPath, hir::ItemInNs)> { ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
let _p = profile::span("import_assists::search_for_imports"); let _p = profile::span("import_assets::search_for_imports");
self.search_for(sema, Some(config.prefix_kind)) 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. /// 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( pub fn search_for_relative_paths(
&self, &self,
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
) -> Vec<(hir::ModPath, hir::ItemInNs)> { ) -> 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) self.search_for(sema, None)
} }
@@ -112,99 +168,142 @@ impl ImportAssets {
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
prefixed: Option<hir::PrefixKind>, prefixed: Option<hir::PrefixKind>,
) -> Vec<(hir::ModPath, hir::ItemInNs)> { ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
let db = sema.db; let current_crate = self.module_with_candidate.krate();
let mut trait_candidates = FxHashSet::default();
let current_crate = self.module_with_name_to_import.krate();
let filter = |candidate: Either<hir::ModuleDef, hir::MacroDef>| { let unfiltered_imports = match self.name_to_import() {
trait_candidates.clear(); NameToImport::Exact(exact_name) => {
match &self.import_candidate { imports_locator::find_exact_imports(sema, current_crate, exact_name.clone())
ImportCandidate::TraitAssocItem(trait_candidate) => { }
let located_assoc_item = match candidate { // FIXME: ideally, we should avoid using `fst` for seacrhing trait imports for assoc items:
Either::Left(ModuleDef::Function(located_function)) => { // instead, we need to look up all trait impls for a certain struct and search through them only
located_function.as_assoc_item(db) // 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
Either::Left(ModuleDef::Const(located_const)) => { // for the details
located_const.as_assoc_item(db) NameToImport::Fuzzy(fuzzy_name) => {
} let (assoc_item_search, limit) = match self.import_candidate {
_ => None, ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => {
(AssocItemSearch::AssocItemsOnly, None)
} }
.map(|assoc| assoc.container(db)) _ => (AssocItemSearch::Exclude, Some(DEFAULT_QUERY_SEARCH_LIMIT)),
.and_then(Self::assoc_to_trait)?; };
imports_locator::find_similar_imports(
trait_candidates.insert(located_assoc_item.into()); sema,
current_crate,
trait_candidate fuzzy_name.clone(),
.ty assoc_item_search,
.iterate_path_candidates( limit,
db, )
current_crate,
&trait_candidates,
None,
|_, assoc| Self::assoc_to_trait(assoc.container(db)),
)
.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());
trait_candidate
.ty
.iterate_method_candidates(
db,
current_crate,
&trait_candidates,
None,
|_, function| {
Self::assoc_to_trait(function.as_assoc_item(db)?.container(db))
},
)
.map(ModuleDef::from)
.map(Either::Left)
}
_ => Some(candidate),
} }
}; };
let mut res = imports_locator::find_exact_imports( let db = sema.db;
sema, let mut res =
current_crate, applicable_defs(self.import_candidate(), current_crate, db, unfiltered_imports)
self.get_search_query().to_string(), .filter_map(|candidate| {
) let item: hir::ItemInNs = candidate.clone().either(Into::into, Into::into);
.filter_map(filter)
.filter_map(|candidate| { let item_to_search = match self.import_candidate {
let item: hir::ItemInNs = candidate.either(Into::into, Into::into); ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => {
if let Some(prefix_kind) = prefixed { let canidate_trait = match candidate {
self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind) Either::Left(module_def) => {
} else { module_def.as_assoc_item(db)?.containing_trait(db)
self.module_with_name_to_import.find_use_path(db, item) }
} _ => None,
.map(|path| (path, item)) }?;
}) ModuleDef::from(canidate_trait).into()
.filter(|(use_path, _)| use_path.len() > 1) }
.take(20) _ => item,
.collect::<Vec<_>>(); };
res.sort_by_key(|(path, _)| path.clone());
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 res
} }
}
fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> { fn applicable_defs<'a>(
if let AssocItemContainer::Trait(extracted_trait) = assoc { import_candidate: &ImportCandidate,
Some(extracted_trait) current_crate: Crate,
} else { db: &RootDatabase,
None unfiltered_imports: Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a>,
) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a> {
let receiver_ty = match import_candidate {
ImportCandidate::Path(_) => return unfiltered_imports,
ImportCandidate::TraitAssocItem(candidate) | ImportCandidate::TraitMethod(candidate) => {
&candidate.receiver_ty
} }
};
let mut required_assoc_items = FxHashSet::default();
let trait_candidates = unfiltered_imports
.filter_map(|input| match input {
Either::Left(module_def) => module_def.as_assoc_item(db),
_ => None,
})
.filter_map(|assoc| {
let assoc_item_trait = assoc.containing_trait(db)?;
required_assoc_items.insert(assoc);
Some(assoc_item_trait.into())
})
.collect();
let mut applicable_defs = FxHashSet::default();
match import_candidate {
ImportCandidate::Path(_) => unreachable!(),
ImportCandidate::TraitAssocItem(_) => receiver_ty.iterate_path_candidates(
db,
current_crate,
&trait_candidates,
None,
|_, assoc| {
if required_assoc_items.contains(&assoc) {
if let AssocItem::Function(f) = assoc {
if f.self_param(db).is_some() {
return None;
}
}
applicable_defs.insert(Either::Left(assoc_to_module_def(assoc)));
}
None::<()>
},
),
ImportCandidate::TraitMethod(_) => receiver_ty.iterate_method_candidates(
db,
current_crate,
&trait_candidates,
None,
|_, function| {
let assoc = function.as_assoc_item(db)?;
if required_assoc_items.contains(&assoc) {
applicable_defs.insert(Either::Left(assoc_to_module_def(assoc)));
}
None::<()>
},
),
};
Box::new(applicable_defs.into_iter())
}
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 +315,19 @@ impl ImportCandidate {
match sema.resolve_method_call(method_call) { match sema.resolve_method_call(method_call) {
Some(_) => None, Some(_) => None,
None => Some(Self::TraitMethod(TraitImportCandidate { None => Some(Self::TraitMethod(TraitImportCandidate {
ty: sema.type_of_expr(&method_call.receiver()?)?, receiver_ty: sema.type_of_expr(&method_call.receiver()?)?,
name: method_call.name_ref()?, name: NameToImport::Exact(method_call.name_ref()?.to_string()),
})), })),
} }
} }
fn for_regular_path( fn for_regular_path(sema: &Semantics<RootDatabase>, path: &ast::Path) -> Option<Self> {
sema: &Semantics<RootDatabase>, if sema.resolve_path(path).is_some() {
path_under_caret: &ast::Path,
) -> Option<Self> {
if sema.resolve_path(path_under_caret).is_some() {
return None; return None;
} }
let segment = path_under_caret.segment()?; let segment = path.segment()?;
let candidate = if let Some(qualifier) = path_under_caret.qualifier() { let candidate = if let Some(qualifier) = path.qualifier() {
let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?; let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
let qualifier_start_path = let qualifier_start_path =
qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?; qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
@@ -244,8 +340,8 @@ impl ImportCandidate {
match qualifier_resolution { match qualifier_resolution {
hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => { hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
ImportCandidate::TraitAssocItem(TraitImportCandidate { ImportCandidate::TraitAssocItem(TraitImportCandidate {
ty: assoc_item_path.ty(sema.db), receiver_ty: assoc_item_path.ty(sema.db),
name: segment.name_ref()?, name: NameToImport::Exact(segment.name_ref()?.to_string()),
}) })
} }
_ => return None, _ => return None,
@@ -253,13 +349,15 @@ impl ImportCandidate {
} else { } else {
ImportCandidate::Path(PathImportCandidate { ImportCandidate::Path(PathImportCandidate {
qualifier: Some(qualifier), qualifier: Some(qualifier),
name: qualifier_start, name: NameToImport::Exact(qualifier_start.to_string()),
}) })
} }
} else { } else {
ImportCandidate::Path(PathImportCandidate { ImportCandidate::Path(PathImportCandidate {
qualifier: None, 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) Some(candidate)

View File

@@ -1,7 +1,10 @@
//! This module contains an import search functionality that is provided to the assists module. //! 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. //! 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 syntax::{ast, AstNode, SyntaxKind::NAME};
use crate::{ use crate::{
@@ -12,69 +15,84 @@ use crate::{
use either::Either; use either::Either;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
const QUERY_SEARCH_LIMIT: usize = 40; pub(crate) const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40;
pub fn find_exact_imports<'a>( pub fn find_exact_imports<'a>(
sema: &Semantics<'a, RootDatabase>, sema: &Semantics<'a, RootDatabase>,
krate: Crate, krate: Crate,
name_to_import: String, name_to_import: String,
) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> { ) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>>> {
let _p = profile::span("find_exact_imports"); let _p = profile::span("find_exact_imports");
find_imports( Box::new(find_imports(
sema, sema,
krate, krate,
{ {
let mut local_query = symbol_index::Query::new(name_to_import.clone()); let mut local_query = symbol_index::Query::new(name_to_import.clone());
local_query.exact(); local_query.exact();
local_query.limit(QUERY_SEARCH_LIMIT); local_query.limit(DEFAULT_QUERY_SEARCH_LIMIT);
local_query local_query
}, },
import_map::Query::new(name_to_import) import_map::Query::new(name_to_import)
.limit(QUERY_SEARCH_LIMIT) .limit(DEFAULT_QUERY_SEARCH_LIMIT)
.name_only() .name_only()
.search_mode(import_map::SearchMode::Equals) .search_mode(import_map::SearchMode::Equals)
.case_sensitive(), .case_sensitive(),
) ))
}
pub enum AssocItemSearch {
Include,
Exclude,
AssocItemsOnly,
} }
pub fn find_similar_imports<'a>( pub fn find_similar_imports<'a>(
sema: &Semantics<'a, RootDatabase>, sema: &Semantics<'a, RootDatabase>,
krate: Crate, krate: Crate,
limit: Option<usize>,
fuzzy_search_string: String, fuzzy_search_string: String,
ignore_assoc_items: bool, assoc_item_search: AssocItemSearch,
name_only: bool, limit: Option<usize>,
) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> + 'a { ) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a> {
let _p = profile::span("find_similar_imports"); let _p = profile::span("find_similar_imports");
let mut external_query = import_map::Query::new(fuzzy_search_string.clone()) let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
.search_mode(import_map::SearchMode::Fuzzy); .search_mode(import_map::SearchMode::Fuzzy)
if name_only { .name_only();
external_query = external_query.name_only();
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); let mut local_query = symbol_index::Query::new(fuzzy_search_string);
if let Some(limit) = limit { if let Some(limit) = limit {
local_query.limit(limit);
external_query = external_query.limit(limit); external_query = external_query.limit(limit);
local_query.limit(limit);
} }
let db = sema.db; let db = sema.db;
find_imports(sema, krate, local_query, external_query).filter(move |import_candidate| { Box::new(find_imports(sema, krate, local_query, external_query).filter(
if ignore_assoc_items { move |import_candidate| match assoc_item_search {
match import_candidate { AssocItemSearch::Include => true,
Either::Left(ModuleDef::Function(function)) => function.as_assoc_item(db).is_none(), AssocItemSearch::Exclude => !is_assoc_item(import_candidate, db),
Either::Left(ModuleDef::Const(const_)) => const_.as_assoc_item(db).is_none(), AssocItemSearch::AssocItemsOnly => is_assoc_item(import_candidate, db),
Either::Left(ModuleDef::TypeAlias(type_alias)) => { },
type_alias.as_assoc_item(db).is_none() ))
} }
_ => true,
} fn is_assoc_item(import_candidate: &Either<ModuleDef, MacroDef>, db: &RootDatabase) -> bool {
} else { match import_candidate {
true 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>( fn find_imports<'a>(

View File

@@ -40,6 +40,10 @@ pub(super) fn impl_(p: &mut Parser) {
type_params::opt_generic_param_list(p); type_params::opt_generic_param_list(p);
} }
// test impl_def_const
// impl const Send for X {}
p.eat(T![const]);
// FIXME: never type // FIXME: never type
// impl ! {} // impl ! {}

View File

@@ -93,7 +93,7 @@ impl BenchCmd {
if is_completion { if is_completion {
let options = CompletionConfig { let options = CompletionConfig {
enable_postfix_completions: true, enable_postfix_completions: true,
enable_autoimport_completions: true, enable_imports_on_the_fly: true,
add_call_parenthesis: true, add_call_parenthesis: true,
add_call_argument_snippets: true, add_call_argument_snippets: true,
snippet_cap: SnippetCap::new(true), snippet_cap: SnippetCap::new(true),

View File

@@ -559,7 +559,7 @@ impl Config {
pub fn completion(&self) -> CompletionConfig { pub fn completion(&self) -> CompletionConfig {
CompletionConfig { CompletionConfig {
enable_postfix_completions: self.data.completion_postfix_enable, 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), && completion_item_edit_resolve(&self.caps),
add_call_parenthesis: self.data.completion_addCallParenthesis, add_call_parenthesis: self.data.completion_addCallParenthesis,
add_call_argument_snippets: self.data.completion_addCallArgumentSnippets, add_call_argument_snippets: self.data.completion_addCallArgumentSnippets,
@@ -581,18 +581,7 @@ impl Config {
AssistConfig { AssistConfig {
snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")), snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
allowed: None, allowed: None,
insert_use: InsertUseConfig { insert_use: self.insert_use_config(),
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,
},
},
} }
} }
pub fn call_info_full(&self) -> bool { pub fn call_info_full(&self) -> bool {

View File

@@ -653,7 +653,7 @@ pub(crate) fn handle_completion(
let mut new_completion_items = let mut new_completion_items =
to_proto::completion_item(&line_index, line_endings, item.clone()); 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 { for new_item in &mut new_completion_items {
fill_resolve_data(&mut new_item.data, &item, &text_document_position); 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 }, FilePosition { file_id, offset },
&resolve_data.full_import_path, &resolve_data.full_import_path,
resolve_data.imported_name, resolve_data.imported_name,
resolve_data.import_for_trait_assoc_item,
)? )?
.into_iter() .into_iter()
.flat_map(|edit| { .flat_map(|edit| {
@@ -1694,6 +1695,7 @@ struct CompletionResolveData {
position: lsp_types::TextDocumentPositionParams, position: lsp_types::TextDocumentPositionParams,
full_import_path: String, full_import_path: String,
imported_name: String, imported_name: String,
import_for_trait_assoc_item: bool,
} }
fn fill_resolve_data( fn fill_resolve_data(
@@ -1710,6 +1712,7 @@ fn fill_resolve_data(
position: position.to_owned(), position: position.to_owned(),
full_import_path, full_import_path,
imported_name, imported_name,
import_for_trait_assoc_item: import_edit.import_for_trait_assoc_item,
}) })
.unwrap(), .unwrap(),
); );

View File

@@ -884,7 +884,7 @@ mod tests {
.completions( .completions(
&ide::CompletionConfig { &ide::CompletionConfig {
enable_postfix_completions: true, enable_postfix_completions: true,
enable_autoimport_completions: true, enable_imports_on_the_fly: true,
add_call_parenthesis: true, add_call_parenthesis: true,
add_call_argument_snippets: true, add_call_argument_snippets: true,
snippet_cap: SnippetCap::new(true), snippet_cap: SnippetCap::new(true),

View File

@@ -0,0 +1,24 @@
SOURCE_FILE@0..25
IMPL@0..24
IMPL_KW@0..4 "impl"
WHITESPACE@4..5 " "
CONST_KW@5..10 "const"
WHITESPACE@10..11 " "
PATH_TYPE@11..15
PATH@11..15
PATH_SEGMENT@11..15
NAME_REF@11..15
IDENT@11..15 "Send"
WHITESPACE@15..16 " "
FOR_KW@16..19 "for"
WHITESPACE@19..20 " "
PATH_TYPE@20..21
PATH@20..21
PATH_SEGMENT@20..21
NAME_REF@20..21
IDENT@20..21 "X"
WHITESPACE@21..22 " "
ASSOC_ITEM_LIST@22..24
L_CURLY@22..23 "{"
R_CURLY@23..24 "}"
WHITESPACE@24..25 "\n"

View File

@@ -0,0 +1 @@
impl const Send for X {}