From 6742f38e49d001359a7a9911becc0fcae4c67910 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 16 Jan 2021 19:33:36 +0200 Subject: Share import_assets and related entities --- crates/ide_db/src/helpers/import_assets.rs | 267 +++++++++++++++++++++++++++++ crates/ide_db/src/helpers/insert_use.rs | 6 + 2 files changed, 273 insertions(+) create mode 100644 crates/ide_db/src/helpers/import_assets.rs (limited to 'crates/ide_db/src/helpers') diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs new file mode 100644 index 000000000..edc3da318 --- /dev/null +++ b/crates/ide_db/src/helpers/import_assets.rs @@ -0,0 +1,267 @@ +//! Look up accessible paths for items. +use either::Either; +use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics}; +use rustc_hash::FxHashSet; +use syntax::{ast, AstNode, SyntaxNode}; + +use crate::{imports_locator, RootDatabase}; + +use super::insert_use::InsertUseConfig; + +#[derive(Debug)] +pub enum ImportCandidate { + // A path, qualified (`std::collections::HashMap`) or not (`HashMap`). + Path(PathImportCandidate), + /// A trait associated function (with no self parameter) or associated constant. + /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type + /// and `name` is the `test_function` + TraitAssocItem(TraitImportCandidate), + /// A trait method with self parameter. + /// For 'test_enum.test_method()', `ty` is the `test_enum` expression type + /// and `name` is the `test_method` + TraitMethod(TraitImportCandidate), +} + +#[derive(Debug)] +pub struct TraitImportCandidate { + pub ty: hir::Type, + pub name: ast::NameRef, +} + +#[derive(Debug)] +pub struct PathImportCandidate { + pub qualifier: Option, + pub name: ast::NameRef, +} + +#[derive(Debug)] +pub struct ImportAssets { + import_candidate: ImportCandidate, + module_with_name_to_import: hir::Module, + syntax_under_caret: SyntaxNode, +} + +impl ImportAssets { + pub fn for_method_call( + method_call: ast::MethodCallExpr, + sema: &Semantics, + ) -> Option { + 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, + }) + } + + pub fn for_regular_path( + path_under_caret: ast::Path, + sema: &Semantics, + ) -> Option { + let syntax_under_caret = path_under_caret.syntax().to_owned(); + 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, + }) + } + + pub fn syntax_under_caret(&self) -> &SyntaxNode { + &self.syntax_under_caret + } + + pub fn import_candidate(&self) -> &ImportCandidate { + &self.import_candidate + } + + fn get_search_query(&self) -> &str { + match &self.import_candidate { + ImportCandidate::Path(candidate) => candidate.name.text(), + ImportCandidate::TraitAssocItem(candidate) + | ImportCandidate::TraitMethod(candidate) => candidate.name.text(), + } + } + + pub fn search_for_imports( + &self, + sema: &Semantics, + config: &InsertUseConfig, + ) -> Vec<(hir::ModPath, hir::ItemInNs)> { + let _p = profile::span("import_assists::search_for_imports"); + self.search_for(sema, Some(config.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, + ) -> Vec<(hir::ModPath, hir::ItemInNs)> { + let _p = profile::span("import_assists::search_for_relative_paths"); + self.search_for(sema, None) + } + + fn search_for( + &self, + sema: &Semantics, + prefixed: Option, + ) -> 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 filter = |candidate: Either| { + 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) + } + _ => None, + } + .map(|assoc| assoc.container(db)) + .and_then(Self::assoc_to_trait)?; + + trait_candidates.insert(located_assoc_item.into()); + + trait_candidate + .ty + .iterate_path_candidates( + 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( + 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) + } + .map(|path| (path, item)) + }) + .filter(|(use_path, _)| use_path.len() > 1) + .take(20) + .collect::>(); + res.sort_by_key(|(path, _)| path.clone()); + res + } + + fn assoc_to_trait(assoc: AssocItemContainer) -> Option { + if let AssocItemContainer::Trait(extracted_trait) = assoc { + Some(extracted_trait) + } else { + None + } + } +} + +impl ImportCandidate { + fn for_method_call( + sema: &Semantics, + method_call: &ast::MethodCallExpr, + ) -> Option { + 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()?, + })), + } + } + + fn for_regular_path( + sema: &Semantics, + path_under_caret: &ast::Path, + ) -> Option { + if sema.resolve_path(path_under_caret).is_some() { + return None; + } + + let segment = path_under_caret.segment()?; + let candidate = if let Some(qualifier) = path_under_caret.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)?; + if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) { + let qualifier_resolution = if qualifier_start_path == qualifier { + qualifier_start_resolution + } else { + sema.resolve_path(&qualifier)? + }; + 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()?, + }) + } + _ => return None, + } + } else { + ImportCandidate::Path(PathImportCandidate { + qualifier: Some(qualifier), + name: qualifier_start, + }) + } + } else { + ImportCandidate::Path(PathImportCandidate { + qualifier: None, + name: segment.syntax().descendants().find_map(ast::NameRef::cast)?, + }) + }; + Some(candidate) + } +} diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs index d2f9f5d25..877d4f1c7 100644 --- a/crates/ide_db/src/helpers/insert_use.rs +++ b/crates/ide_db/src/helpers/insert_use.rs @@ -15,6 +15,12 @@ use syntax::{ }; use test_utils::mark; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct InsertUseConfig { + pub merge: Option, + pub prefix_kind: hir::PrefixKind, +} + #[derive(Debug, Clone)] pub enum ImportScope { File(ast::SourceFile), -- cgit v1.2.3