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,
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)
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,
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)
}
};
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 {
}
}
-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)
}
}
let name = match candidate {
ImportCandidate::Path(it) => &it.name,
ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name,
- };
+ }
+ .text();
GroupLabel(format!("Qualify {}", name))
}
//! 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;
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() {
}
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,
);
}
+ #[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(
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()
+ "#]],
+ );
+ }
}
#[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>,
pub struct ImportEdit {
pub import_path: ModPath,
pub import_scope: ImportScope,
+ pub import_for_trait_assoc_item: bool,
}
impl ImportEdit {
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);
}
}
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()?;
})
.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])
}
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;
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.
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),
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 {
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 {
Trait,
TypeAlias,
BuiltinType,
+ AssociatedItem,
}
/// A way to match import map contents against the search query.
query: String,
lowercased: String,
name_only: bool,
+ assoc_items_only: bool,
search_mode: SearchMode,
case_sensitive: bool,
limit: usize,
query,
lowercased,
name_only: false,
+ assoc_items_only: false,
search_mode: SearchMode::Contains,
case_sensitive: false,
limit: usize::max_value(),
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 }
}
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 {
);
}
+ #[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#"
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.
position: FilePosition,
full_import_path: &str,
imported_name: String,
+ import_for_trait_assoc_item: bool,
) -> Cancelable<Vec<TextEdit>> {
Ok(self
.with_db(|db| {
position,
full_import_path,
imported_name,
+ import_for_trait_assoc_item,
)
})?
.unwrap_or_default())
//! 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 {
#[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)
}
) -> 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)
}
};
- 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(),
}
}
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)?;
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,
} 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)
//! 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::{
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,
{
.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>(
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),
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,
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 {
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);
}
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| {
position: lsp_types::TextDocumentPositionParams,
full_import_path: String,
imported_name: String,
+ import_for_trait_assoc_item: bool,
}
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(),
);
.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),