mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-25 12:33:33 +00:00
7873: Consider unresolved qualifiers during flyimport r=matklad a=SomeoneToIgnore Closes https://github.com/rust-analyzer/rust-analyzer/issues/7679 Takes unresolved qualifiers into account, providing better completions (or none, if the path is resolved or do not match). Does not handle cases when both path qualifier and some trait has to be imported: there are many extra issues with those (such as overlapping imports, for instance) that will require large diffs to address. Also does not do a fuzzy search on qualifier, that requires some adjustments in `import_map` for better queries and changes to the default replace range which also seems relatively big to include here. ![qualifier_completion](https://user-images.githubusercontent.com/2690773/110040808-0af8dc00-7d4c-11eb-83db-65af94e843bb.gif) 7933: Improve compilation speed r=matklad a=matklad bors r+ 🤖 Co-authored-by: Kirill Bulatov <mail4score@gmail.com> Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
21913d0fdb
18 changed files with 848 additions and 381 deletions
|
@ -1114,6 +1114,7 @@ pub enum AssocItem {
|
||||||
Const(Const),
|
Const(Const),
|
||||||
TypeAlias(TypeAlias),
|
TypeAlias(TypeAlias),
|
||||||
}
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum AssocItemContainer {
|
pub enum AssocItemContainer {
|
||||||
Trait(Trait),
|
Trait(Trait),
|
||||||
Impl(Impl),
|
Impl(Impl),
|
||||||
|
@ -2136,6 +2137,16 @@ impl ScopeDef {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ItemInNs> for ScopeDef {
|
||||||
|
fn from(item: ItemInNs) -> Self {
|
||||||
|
match item {
|
||||||
|
ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()),
|
||||||
|
ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()),
|
||||||
|
ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait HasVisibility {
|
pub trait HasVisibility {
|
||||||
fn visibility(&self, db: &dyn HirDatabase) -> Visibility;
|
fn visibility(&self, db: &dyn HirDatabase) -> Visibility;
|
||||||
fn is_visible_from(&self, db: &dyn HirDatabase, module: Module) -> bool {
|
fn is_visible_from(&self, db: &dyn HirDatabase, module: Module) -> bool {
|
||||||
|
|
|
@ -478,7 +478,6 @@ 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| {
|
||||||
|
@ -488,7 +487,6 @@ 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())
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use ide_db::helpers::{
|
use ide_db::helpers::{
|
||||||
import_assets::{ImportAssets, ImportCandidate},
|
import_assets::{ImportAssets, ImportCandidate},
|
||||||
insert_use::{insert_use, ImportScope},
|
insert_use::{insert_use, ImportScope},
|
||||||
mod_path_to_ast,
|
item_name, mod_path_to_ast,
|
||||||
};
|
};
|
||||||
use syntax::{ast, AstNode, SyntaxNode};
|
use syntax::{ast, AstNode, SyntaxNode};
|
||||||
|
|
||||||
|
@ -92,14 +92,19 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
|
||||||
let range = ctx.sema.original_range(&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 = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?;
|
let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?;
|
||||||
for (import, _) in proposed_imports {
|
for import in proposed_imports {
|
||||||
|
let name = match item_name(ctx.db(), import.original_item) {
|
||||||
|
Some(name) => name,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
acc.add_group(
|
acc.add_group(
|
||||||
&group,
|
&group,
|
||||||
AssistId("auto_import", AssistKind::QuickFix),
|
AssistId("auto_import", AssistKind::QuickFix),
|
||||||
format!("Import `{}`", &import),
|
format!("Import `{}`", name),
|
||||||
range,
|
range,
|
||||||
|builder| {
|
|builder| {
|
||||||
let rewriter = insert_use(&scope, mod_path_to_ast(&import), ctx.config.insert_use);
|
let rewriter =
|
||||||
|
insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use);
|
||||||
builder.rewrite(rewriter);
|
builder.rewrite(rewriter);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -125,10 +130,10 @@ 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.text()),
|
ImportCandidate::Path(candidate) => format!("Import {}", candidate.name.text()),
|
||||||
ImportCandidate::TraitAssocItem(candidate) => {
|
ImportCandidate::TraitAssocItem(candidate) => {
|
||||||
format!("Import a trait for item {}", candidate.name.text())
|
format!("Import a trait for item {}", candidate.assoc_item_name.text())
|
||||||
}
|
}
|
||||||
ImportCandidate::TraitMethod(candidate) => {
|
ImportCandidate::TraitMethod(candidate) => {
|
||||||
format!("Import a trait for method {}", candidate.name.text())
|
format!("Import a trait for method {}", candidate.assoc_item_name.text())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
GroupLabel(name)
|
GroupLabel(name)
|
||||||
|
@ -220,41 +225,6 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn auto_imports_are_merged() {
|
|
||||||
check_assist(
|
|
||||||
auto_import,
|
|
||||||
r"
|
|
||||||
use PubMod::PubStruct1;
|
|
||||||
|
|
||||||
struct Test {
|
|
||||||
test: Pub$0Struct2<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod PubMod {
|
|
||||||
pub struct PubStruct1;
|
|
||||||
pub struct PubStruct2<T> {
|
|
||||||
_t: T,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
",
|
|
||||||
r"
|
|
||||||
use PubMod::{PubStruct1, PubStruct2};
|
|
||||||
|
|
||||||
struct Test {
|
|
||||||
test: PubStruct2<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod PubMod {
|
|
||||||
pub struct PubStruct1;
|
|
||||||
pub struct PubStruct2<T> {
|
|
||||||
_t: T,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn applicable_when_found_multiple_imports() {
|
fn applicable_when_found_multiple_imports() {
|
||||||
check_assist(
|
check_assist(
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
use hir::AsAssocItem;
|
use hir::AsAssocItem;
|
||||||
use ide_db::helpers::{import_assets::ImportCandidate, mod_path_to_ast};
|
use ide_db::helpers::{
|
||||||
|
import_assets::{ImportCandidate, LocatedImport},
|
||||||
|
item_name, mod_path_to_ast,
|
||||||
|
};
|
||||||
use ide_db::RootDatabase;
|
use ide_db::RootDatabase;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast,
|
ast,
|
||||||
|
@ -71,17 +74,17 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
|
||||||
};
|
};
|
||||||
|
|
||||||
let group_label = group_label(candidate);
|
let group_label = group_label(candidate);
|
||||||
for (import, item) in proposed_imports {
|
for import in proposed_imports {
|
||||||
acc.add_group(
|
acc.add_group(
|
||||||
&group_label,
|
&group_label,
|
||||||
AssistId("qualify_path", AssistKind::QuickFix),
|
AssistId("qualify_path", AssistKind::QuickFix),
|
||||||
label(candidate, &import),
|
label(ctx.db(), candidate, &import),
|
||||||
range,
|
range,
|
||||||
|builder| {
|
|builder| {
|
||||||
qualify_candidate.qualify(
|
qualify_candidate.qualify(
|
||||||
|replace_with: String| builder.replace(range, replace_with),
|
|replace_with: String| builder.replace(range, replace_with),
|
||||||
import,
|
&import.import_path,
|
||||||
item,
|
import.item_to_import,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -97,8 +100,13 @@ enum QualifyCandidate<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QualifyCandidate<'_> {
|
impl QualifyCandidate<'_> {
|
||||||
fn qualify(&self, mut replacer: impl FnMut(String), import: hir::ModPath, item: hir::ItemInNs) {
|
fn qualify(
|
||||||
let import = mod_path_to_ast(&import);
|
&self,
|
||||||
|
mut replacer: impl FnMut(String),
|
||||||
|
import: &hir::ModPath,
|
||||||
|
item: hir::ItemInNs,
|
||||||
|
) {
|
||||||
|
let import = mod_path_to_ast(import);
|
||||||
match self {
|
match self {
|
||||||
QualifyCandidate::QualifierStart(segment, generics) => {
|
QualifyCandidate::QualifierStart(segment, generics) => {
|
||||||
let generics = generics.as_ref().map_or_else(String::new, ToString::to_string);
|
let generics = generics.as_ref().map_or_else(String::new, ToString::to_string);
|
||||||
|
@ -183,23 +191,29 @@ fn item_as_trait(db: &RootDatabase, item: hir::ItemInNs) -> Option<hir::Trait> {
|
||||||
fn group_label(candidate: &ImportCandidate) -> GroupLabel {
|
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.assoc_item_name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.text();
|
.text();
|
||||||
GroupLabel(format!("Qualify {}", name))
|
GroupLabel(format!("Qualify {}", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String {
|
fn label(db: &RootDatabase, candidate: &ImportCandidate, import: &LocatedImport) -> String {
|
||||||
|
let display_path = match item_name(db, import.original_item) {
|
||||||
|
Some(display_path) => display_path.to_string(),
|
||||||
|
None => "{unknown}".to_string(),
|
||||||
|
};
|
||||||
match candidate {
|
match candidate {
|
||||||
ImportCandidate::Path(candidate) => {
|
ImportCandidate::Path(candidate) => {
|
||||||
if candidate.qualifier.is_some() {
|
if candidate.qualifier.is_some() {
|
||||||
format!("Qualify with `{}`", &import)
|
format!("Qualify with `{}`", display_path)
|
||||||
} else {
|
} else {
|
||||||
format!("Qualify as `{}`", &import)
|
format!("Qualify as `{}`", display_path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import),
|
ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", display_path),
|
||||||
ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import),
|
ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", display_path),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
use hir::ModuleDef;
|
||||||
use ide_db::helpers::mod_path_to_ast;
|
use ide_db::helpers::mod_path_to_ast;
|
||||||
use ide_db::imports_locator;
|
use ide_db::items_locator;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, make, AstNode, NameOwner},
|
ast::{self, make, AstNode, NameOwner},
|
||||||
|
@ -64,13 +65,11 @@ pub(crate) fn replace_derive_with_manual_impl(
|
||||||
let current_module = ctx.sema.scope(annotated_name.syntax()).module()?;
|
let current_module = ctx.sema.scope(annotated_name.syntax()).module()?;
|
||||||
let current_crate = current_module.krate();
|
let current_crate = current_module.krate();
|
||||||
|
|
||||||
let found_traits = imports_locator::find_exact_imports(
|
let found_traits =
|
||||||
&ctx.sema,
|
items_locator::with_exact_name(&ctx.sema, current_crate, trait_token.text().to_string())
|
||||||
current_crate,
|
.into_iter()
|
||||||
trait_token.text().to_string(),
|
.filter_map(|item| match ModuleDef::from(item.as_module_def_id()?) {
|
||||||
)
|
ModuleDef::Trait(trait_) => Some(trait_),
|
||||||
.filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
|
|
||||||
either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.flat_map(|trait_| {
|
.flat_map(|trait_| {
|
||||||
|
|
|
@ -21,6 +21,46 @@
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Also completes associated items, that require trait imports.
|
//! Also completes associated items, that require trait imports.
|
||||||
|
//! If any unresolved and/or partially-qualified path predeces the input, it will be taken into account.
|
||||||
|
//! Currently, only the imports with their import path ending with the whole qialifier will be proposed
|
||||||
|
//! (no fuzzy matching for qualifier).
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! mod foo {
|
||||||
|
//! pub mod bar {
|
||||||
|
//! pub struct Item;
|
||||||
|
//!
|
||||||
|
//! impl Item {
|
||||||
|
//! pub const TEST_ASSOC: usize = 3;
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn main() {
|
||||||
|
//! bar::Item::TEST_A$0
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//! ->
|
||||||
|
//! ```
|
||||||
|
//! use foo::bar;
|
||||||
|
//!
|
||||||
|
//! mod foo {
|
||||||
|
//! pub mod bar {
|
||||||
|
//! pub struct Item;
|
||||||
|
//!
|
||||||
|
//! impl Item {
|
||||||
|
//! pub const TEST_ASSOC: usize = 3;
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn main() {
|
||||||
|
//! bar::Item::TEST_ASSOC
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! NOTE: currently, if an assoc item comes from a trait that's not currently imported and it also has an unresolved and/or partially-qualified path,
|
||||||
|
//! no imports will be proposed.
|
||||||
//!
|
//!
|
||||||
//! .Fuzzy search details
|
//! .Fuzzy search details
|
||||||
//!
|
//!
|
||||||
|
@ -48,12 +88,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 hir::{AsAssocItem, ModPath, ScopeDef};
|
use hir::ModPath;
|
||||||
use ide_db::helpers::{
|
use ide_db::helpers::{
|
||||||
import_assets::{ImportAssets, ImportCandidate},
|
import_assets::{ImportAssets, ImportCandidate},
|
||||||
insert_use::ImportScope,
|
insert_use::ImportScope,
|
||||||
};
|
};
|
||||||
use rustc_hash::FxHashSet;
|
use itertools::Itertools;
|
||||||
use syntax::{AstNode, SyntaxNode, T};
|
use syntax::{AstNode, SyntaxNode, T};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -92,50 +132,26 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
|
||||||
&ctx.sema,
|
&ctx.sema,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let scope_definitions = scope_definitions(ctx);
|
acc.add_all(
|
||||||
let mut all_mod_paths = import_assets
|
import_assets
|
||||||
.search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
|
.search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(mod_path, item_in_ns)| {
|
.sorted_by_key(|located_import| {
|
||||||
let scope_item = match item_in_ns {
|
compute_fuzzy_completion_order_key(
|
||||||
hir::ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()),
|
&located_import.import_path,
|
||||||
hir::ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()),
|
&user_input_lowercased,
|
||||||
hir::ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()),
|
)
|
||||||
};
|
|
||||||
(mod_path, scope_item)
|
|
||||||
})
|
})
|
||||||
.filter(|(_, proposed_def)| !scope_definitions.contains(proposed_def))
|
.filter_map(|import| {
|
||||||
.collect::<Vec<_>>();
|
render_resolution_with_import(
|
||||||
all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
|
RenderContext::new(ctx),
|
||||||
compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
|
ImportEdit { import, scope: import_scope.clone() },
|
||||||
});
|
)
|
||||||
|
}),
|
||||||
acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, 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(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scope_definitions(ctx: &CompletionContext) -> FxHashSet<ScopeDef> {
|
|
||||||
let mut scope_definitions = FxHashSet::default();
|
|
||||||
ctx.scope.process_all_names(&mut |_, scope_def| {
|
|
||||||
scope_definitions.insert(scope_def);
|
|
||||||
});
|
|
||||||
scope_definitions
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn position_for_import<'a>(
|
pub(crate) fn position_for_import<'a>(
|
||||||
ctx: &'a CompletionContext,
|
ctx: &'a CompletionContext,
|
||||||
import_candidate: Option<&ImportCandidate>,
|
import_candidate: Option<&ImportCandidate>,
|
||||||
|
@ -160,23 +176,30 @@ fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAs
|
||||||
current_module,
|
current_module,
|
||||||
ctx.sema.type_of_expr(dot_receiver)?,
|
ctx.sema.type_of_expr(dot_receiver)?,
|
||||||
fuzzy_name,
|
fuzzy_name,
|
||||||
|
dot_receiver.syntax().clone(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let fuzzy_name_length = fuzzy_name.len();
|
let fuzzy_name_length = fuzzy_name.len();
|
||||||
|
let approximate_node = match current_module.definition_source(ctx.db).value {
|
||||||
|
hir::ModuleSource::SourceFile(s) => s.syntax().clone(),
|
||||||
|
hir::ModuleSource::Module(m) => m.syntax().clone(),
|
||||||
|
hir::ModuleSource::BlockExpr(b) => b.syntax().clone(),
|
||||||
|
};
|
||||||
let assets_for_path = ImportAssets::for_fuzzy_path(
|
let assets_for_path = ImportAssets::for_fuzzy_path(
|
||||||
current_module,
|
current_module,
|
||||||
ctx.path_qual.clone(),
|
ctx.path_qual.clone(),
|
||||||
fuzzy_name,
|
fuzzy_name,
|
||||||
&ctx.sema,
|
&ctx.sema,
|
||||||
);
|
approximate_node,
|
||||||
|
)?;
|
||||||
|
|
||||||
if matches!(assets_for_path.as_ref()?.import_candidate(), ImportCandidate::Path(_))
|
if matches!(assets_for_path.import_candidate(), ImportCandidate::Path(_))
|
||||||
&& fuzzy_name_length < 2
|
&& fuzzy_name_length < 2
|
||||||
{
|
{
|
||||||
cov_mark::hit!(ignore_short_input_for_path);
|
cov_mark::hit!(ignore_short_input_for_path);
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
assets_for_path
|
Some(assets_for_path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,11 +209,11 @@ fn compute_fuzzy_completion_order_key(
|
||||||
user_input_lowercased: &str,
|
user_input_lowercased: &str,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
cov_mark::hit!(certain_fuzzy_order_test);
|
cov_mark::hit!(certain_fuzzy_order_test);
|
||||||
let proposed_import_name = match proposed_mod_path.segments().last() {
|
let import_name = match proposed_mod_path.segments().last() {
|
||||||
Some(name) => name.to_string().to_lowercase(),
|
Some(name) => name.to_string().to_lowercase(),
|
||||||
None => return usize::MAX,
|
None => return usize::MAX,
|
||||||
};
|
};
|
||||||
match proposed_import_name.match_indices(user_input_lowercased).next() {
|
match import_name.match_indices(user_input_lowercased).next() {
|
||||||
Some((first_matching_index, _)) => first_matching_index,
|
Some((first_matching_index, _)) => first_matching_index,
|
||||||
None => usize::MAX,
|
None => usize::MAX,
|
||||||
}
|
}
|
||||||
|
@ -773,4 +796,155 @@ fn main() {
|
||||||
}"#,
|
}"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unresolved_qualifier() {
|
||||||
|
let fixture = r#"
|
||||||
|
mod foo {
|
||||||
|
pub mod bar {
|
||||||
|
pub mod baz {
|
||||||
|
pub struct Item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
bar::baz::Ite$0
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
check(
|
||||||
|
fixture,
|
||||||
|
expect![[r#"
|
||||||
|
st foo::bar::baz::Item
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check_edit(
|
||||||
|
"Item",
|
||||||
|
fixture,
|
||||||
|
r#"
|
||||||
|
use foo::bar;
|
||||||
|
|
||||||
|
mod foo {
|
||||||
|
pub mod bar {
|
||||||
|
pub mod baz {
|
||||||
|
pub struct Item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
bar::baz::Item
|
||||||
|
}"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unresolved_assoc_item_container() {
|
||||||
|
let fixture = r#"
|
||||||
|
mod foo {
|
||||||
|
pub struct Item;
|
||||||
|
|
||||||
|
impl Item {
|
||||||
|
pub const TEST_ASSOC: usize = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Item::TEST_A$0
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
check(
|
||||||
|
fixture,
|
||||||
|
expect![[r#"
|
||||||
|
ct TEST_ASSOC (foo::Item)
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check_edit(
|
||||||
|
"TEST_ASSOC",
|
||||||
|
fixture,
|
||||||
|
r#"
|
||||||
|
use foo::Item;
|
||||||
|
|
||||||
|
mod foo {
|
||||||
|
pub struct Item;
|
||||||
|
|
||||||
|
impl Item {
|
||||||
|
pub const TEST_ASSOC: usize = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Item::TEST_ASSOC
|
||||||
|
}"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unresolved_assoc_item_container_with_path() {
|
||||||
|
let fixture = r#"
|
||||||
|
mod foo {
|
||||||
|
pub mod bar {
|
||||||
|
pub struct Item;
|
||||||
|
|
||||||
|
impl Item {
|
||||||
|
pub const TEST_ASSOC: usize = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
bar::Item::TEST_A$0
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
check(
|
||||||
|
fixture,
|
||||||
|
expect![[r#"
|
||||||
|
ct TEST_ASSOC (foo::bar::Item)
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check_edit(
|
||||||
|
"TEST_ASSOC",
|
||||||
|
fixture,
|
||||||
|
r#"
|
||||||
|
use foo::bar;
|
||||||
|
|
||||||
|
mod foo {
|
||||||
|
pub mod bar {
|
||||||
|
pub struct Item;
|
||||||
|
|
||||||
|
impl Item {
|
||||||
|
pub const TEST_ASSOC: usize = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
bar::Item::TEST_ASSOC
|
||||||
|
}"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzzy_unresolved_path() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
mod foo {
|
||||||
|
pub mod bar {
|
||||||
|
pub struct Item;
|
||||||
|
|
||||||
|
impl Item {
|
||||||
|
pub const TEST_ASSOC: usize = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
bar::Ass$0
|
||||||
|
}"#,
|
||||||
|
expect![[]],
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,16 @@
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use hir::{Documentation, ModPath, Mutability};
|
use hir::{Documentation, Mutability};
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
helpers::{
|
helpers::{
|
||||||
|
import_assets::LocatedImport,
|
||||||
insert_use::{self, ImportScope, InsertUseConfig},
|
insert_use::{self, ImportScope, InsertUseConfig},
|
||||||
mod_path_to_ast, SnippetCap,
|
mod_path_to_ast, SnippetCap,
|
||||||
},
|
},
|
||||||
SymbolKind,
|
SymbolKind,
|
||||||
};
|
};
|
||||||
use stdx::{impl_from, never};
|
use stdx::{format_to, impl_from, never};
|
||||||
use syntax::{algo, TextRange};
|
use syntax::{algo, TextRange};
|
||||||
use text_edit::TextEdit;
|
use text_edit::TextEdit;
|
||||||
|
|
||||||
|
@ -272,9 +273,8 @@ impl CompletionItem {
|
||||||
/// An extra import to add after the completion is applied.
|
/// An extra import to add after the completion is applied.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ImportEdit {
|
pub struct ImportEdit {
|
||||||
pub import_path: ModPath,
|
pub import: LocatedImport,
|
||||||
pub import_scope: ImportScope,
|
pub scope: ImportScope,
|
||||||
pub import_for_trait_assoc_item: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImportEdit {
|
impl ImportEdit {
|
||||||
|
@ -284,7 +284,7 @@ impl ImportEdit {
|
||||||
let _p = profile::span("ImportEdit::to_text_edit");
|
let _p = profile::span("ImportEdit::to_text_edit");
|
||||||
|
|
||||||
let rewriter =
|
let rewriter =
|
||||||
insert_use::insert_use(&self.import_scope, mod_path_to_ast(&self.import_path), cfg);
|
insert_use::insert_use(&self.scope, mod_path_to_ast(&self.import.import_path), cfg);
|
||||||
let old_ast = rewriter.rewrite_root()?;
|
let old_ast = rewriter.rewrite_root()?;
|
||||||
let mut import_insert = TextEdit::builder();
|
let mut import_insert = TextEdit::builder();
|
||||||
algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
|
algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
|
||||||
|
@ -322,20 +322,19 @@ impl Builder {
|
||||||
let mut lookup = self.lookup;
|
let mut lookup = self.lookup;
|
||||||
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(original_path) = self
|
||||||
if import_to_add.import_for_trait_assoc_item {
|
.import_to_add
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|import_edit| import_edit.import.original_path.as_ref())
|
||||||
|
{
|
||||||
lookup = lookup.or_else(|| Some(label.clone()));
|
lookup = lookup.or_else(|| Some(label.clone()));
|
||||||
insert_text = insert_text.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.pop_segment();
|
|
||||||
|
|
||||||
if !import_path_without_last_segment.segments().is_empty() {
|
let original_path_label = original_path.to_string();
|
||||||
lookup = lookup.or_else(|| Some(label.clone()));
|
if original_path_label.ends_with(&label) {
|
||||||
insert_text = insert_text.or_else(|| Some(label.clone()));
|
label = original_path_label;
|
||||||
label = format!("{}::{}", import_path_without_last_segment, label);
|
} else {
|
||||||
}
|
format_to!(label, " ({})", original_path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,9 +438,3 @@ impl Builder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Into<CompletionItem> for Builder {
|
|
||||||
fn into(self) -> CompletionItem {
|
|
||||||
self.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,7 +13,9 @@ mod completions;
|
||||||
|
|
||||||
use completions::flyimport::position_for_import;
|
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::{import_assets::LocatedImport, insert_use::ImportScope},
|
||||||
|
items_locator, RootDatabase,
|
||||||
};
|
};
|
||||||
use text_edit::TextEdit;
|
use text_edit::TextEdit;
|
||||||
|
|
||||||
|
@ -139,25 +141,27 @@ 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 position_for_import = position_for_import(&ctx, None)?;
|
let position_for_import = position_for_import(&ctx, None)?;
|
||||||
let import_scope = ImportScope::find_insert_use_container(position_for_import, &ctx.sema)?;
|
let scope = ImportScope::find_insert_use_container(position_for_import, &ctx.sema)?;
|
||||||
|
|
||||||
let current_module = ctx.sema.scope(position_for_import).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, item_to_import) =
|
||||||
|
items_locator::with_exact_name(&ctx.sema, current_crate, imported_name)
|
||||||
|
.into_iter()
|
||||||
.filter_map(|candidate| {
|
.filter_map(|candidate| {
|
||||||
let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
|
current_module
|
||||||
current_module.find_use_path_prefixed(db, item, config.insert_use.prefix_kind)
|
.find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind)
|
||||||
|
.zip(Some(candidate))
|
||||||
})
|
})
|
||||||
.find(|mod_path| mod_path.to_string() == full_import_path)?;
|
.find(|(mod_path, _)| mod_path.to_string() == full_import_path)?;
|
||||||
|
let import =
|
||||||
|
LocatedImport::new(import_path.clone(), item_to_import, item_to_import, Some(import_path));
|
||||||
|
|
||||||
ImportEdit { import_path, import_scope, import_for_trait_assoc_item }
|
ImportEdit { import, scope }.to_text_edit(config.insert_use).map(|edit| vec![edit])
|
||||||
.to_text_edit(config.insert_use)
|
|
||||||
.map(|edit| vec![edit])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -13,7 +13,10 @@ mod builder_ext;
|
||||||
use hir::{
|
use hir::{
|
||||||
AsAssocItem, Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type,
|
AsAssocItem, Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type,
|
||||||
};
|
};
|
||||||
use ide_db::{helpers::SnippetCap, RootDatabase, SymbolKind};
|
use ide_db::{
|
||||||
|
helpers::{item_name, SnippetCap},
|
||||||
|
RootDatabase, SymbolKind,
|
||||||
|
};
|
||||||
use syntax::TextRange;
|
use syntax::TextRange;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -50,18 +53,20 @@ pub(crate) fn render_resolution<'a>(
|
||||||
pub(crate) fn render_resolution_with_import<'a>(
|
pub(crate) fn render_resolution_with_import<'a>(
|
||||||
ctx: RenderContext<'a>,
|
ctx: RenderContext<'a>,
|
||||||
import_edit: ImportEdit,
|
import_edit: ImportEdit,
|
||||||
resolution: &ScopeDef,
|
|
||||||
) -> Option<CompletionItem> {
|
) -> Option<CompletionItem> {
|
||||||
|
let resolution = ScopeDef::from(import_edit.import.original_item);
|
||||||
let local_name = match resolution {
|
let local_name = match resolution {
|
||||||
ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(),
|
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::Const(c)) => c.name(ctx.completion.db)?.to_string(),
|
||||||
ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.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(),
|
_ => item_name(ctx.db(), import_edit.import.original_item)?.to_string(),
|
||||||
};
|
};
|
||||||
Render::new(ctx).render_resolution(local_name, Some(import_edit), resolution).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.
|
||||||
|
|
|
@ -2,11 +2,19 @@
|
||||||
pub mod insert_use;
|
pub mod insert_use;
|
||||||
pub mod import_assets;
|
pub mod import_assets;
|
||||||
|
|
||||||
use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait};
|
use hir::{Crate, Enum, ItemInNs, MacroDef, Module, ModuleDef, Name, ScopeDef, Semantics, Trait};
|
||||||
use syntax::ast::{self, make};
|
use syntax::ast::{self, make};
|
||||||
|
|
||||||
use crate::RootDatabase;
|
use crate::RootDatabase;
|
||||||
|
|
||||||
|
pub fn item_name(db: &RootDatabase, item: ItemInNs) -> Option<Name> {
|
||||||
|
match item {
|
||||||
|
ItemInNs::Types(module_def_id) => ModuleDef::from(module_def_id).name(db),
|
||||||
|
ItemInNs::Values(module_def_id) => ModuleDef::from(module_def_id).name(db),
|
||||||
|
ItemInNs::Macros(macro_def_id) => MacroDef::from(macro_def_id).name(db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Converts the mod path struct into its ast representation.
|
/// Converts the mod path struct into its ast representation.
|
||||||
pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path {
|
pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path {
|
||||||
let _p = profile::span("mod_path_to_ast");
|
let _p = profile::span("mod_path_to_ast");
|
||||||
|
|
|
@ -1,19 +1,29 @@
|
||||||
//! Look up accessible paths for items.
|
//! Look up accessible paths for items.
|
||||||
use either::Either;
|
use hir::{
|
||||||
use hir::{AsAssocItem, AssocItem, Crate, MacroDef, Module, ModuleDef, PrefixKind, Semantics};
|
AsAssocItem, AssocItem, AssocItemContainer, Crate, ItemInNs, MacroDef, ModPath, Module,
|
||||||
|
ModuleDef, PathResolution, PrefixKind, ScopeDef, Semantics, Type,
|
||||||
|
};
|
||||||
|
use itertools::Itertools;
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use syntax::{ast, AstNode};
|
use syntax::{ast, utils::path_to_string_stripping_turbo_fish, AstNode, SyntaxNode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
imports_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT},
|
items_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT},
|
||||||
RootDatabase,
|
RootDatabase,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::item_name;
|
||||||
|
|
||||||
|
/// A candidate for import, derived during various IDE activities:
|
||||||
|
/// * completion with imports on the fly proposals
|
||||||
|
/// * completion edit resolve requests
|
||||||
|
/// * assists
|
||||||
|
/// * etc.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ImportCandidate {
|
pub enum ImportCandidate {
|
||||||
// A path, qualified (`std::collections::HashMap`) or not (`HashMap`).
|
/// A path, qualified (`std::collections::HashMap`) or not (`HashMap`).
|
||||||
Path(PathImportCandidate),
|
Path(PathImportCandidate),
|
||||||
/// A trait associated function (with no self parameter) or associated constant.
|
/// A trait associated function (with no self parameter) or an associated constant.
|
||||||
/// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type
|
/// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type
|
||||||
/// and `name` is the `test_function`
|
/// and `name` is the `test_function`
|
||||||
TraitAssocItem(TraitImportCandidate),
|
TraitAssocItem(TraitImportCandidate),
|
||||||
|
@ -23,21 +33,40 @@ pub enum ImportCandidate {
|
||||||
TraitMethod(TraitImportCandidate),
|
TraitMethod(TraitImportCandidate),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A trait import needed for a given associated item access.
|
||||||
|
/// For `some::path::SomeStruct::ASSOC_`, contains the
|
||||||
|
/// type of `some::path::SomeStruct` and `ASSOC_` as the item name.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TraitImportCandidate {
|
pub struct TraitImportCandidate {
|
||||||
pub receiver_ty: hir::Type,
|
/// A type of the item that has the associated item accessed at.
|
||||||
pub name: NameToImport,
|
pub receiver_ty: Type,
|
||||||
|
/// The associated item name that the trait to import should contain.
|
||||||
|
pub assoc_item_name: NameToImport,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Path import for a given name, qualified or not.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PathImportCandidate {
|
pub struct PathImportCandidate {
|
||||||
pub qualifier: Option<ast::Path>,
|
/// Optional qualifier before name.
|
||||||
|
pub qualifier: Option<FirstSegmentUnresolved>,
|
||||||
|
/// The name the item (struct, trait, enum, etc.) should have.
|
||||||
pub name: NameToImport,
|
pub name: NameToImport,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A qualifier that has a first segment and it's unresolved.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FirstSegmentUnresolved {
|
||||||
|
fist_segment: ast::NameRef,
|
||||||
|
full_qualifier: ast::Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A name that will be used during item lookups.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum NameToImport {
|
pub enum NameToImport {
|
||||||
|
/// Requires items with names that exactly match the given string, case-sensitive.
|
||||||
Exact(String),
|
Exact(String),
|
||||||
|
/// Requires items with names that case-insensitively contain all letters from the string,
|
||||||
|
/// in the same order, but not necessary adjacent.
|
||||||
Fuzzy(String),
|
Fuzzy(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,10 +79,12 @@ impl NameToImport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A struct to find imports in the project, given a certain name (or its part) and the context.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ImportAssets {
|
pub struct ImportAssets {
|
||||||
import_candidate: ImportCandidate,
|
import_candidate: ImportCandidate,
|
||||||
module_with_candidate: hir::Module,
|
candidate_node: SyntaxNode,
|
||||||
|
module_with_candidate: Module,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImportAssets {
|
impl ImportAssets {
|
||||||
|
@ -61,9 +92,11 @@ impl ImportAssets {
|
||||||
method_call: &ast::MethodCallExpr,
|
method_call: &ast::MethodCallExpr,
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
|
let candidate_node = method_call.syntax().clone();
|
||||||
Some(Self {
|
Some(Self {
|
||||||
import_candidate: ImportCandidate::for_method_call(sema, method_call)?,
|
import_candidate: ImportCandidate::for_method_call(sema, method_call)?,
|
||||||
module_with_candidate: sema.scope(method_call.syntax()).module()?,
|
module_with_candidate: sema.scope(&candidate_node).module()?,
|
||||||
|
candidate_node,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,94 +104,94 @@ impl ImportAssets {
|
||||||
fully_qualified_path: &ast::Path,
|
fully_qualified_path: &ast::Path,
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let syntax_under_caret = fully_qualified_path.syntax();
|
let candidate_node = fully_qualified_path.syntax().clone();
|
||||||
if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
|
if candidate_node.ancestors().find_map(ast::Use::cast).is_some() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(Self {
|
Some(Self {
|
||||||
import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?,
|
import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?,
|
||||||
module_with_candidate: sema.scope(syntax_under_caret).module()?,
|
module_with_candidate: sema.scope(&candidate_node).module()?,
|
||||||
|
candidate_node,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_fuzzy_path(
|
pub fn for_fuzzy_path(
|
||||||
module_with_path: Module,
|
module_with_candidate: Module,
|
||||||
qualifier: Option<ast::Path>,
|
qualifier: Option<ast::Path>,
|
||||||
fuzzy_name: String,
|
fuzzy_name: String,
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
|
candidate_node: SyntaxNode,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
Some(match qualifier {
|
Some(Self {
|
||||||
Some(qualifier) => {
|
import_candidate: ImportCandidate::for_fuzzy_path(qualifier, fuzzy_name, sema)?,
|
||||||
let qualifier_resolution = sema.resolve_path(&qualifier)?;
|
module_with_candidate,
|
||||||
match qualifier_resolution {
|
candidate_node,
|
||||||
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(
|
pub fn for_fuzzy_method_call(
|
||||||
module_with_method_call: Module,
|
module_with_method_call: Module,
|
||||||
receiver_ty: hir::Type,
|
receiver_ty: Type,
|
||||||
fuzzy_method_name: String,
|
fuzzy_method_name: String,
|
||||||
|
candidate_node: SyntaxNode,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
Some(Self {
|
Some(Self {
|
||||||
import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
|
import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
|
||||||
receiver_ty,
|
receiver_ty,
|
||||||
name: NameToImport::Fuzzy(fuzzy_method_name),
|
assoc_item_name: NameToImport::Fuzzy(fuzzy_method_name),
|
||||||
}),
|
}),
|
||||||
module_with_candidate: module_with_method_call,
|
module_with_candidate: module_with_method_call,
|
||||||
|
candidate_node,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An import (not necessary the only one) that corresponds a certain given [`PathImportCandidate`].
|
||||||
|
/// (the structure is not entirely correct, since there can be situations requiring two imports, see FIXME below for the details)
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct LocatedImport {
|
||||||
|
/// The path to use in the `use` statement for a given candidate to be imported.
|
||||||
|
pub import_path: ModPath,
|
||||||
|
/// An item that will be imported with the import path given.
|
||||||
|
pub item_to_import: ItemInNs,
|
||||||
|
/// The path import candidate, resolved.
|
||||||
|
///
|
||||||
|
/// Not necessary matches the import:
|
||||||
|
/// For any associated constant from the trait, we try to access as `some::path::SomeStruct::ASSOC_`
|
||||||
|
/// the original item is the associated constant, but the import has to be a trait that
|
||||||
|
/// defines this constant.
|
||||||
|
pub original_item: ItemInNs,
|
||||||
|
/// A path of the original item.
|
||||||
|
pub original_path: Option<ModPath>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocatedImport {
|
||||||
|
pub fn new(
|
||||||
|
import_path: ModPath,
|
||||||
|
item_to_import: ItemInNs,
|
||||||
|
original_item: ItemInNs,
|
||||||
|
original_path: Option<ModPath>,
|
||||||
|
) -> Self {
|
||||||
|
Self { import_path, item_to_import, original_item, original_path }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ImportAssets {
|
impl ImportAssets {
|
||||||
pub fn import_candidate(&self) -> &ImportCandidate {
|
pub fn import_candidate(&self) -> &ImportCandidate {
|
||||||
&self.import_candidate
|
&self.import_candidate
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name_to_import(&self) -> &NameToImport {
|
|
||||||
match &self.import_candidate {
|
|
||||||
ImportCandidate::Path(candidate) => &candidate.name,
|
|
||||||
ImportCandidate::TraitAssocItem(candidate)
|
|
||||||
| ImportCandidate::TraitMethod(candidate) => &candidate.name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn search_for_imports(
|
pub fn search_for_imports(
|
||||||
&self,
|
&self,
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
prefix_kind: PrefixKind,
|
prefix_kind: PrefixKind,
|
||||||
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
|
) -> Vec<LocatedImport> {
|
||||||
let _p = profile::span("import_assets::search_for_imports");
|
let _p = profile::span("import_assets::search_for_imports");
|
||||||
self.search_for(sema, Some(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.
|
||||||
pub fn search_for_relative_paths(
|
pub fn search_for_relative_paths(&self, sema: &Semantics<RootDatabase>) -> Vec<LocatedImport> {
|
||||||
&self,
|
|
||||||
sema: &Semantics<RootDatabase>,
|
|
||||||
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
|
|
||||||
let _p = profile::span("import_assets::search_for_relative_paths");
|
let _p = profile::span("import_assets::search_for_relative_paths");
|
||||||
self.search_for(sema, None)
|
self.search_for(sema, None)
|
||||||
}
|
}
|
||||||
|
@ -166,29 +199,29 @@ impl ImportAssets {
|
||||||
fn search_for(
|
fn search_for(
|
||||||
&self,
|
&self,
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
prefixed: Option<hir::PrefixKind>,
|
prefixed: Option<PrefixKind>,
|
||||||
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
|
) -> Vec<LocatedImport> {
|
||||||
let current_crate = self.module_with_candidate.krate();
|
let items_with_candidate_name = match self.name_to_import() {
|
||||||
|
NameToImport::Exact(exact_name) => items_locator::with_exact_name(
|
||||||
let unfiltered_imports = match self.name_to_import() {
|
sema,
|
||||||
NameToImport::Exact(exact_name) => {
|
self.module_with_candidate.krate(),
|
||||||
imports_locator::find_exact_imports(sema, current_crate, exact_name.clone())
|
exact_name.clone(),
|
||||||
}
|
),
|
||||||
// FIXME: ideally, we should avoid using `fst` for seacrhing trait imports for assoc items:
|
// 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
|
// 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
|
// 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
|
// and https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Blanket.20trait.20impls.20lookup
|
||||||
// for the details
|
// for the details
|
||||||
NameToImport::Fuzzy(fuzzy_name) => {
|
NameToImport::Fuzzy(fuzzy_name) => {
|
||||||
let (assoc_item_search, limit) = match self.import_candidate {
|
let (assoc_item_search, limit) = if self.import_candidate.is_trait_candidate() {
|
||||||
ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => {
|
|
||||||
(AssocItemSearch::AssocItemsOnly, None)
|
(AssocItemSearch::AssocItemsOnly, None)
|
||||||
}
|
} else {
|
||||||
_ => (AssocItemSearch::Exclude, Some(DEFAULT_QUERY_SEARCH_LIMIT)),
|
(AssocItemSearch::Include, Some(DEFAULT_QUERY_SEARCH_LIMIT))
|
||||||
};
|
};
|
||||||
imports_locator::find_similar_imports(
|
|
||||||
|
items_locator::with_similar_name(
|
||||||
sema,
|
sema,
|
||||||
current_crate,
|
self.module_with_candidate.krate(),
|
||||||
fuzzy_name.clone(),
|
fuzzy_name.clone(),
|
||||||
assoc_item_search,
|
assoc_item_search,
|
||||||
limit,
|
limit,
|
||||||
|
@ -196,63 +229,224 @@ impl ImportAssets {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = sema.db;
|
let scope_definitions = self.scope_definitions(sema);
|
||||||
let mut res =
|
self.applicable_defs(sema.db, prefixed, items_with_candidate_name)
|
||||||
applicable_defs(self.import_candidate(), current_crate, db, unfiltered_imports)
|
.into_iter()
|
||||||
.filter_map(|candidate| {
|
.filter(|import| import.import_path.len() > 1)
|
||||||
let item: hir::ItemInNs = candidate.clone().either(Into::into, Into::into);
|
.filter(|import| !scope_definitions.contains(&ScopeDef::from(import.item_to_import)))
|
||||||
|
.sorted_by_key(|import| import.import_path.clone())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
let item_to_search = match self.import_candidate {
|
fn scope_definitions(&self, sema: &Semantics<RootDatabase>) -> FxHashSet<ScopeDef> {
|
||||||
ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => {
|
let mut scope_definitions = FxHashSet::default();
|
||||||
let canidate_trait = match candidate {
|
sema.scope(&self.candidate_node).process_all_names(&mut |_, scope_def| {
|
||||||
Either::Left(module_def) => {
|
scope_definitions.insert(scope_def);
|
||||||
module_def.as_assoc_item(db)?.containing_trait(db)
|
});
|
||||||
|
scope_definitions
|
||||||
}
|
}
|
||||||
_ => None,
|
|
||||||
}?;
|
fn name_to_import(&self) -> &NameToImport {
|
||||||
ModuleDef::from(canidate_trait).into()
|
match &self.import_candidate {
|
||||||
|
ImportCandidate::Path(candidate) => &candidate.name,
|
||||||
|
ImportCandidate::TraitAssocItem(candidate)
|
||||||
|
| ImportCandidate::TraitMethod(candidate) => &candidate.assoc_item_name,
|
||||||
}
|
}
|
||||||
_ => item,
|
}
|
||||||
|
|
||||||
|
fn applicable_defs(
|
||||||
|
&self,
|
||||||
|
db: &RootDatabase,
|
||||||
|
prefixed: Option<PrefixKind>,
|
||||||
|
items_with_candidate_name: FxHashSet<ItemInNs>,
|
||||||
|
) -> FxHashSet<LocatedImport> {
|
||||||
|
let _p = profile::span("import_assets::applicable_defs");
|
||||||
|
let current_crate = self.module_with_candidate.krate();
|
||||||
|
|
||||||
|
let mod_path = |item| {
|
||||||
|
get_mod_path(db, item_for_path_search(db, item)?, &self.module_with_candidate, prefixed)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(prefix_kind) = prefixed {
|
match &self.import_candidate {
|
||||||
self.module_with_candidate.find_use_path_prefixed(
|
ImportCandidate::Path(path_candidate) => {
|
||||||
db,
|
path_applicable_imports(db, path_candidate, mod_path, items_with_candidate_name)
|
||||||
item_to_search,
|
}
|
||||||
prefix_kind,
|
ImportCandidate::TraitAssocItem(trait_candidate) => trait_applicable_items(
|
||||||
)
|
db,
|
||||||
} else {
|
current_crate,
|
||||||
self.module_with_candidate.find_use_path(db, item_to_search)
|
trait_candidate,
|
||||||
|
true,
|
||||||
|
mod_path,
|
||||||
|
items_with_candidate_name,
|
||||||
|
),
|
||||||
|
ImportCandidate::TraitMethod(trait_candidate) => trait_applicable_items(
|
||||||
|
db,
|
||||||
|
current_crate,
|
||||||
|
trait_candidate,
|
||||||
|
false,
|
||||||
|
mod_path,
|
||||||
|
items_with_candidate_name,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
.map(|path| (path, item))
|
|
||||||
})
|
|
||||||
.filter(|(use_path, _)| use_path.len() > 1)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
res.sort_by_cached_key(|(path, _)| path.clone());
|
|
||||||
res
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn applicable_defs<'a>(
|
fn path_applicable_imports(
|
||||||
import_candidate: &ImportCandidate,
|
|
||||||
current_crate: Crate,
|
|
||||||
db: &RootDatabase,
|
db: &RootDatabase,
|
||||||
unfiltered_imports: Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a>,
|
path_candidate: &PathImportCandidate,
|
||||||
) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a> {
|
mod_path: impl Fn(ItemInNs) -> Option<ModPath> + Copy,
|
||||||
let receiver_ty = match import_candidate {
|
items_with_candidate_name: FxHashSet<ItemInNs>,
|
||||||
ImportCandidate::Path(_) => return unfiltered_imports,
|
) -> FxHashSet<LocatedImport> {
|
||||||
ImportCandidate::TraitAssocItem(candidate) | ImportCandidate::TraitMethod(candidate) => {
|
let _p = profile::span("import_assets::path_applicable_imports");
|
||||||
&candidate.receiver_ty
|
|
||||||
|
let (unresolved_first_segment, unresolved_qualifier) = match &path_candidate.qualifier {
|
||||||
|
None => {
|
||||||
|
return items_with_candidate_name
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|item| {
|
||||||
|
Some(LocatedImport::new(mod_path(item)?, item, item, mod_path(item)))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
}
|
}
|
||||||
|
Some(first_segment_unresolved) => (
|
||||||
|
first_segment_unresolved.fist_segment.to_string(),
|
||||||
|
path_to_string_stripping_turbo_fish(&first_segment_unresolved.full_qualifier),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
items_with_candidate_name
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|item| {
|
||||||
|
import_for_item(db, mod_path, &unresolved_first_segment, &unresolved_qualifier, item)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_for_item(
|
||||||
|
db: &RootDatabase,
|
||||||
|
mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
|
||||||
|
unresolved_first_segment: &str,
|
||||||
|
unresolved_qualifier: &str,
|
||||||
|
original_item: ItemInNs,
|
||||||
|
) -> Option<LocatedImport> {
|
||||||
|
let _p = profile::span("import_assets::import_for_item");
|
||||||
|
|
||||||
|
let original_item_candidate = item_for_path_search(db, original_item)?;
|
||||||
|
let import_path_candidate = mod_path(original_item_candidate)?;
|
||||||
|
let import_path_string = import_path_candidate.to_string();
|
||||||
|
|
||||||
|
let expected_import_end = if item_as_assoc(db, original_item).is_some() {
|
||||||
|
unresolved_qualifier.to_string()
|
||||||
|
} else {
|
||||||
|
format!("{}::{}", unresolved_qualifier, item_name(db, original_item)?)
|
||||||
|
};
|
||||||
|
if !import_path_string.contains(unresolved_first_segment)
|
||||||
|
|| !import_path_string.ends_with(&expected_import_end)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let segment_import =
|
||||||
|
find_import_for_segment(db, original_item_candidate, &unresolved_first_segment)?;
|
||||||
|
let trait_item_to_import = item_as_assoc(db, original_item)
|
||||||
|
.and_then(|assoc| assoc.containing_trait(db))
|
||||||
|
.map(|trait_| ItemInNs::from(ModuleDef::from(trait_)));
|
||||||
|
Some(match (segment_import == original_item_candidate, trait_item_to_import) {
|
||||||
|
(true, Some(_)) => {
|
||||||
|
// FIXME we should be able to import both the trait and the segment,
|
||||||
|
// but it's unclear what to do with overlapping edits (merge imports?)
|
||||||
|
// especially in case of lazy completion edit resolutions.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
(false, Some(trait_to_import)) => LocatedImport::new(
|
||||||
|
mod_path(trait_to_import)?,
|
||||||
|
trait_to_import,
|
||||||
|
original_item,
|
||||||
|
mod_path(original_item),
|
||||||
|
),
|
||||||
|
(true, None) => LocatedImport::new(
|
||||||
|
import_path_candidate,
|
||||||
|
original_item_candidate,
|
||||||
|
original_item,
|
||||||
|
mod_path(original_item),
|
||||||
|
),
|
||||||
|
(false, None) => LocatedImport::new(
|
||||||
|
mod_path(segment_import)?,
|
||||||
|
segment_import,
|
||||||
|
original_item,
|
||||||
|
mod_path(original_item),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> {
|
||||||
|
Some(match item {
|
||||||
|
ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) {
|
||||||
|
Some(assoc_item) => match assoc_item.container(db) {
|
||||||
|
AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
|
||||||
|
AssocItemContainer::Impl(impl_) => {
|
||||||
|
ItemInNs::from(ModuleDef::from(impl_.target_ty(db).as_adt()?))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => item,
|
||||||
|
},
|
||||||
|
ItemInNs::Macros(_) => item,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_import_for_segment(
|
||||||
|
db: &RootDatabase,
|
||||||
|
original_item: ItemInNs,
|
||||||
|
unresolved_first_segment: &str,
|
||||||
|
) -> Option<ItemInNs> {
|
||||||
|
let segment_is_name = item_name(db, original_item)
|
||||||
|
.map(|name| name.to_string() == unresolved_first_segment)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
Some(if segment_is_name {
|
||||||
|
original_item
|
||||||
|
} else {
|
||||||
|
let matching_module =
|
||||||
|
module_with_segment_name(db, &unresolved_first_segment, original_item)?;
|
||||||
|
ItemInNs::from(ModuleDef::from(matching_module))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module_with_segment_name(
|
||||||
|
db: &RootDatabase,
|
||||||
|
segment_name: &str,
|
||||||
|
candidate: ItemInNs,
|
||||||
|
) -> Option<Module> {
|
||||||
|
let mut current_module = match candidate {
|
||||||
|
ItemInNs::Types(module_def_id) => ModuleDef::from(module_def_id).module(db),
|
||||||
|
ItemInNs::Values(module_def_id) => ModuleDef::from(module_def_id).module(db),
|
||||||
|
ItemInNs::Macros(macro_def_id) => MacroDef::from(macro_def_id).module(db),
|
||||||
|
};
|
||||||
|
while let Some(module) = current_module {
|
||||||
|
if let Some(module_name) = module.name(db) {
|
||||||
|
if module_name.to_string() == segment_name {
|
||||||
|
return Some(module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_module = module.parent(db);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trait_applicable_items(
|
||||||
|
db: &RootDatabase,
|
||||||
|
current_crate: Crate,
|
||||||
|
trait_candidate: &TraitImportCandidate,
|
||||||
|
trait_assoc_item: bool,
|
||||||
|
mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
|
||||||
|
items_with_candidate_name: FxHashSet<ItemInNs>,
|
||||||
|
) -> FxHashSet<LocatedImport> {
|
||||||
|
let _p = profile::span("import_assets::trait_applicable_items");
|
||||||
let mut required_assoc_items = FxHashSet::default();
|
let mut required_assoc_items = FxHashSet::default();
|
||||||
|
|
||||||
let trait_candidates = unfiltered_imports
|
let trait_candidates = items_with_candidate_name
|
||||||
.filter_map(|input| match input {
|
.into_iter()
|
||||||
Either::Left(module_def) => module_def.as_assoc_item(db),
|
.filter_map(|input| item_as_assoc(db, input))
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.filter_map(|assoc| {
|
.filter_map(|assoc| {
|
||||||
let assoc_item_trait = assoc.containing_trait(db)?;
|
let assoc_item_trait = assoc.containing_trait(db)?;
|
||||||
required_assoc_items.insert(assoc);
|
required_assoc_items.insert(assoc);
|
||||||
|
@ -260,11 +454,10 @@ fn applicable_defs<'a>(
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut applicable_defs = FxHashSet::default();
|
let mut located_imports = FxHashSet::default();
|
||||||
|
|
||||||
match import_candidate {
|
if trait_assoc_item {
|
||||||
ImportCandidate::Path(_) => unreachable!(),
|
trait_candidate.receiver_ty.iterate_path_candidates(
|
||||||
ImportCandidate::TraitAssocItem(_) => receiver_ty.iterate_path_candidates(
|
|
||||||
db,
|
db,
|
||||||
current_crate,
|
current_crate,
|
||||||
&trait_candidates,
|
&trait_candidates,
|
||||||
|
@ -276,12 +469,21 @@ fn applicable_defs<'a>(
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
applicable_defs.insert(Either::Left(assoc_to_module_def(assoc)));
|
|
||||||
|
let item = ItemInNs::from(ModuleDef::from(assoc.containing_trait(db)?));
|
||||||
|
let original_item = assoc_to_item(assoc);
|
||||||
|
located_imports.insert(LocatedImport::new(
|
||||||
|
mod_path(item)?,
|
||||||
|
item,
|
||||||
|
original_item,
|
||||||
|
mod_path(original_item),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
None::<()>
|
None::<()>
|
||||||
},
|
},
|
||||||
),
|
)
|
||||||
ImportCandidate::TraitMethod(_) => receiver_ty.iterate_method_candidates(
|
} else {
|
||||||
|
trait_candidate.receiver_ty.iterate_method_candidates(
|
||||||
db,
|
db,
|
||||||
current_crate,
|
current_crate,
|
||||||
&trait_candidates,
|
&trait_candidates,
|
||||||
|
@ -289,21 +491,41 @@ fn applicable_defs<'a>(
|
||||||
|_, function| {
|
|_, function| {
|
||||||
let assoc = function.as_assoc_item(db)?;
|
let assoc = function.as_assoc_item(db)?;
|
||||||
if required_assoc_items.contains(&assoc) {
|
if required_assoc_items.contains(&assoc) {
|
||||||
applicable_defs.insert(Either::Left(assoc_to_module_def(assoc)));
|
let item = ItemInNs::from(ModuleDef::from(assoc.containing_trait(db)?));
|
||||||
|
let original_item = assoc_to_item(assoc);
|
||||||
|
located_imports.insert(LocatedImport::new(
|
||||||
|
mod_path(item)?,
|
||||||
|
item,
|
||||||
|
original_item,
|
||||||
|
mod_path(original_item),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
None::<()>
|
None::<()>
|
||||||
},
|
},
|
||||||
),
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
Box::new(applicable_defs.into_iter())
|
located_imports
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assoc_to_module_def(assoc: AssocItem) -> ModuleDef {
|
fn assoc_to_item(assoc: AssocItem) -> ItemInNs {
|
||||||
match assoc {
|
match assoc {
|
||||||
AssocItem::Function(f) => f.into(),
|
AssocItem::Function(f) => ItemInNs::from(ModuleDef::from(f)),
|
||||||
AssocItem::Const(c) => c.into(),
|
AssocItem::Const(c) => ItemInNs::from(ModuleDef::from(c)),
|
||||||
AssocItem::TypeAlias(t) => t.into(),
|
AssocItem::TypeAlias(t) => ItemInNs::from(ModuleDef::from(t)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_mod_path(
|
||||||
|
db: &RootDatabase,
|
||||||
|
item_to_search: ItemInNs,
|
||||||
|
module_with_candidate: &Module,
|
||||||
|
prefixed: Option<PrefixKind>,
|
||||||
|
) -> Option<ModPath> {
|
||||||
|
if let Some(prefix_kind) = prefixed {
|
||||||
|
module_with_candidate.find_use_path_prefixed(db, item_to_search, prefix_kind)
|
||||||
|
} else {
|
||||||
|
module_with_candidate.find_use_path(db, item_to_search)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,7 +538,7 @@ impl ImportCandidate {
|
||||||
Some(_) => None,
|
Some(_) => None,
|
||||||
None => Some(Self::TraitMethod(TraitImportCandidate {
|
None => Some(Self::TraitMethod(TraitImportCandidate {
|
||||||
receiver_ty: sema.type_of_expr(&method_call.receiver()?)?,
|
receiver_ty: sema.type_of_expr(&method_call.receiver()?)?,
|
||||||
name: NameToImport::Exact(method_call.name_ref()?.to_string()),
|
assoc_item_name: NameToImport::Exact(method_call.name_ref()?.to_string()),
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -325,41 +547,63 @@ impl ImportCandidate {
|
||||||
if sema.resolve_path(path).is_some() {
|
if sema.resolve_path(path).is_some() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
path_import_candidate(
|
||||||
|
sema,
|
||||||
|
path.qualifier(),
|
||||||
|
NameToImport::Exact(path.segment()?.name_ref()?.to_string()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let segment = path.segment()?;
|
fn for_fuzzy_path(
|
||||||
let candidate = if let Some(qualifier) = path.qualifier() {
|
qualifier: Option<ast::Path>,
|
||||||
let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
|
fuzzy_name: String,
|
||||||
let qualifier_start_path =
|
sema: &Semantics<RootDatabase>,
|
||||||
qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
|
) -> Option<Self> {
|
||||||
if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) {
|
path_import_candidate(sema, qualifier, NameToImport::Fuzzy(fuzzy_name))
|
||||||
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 {
|
|
||||||
receiver_ty: assoc_item_path.ty(sema.db),
|
|
||||||
name: NameToImport::Exact(segment.name_ref()?.to_string()),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
_ => return None,
|
|
||||||
}
|
fn is_trait_candidate(&self) -> bool {
|
||||||
} else {
|
matches!(self, ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_))
|
||||||
ImportCandidate::Path(PathImportCandidate {
|
|
||||||
qualifier: Some(qualifier),
|
|
||||||
name: NameToImport::Exact(qualifier_start.to_string()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ImportCandidate::Path(PathImportCandidate {
|
|
||||||
qualifier: None,
|
|
||||||
name: NameToImport::Exact(
|
|
||||||
segment.syntax().descendants().find_map(ast::NameRef::cast)?.to_string(),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
};
|
|
||||||
Some(candidate)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path_import_candidate(
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
qualifier: Option<ast::Path>,
|
||||||
|
name: NameToImport,
|
||||||
|
) -> Option<ImportCandidate> {
|
||||||
|
Some(match qualifier {
|
||||||
|
Some(qualifier) => match sema.resolve_path(&qualifier) {
|
||||||
|
None => {
|
||||||
|
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 sema.resolve_path(&qualifier_start_path).is_none() {
|
||||||
|
ImportCandidate::Path(PathImportCandidate {
|
||||||
|
qualifier: Some(FirstSegmentUnresolved {
|
||||||
|
fist_segment: qualifier_start,
|
||||||
|
full_qualifier: qualifier,
|
||||||
|
}),
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(PathResolution::Def(ModuleDef::Adt(assoc_item_path))) => {
|
||||||
|
ImportCandidate::TraitAssocItem(TraitImportCandidate {
|
||||||
|
receiver_ty: assoc_item_path.ty(sema.db),
|
||||||
|
assoc_item_name: name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Some(_) => return None,
|
||||||
|
},
|
||||||
|
None => ImportCandidate::Path(PathImportCandidate { qualifier: None, name }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn item_as_assoc(db: &RootDatabase, item: ItemInNs) -> Option<AssocItem> {
|
||||||
|
item.as_module_def_id()
|
||||||
|
.and_then(|module_def_id| ModuleDef::from(module_def_id).as_assoc_item(db))
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +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 either::Either;
|
||||||
use hir::{
|
use hir::{
|
||||||
import_map::{self, ImportKind},
|
import_map::{self, ImportKind},
|
||||||
AsAssocItem, Crate, MacroDef, ModuleDef, Semantics,
|
AsAssocItem, Crate, ItemInNs, ModuleDef, Semantics,
|
||||||
};
|
};
|
||||||
use syntax::{ast, AstNode, SyntaxKind::NAME};
|
use syntax::{ast, AstNode, SyntaxKind::NAME};
|
||||||
|
|
||||||
|
@ -12,47 +13,47 @@ use crate::{
|
||||||
symbol_index::{self, FileSymbol},
|
symbol_index::{self, FileSymbol},
|
||||||
RootDatabase,
|
RootDatabase,
|
||||||
};
|
};
|
||||||
use either::Either;
|
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
pub(crate) const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40;
|
pub(crate) const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40;
|
||||||
|
|
||||||
pub fn find_exact_imports<'a>(
|
pub fn with_exact_name(
|
||||||
sema: &Semantics<'a, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
krate: Crate,
|
krate: Crate,
|
||||||
name_to_import: String,
|
exact_name: String,
|
||||||
) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>>> {
|
) -> FxHashSet<ItemInNs> {
|
||||||
let _p = profile::span("find_exact_imports");
|
let _p = profile::span("find_exact_imports");
|
||||||
Box::new(find_imports(
|
find_items(
|
||||||
sema,
|
sema,
|
||||||
krate,
|
krate,
|
||||||
{
|
{
|
||||||
let mut local_query = symbol_index::Query::new(name_to_import.clone());
|
let mut local_query = symbol_index::Query::new(exact_name.clone());
|
||||||
local_query.exact();
|
local_query.exact();
|
||||||
local_query.limit(DEFAULT_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(exact_name)
|
||||||
.limit(DEFAULT_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(),
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum AssocItemSearch {
|
pub enum AssocItemSearch {
|
||||||
Include,
|
Include,
|
||||||
Exclude,
|
Exclude,
|
||||||
AssocItemsOnly,
|
AssocItemsOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_similar_imports<'a>(
|
pub fn with_similar_name(
|
||||||
sema: &Semantics<'a, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
krate: Crate,
|
krate: Crate,
|
||||||
fuzzy_search_string: String,
|
fuzzy_search_string: String,
|
||||||
assoc_item_search: AssocItemSearch,
|
assoc_item_search: AssocItemSearch,
|
||||||
limit: Option<usize>,
|
limit: Option<usize>,
|
||||||
) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a> {
|
) -> FxHashSet<ItemInNs> {
|
||||||
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())
|
||||||
|
@ -76,37 +77,39 @@ pub fn find_similar_imports<'a>(
|
||||||
local_query.limit(limit);
|
local_query.limit(limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
let db = sema.db;
|
find_items(sema, krate, local_query, external_query)
|
||||||
Box::new(find_imports(sema, krate, local_query, external_query).filter(
|
.into_iter()
|
||||||
move |import_candidate| match assoc_item_search {
|
.filter(move |&item| match assoc_item_search {
|
||||||
AssocItemSearch::Include => true,
|
AssocItemSearch::Include => true,
|
||||||
AssocItemSearch::Exclude => !is_assoc_item(import_candidate, db),
|
AssocItemSearch::Exclude => !is_assoc_item(item, sema.db),
|
||||||
AssocItemSearch::AssocItemsOnly => is_assoc_item(import_candidate, db),
|
AssocItemSearch::AssocItemsOnly => is_assoc_item(item, sema.db),
|
||||||
},
|
})
|
||||||
))
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_assoc_item(import_candidate: &Either<ModuleDef, MacroDef>, db: &RootDatabase) -> bool {
|
fn is_assoc_item(item: ItemInNs, db: &RootDatabase) -> bool {
|
||||||
match import_candidate {
|
item.as_module_def_id()
|
||||||
Either::Left(ModuleDef::Function(function)) => function.as_assoc_item(db).is_some(),
|
.and_then(|module_def_id| ModuleDef::from(module_def_id).as_assoc_item(db))
|
||||||
Either::Left(ModuleDef::Const(const_)) => const_.as_assoc_item(db).is_some(),
|
.is_some()
|
||||||
Either::Left(ModuleDef::TypeAlias(type_alias)) => type_alias.as_assoc_item(db).is_some(),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_imports<'a>(
|
fn find_items(
|
||||||
sema: &Semantics<'a, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
krate: Crate,
|
krate: Crate,
|
||||||
local_query: symbol_index::Query,
|
local_query: symbol_index::Query,
|
||||||
external_query: import_map::Query,
|
external_query: import_map::Query,
|
||||||
) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
|
) -> FxHashSet<ItemInNs> {
|
||||||
let _p = profile::span("find_similar_imports");
|
let _p = profile::span("find_similar_imports");
|
||||||
let db = sema.db;
|
let db = sema.db;
|
||||||
|
|
||||||
// Query dependencies first.
|
// Query dependencies first.
|
||||||
let mut candidates: FxHashSet<_> =
|
let mut candidates = krate
|
||||||
krate.query_external_importables(db, external_query).collect();
|
.query_external_importables(db, external_query)
|
||||||
|
.map(|external_importable| match external_importable {
|
||||||
|
Either::Left(module_def) => ItemInNs::from(module_def),
|
||||||
|
Either::Right(macro_def) => ItemInNs::from(macro_def),
|
||||||
|
})
|
||||||
|
.collect::<FxHashSet<_>>();
|
||||||
|
|
||||||
// Query the local crate using the symbol index.
|
// Query the local crate using the symbol index.
|
||||||
let local_results = symbol_index::crate_symbols(db, krate.into(), local_query);
|
let local_results = symbol_index::crate_symbols(db, krate.into(), local_query);
|
||||||
|
@ -114,19 +117,19 @@ fn find_imports<'a>(
|
||||||
candidates.extend(
|
candidates.extend(
|
||||||
local_results
|
local_results
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|import_candidate| get_name_definition(sema, &import_candidate))
|
.filter_map(|local_candidate| get_name_definition(sema, &local_candidate))
|
||||||
.filter_map(|name_definition_to_import| match name_definition_to_import {
|
.filter_map(|name_definition_to_import| match name_definition_to_import {
|
||||||
Definition::ModuleDef(module_def) => Some(Either::Left(module_def)),
|
Definition::ModuleDef(module_def) => Some(ItemInNs::from(module_def)),
|
||||||
Definition::Macro(macro_def) => Some(Either::Right(macro_def)),
|
Definition::Macro(macro_def) => Some(ItemInNs::from(macro_def)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
candidates.into_iter()
|
candidates
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_name_definition<'a>(
|
fn get_name_definition(
|
||||||
sema: &Semantics<'a, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
import_candidate: &FileSymbol,
|
import_candidate: &FileSymbol,
|
||||||
) -> Option<Definition> {
|
) -> Option<Definition> {
|
||||||
let _p = profile::span("get_name_definition");
|
let _p = profile::span("get_name_definition");
|
|
@ -8,7 +8,7 @@ pub mod line_index;
|
||||||
pub mod symbol_index;
|
pub mod symbol_index;
|
||||||
pub mod defs;
|
pub mod defs;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
pub mod imports_locator;
|
pub mod items_locator;
|
||||||
pub mod source_change;
|
pub mod source_change;
|
||||||
pub mod ty_filter;
|
pub mod ty_filter;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
|
|
@ -697,7 +697,6 @@ 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| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel)))
|
.flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel)))
|
||||||
|
@ -1525,7 +1524,6 @@ 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(
|
||||||
|
@ -1534,15 +1532,13 @@ fn fill_resolve_data(
|
||||||
position: &TextDocumentPositionParams,
|
position: &TextDocumentPositionParams,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let import_edit = item.import_to_add()?;
|
let import_edit = item.import_to_add()?;
|
||||||
let full_import_path = import_edit.import_path.to_string();
|
let import_path = &import_edit.import.import_path;
|
||||||
let imported_name = import_edit.import_path.segments().last()?.to_string();
|
|
||||||
|
|
||||||
*resolve_data = Some(
|
*resolve_data = Some(
|
||||||
to_value(CompletionResolveData {
|
to_value(CompletionResolveData {
|
||||||
position: position.to_owned(),
|
position: position.to_owned(),
|
||||||
full_import_path,
|
full_import_path: import_path.to_string(),
|
||||||
imported_name,
|
imported_name: import_path.segments().last()?.to_string(),
|
||||||
import_for_trait_assoc_item: import_edit.import_for_trait_assoc_item,
|
|
||||||
})
|
})
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -25,10 +25,10 @@ serde = { version = "1.0.106", features = ["derive"] }
|
||||||
stdx = { path = "../stdx", version = "0.0.0" }
|
stdx = { path = "../stdx", version = "0.0.0" }
|
||||||
text_edit = { path = "../text_edit", version = "0.0.0" }
|
text_edit = { path = "../text_edit", version = "0.0.0" }
|
||||||
parser = { path = "../parser", version = "0.0.0" }
|
parser = { path = "../parser", version = "0.0.0" }
|
||||||
test_utils = { path = "../test_utils", version = "0.0.0" }
|
|
||||||
profile = { path = "../profile", version = "0.0.0" }
|
profile = { path = "../profile", version = "0.0.0" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
test_utils = { path = "../test_utils" }
|
||||||
walkdir = "2.3.1"
|
walkdir = "2.3.1"
|
||||||
rayon = "1"
|
rayon = "1"
|
||||||
expect-test = "1.1"
|
expect-test = "1.1"
|
||||||
|
|
|
@ -91,6 +91,10 @@ pub fn path_from_segments(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn path_from_text(text: &str) -> ast::Path {
|
||||||
|
ast_from_text(&format!("fn main() {{ let test = {}; }}", text))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn glob_use_tree() -> ast::UseTree {
|
pub fn glob_use_tree() -> ast::UseTree {
|
||||||
ast_from_text("use *;")
|
ast_from_text("use *;")
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ pub mod algo;
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod fuzz;
|
pub mod fuzz;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
use std::{marker::PhantomData, sync::Arc};
|
use std::{marker::PhantomData, sync::Arc};
|
||||||
|
|
||||||
|
|
43
crates/syntax/src/utils.rs
Normal file
43
crates/syntax/src/utils.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
//! A set of utils methods to reuse on other abstraction levels
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use crate::{ast, match_ast, AstNode};
|
||||||
|
|
||||||
|
pub fn path_to_string_stripping_turbo_fish(path: &ast::Path) -> String {
|
||||||
|
path.syntax()
|
||||||
|
.children()
|
||||||
|
.filter_map(|node| {
|
||||||
|
match_ast! {
|
||||||
|
match node {
|
||||||
|
ast::PathSegment(it) => {
|
||||||
|
Some(it.name_ref()?.to_string())
|
||||||
|
},
|
||||||
|
ast::Path(it) => {
|
||||||
|
Some(path_to_string_stripping_turbo_fish(&it))
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join("::")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::path_to_string_stripping_turbo_fish;
|
||||||
|
use crate::ast::make;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn turbofishes_are_stripped() {
|
||||||
|
assert_eq!("Vec", path_to_string_stripping_turbo_fish(&make::path_from_text("Vec::<i32>")),);
|
||||||
|
assert_eq!(
|
||||||
|
"Vec::new",
|
||||||
|
path_to_string_stripping_turbo_fish(&make::path_from_text("Vec::<i32>::new")),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"Vec::new",
|
||||||
|
path_to_string_stripping_turbo_fish(&make::path_from_text("Vec::new()")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue