aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide_assists/src/handlers/auto_import.rs8
-rw-r--r--crates/ide_assists/src/handlers/qualify_path.rs14
-rw-r--r--crates/ide_db/src/helpers/import_assets.rs82
3 files changed, 64 insertions, 40 deletions
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs
index 5546c3a4e..7caee8df0 100644
--- a/crates/ide_assists/src/handlers/auto_import.rs
+++ b/crates/ide_assists/src/handlers/auto_import.rs
@@ -1,7 +1,7 @@
1use ide_db::helpers::{ 1use ide_db::helpers::{
2 import_assets::{ImportAssets, ImportCandidate}, 2 import_assets::{ImportAssets, ImportCandidate},
3 insert_use::{insert_use, ImportScope}, 3 insert_use::{insert_use, ImportScope},
4 mod_path_to_ast, 4 item_name, mod_path_to_ast,
5}; 5};
6use syntax::{ast, AstNode, SyntaxNode}; 6use syntax::{ast, AstNode, SyntaxNode};
7 7
@@ -93,7 +93,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
93 let group = import_group_message(import_assets.import_candidate()); 93 let group = import_group_message(import_assets.import_candidate());
94 let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?; 94 let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?;
95 for import in proposed_imports { 95 for import in proposed_imports {
96 let name = match import.original_item_name(ctx.db()) { 96 let name = match item_name(ctx.db(), import.original_item) {
97 Some(name) => name, 97 Some(name) => name,
98 None => continue, 98 None => continue,
99 }; 99 };
@@ -130,10 +130,10 @@ fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel {
130 let name = match import_candidate { 130 let name = match import_candidate {
131 ImportCandidate::Path(candidate) => format!("Import {}", candidate.name.text()), 131 ImportCandidate::Path(candidate) => format!("Import {}", candidate.name.text()),
132 ImportCandidate::TraitAssocItem(candidate) => { 132 ImportCandidate::TraitAssocItem(candidate) => {
133 format!("Import a trait for item {}", candidate.name.text()) 133 format!("Import a trait for item {}", candidate.assoc_item_name.text())
134 } 134 }
135 ImportCandidate::TraitMethod(candidate) => { 135 ImportCandidate::TraitMethod(candidate) => {
136 format!("Import a trait for method {}", candidate.name.text()) 136 format!("Import a trait for method {}", candidate.assoc_item_name.text())
137 } 137 }
138 }; 138 };
139 GroupLabel(name) 139 GroupLabel(name)
diff --git a/crates/ide_assists/src/handlers/qualify_path.rs b/crates/ide_assists/src/handlers/qualify_path.rs
index b36dd3823..272874ae3 100644
--- a/crates/ide_assists/src/handlers/qualify_path.rs
+++ b/crates/ide_assists/src/handlers/qualify_path.rs
@@ -2,8 +2,8 @@ use std::iter;
2 2
3use hir::AsAssocItem; 3use hir::AsAssocItem;
4use ide_db::helpers::{ 4use ide_db::helpers::{
5 import_assets::{ImportCandidate, LocatedImport, Qualifier}, 5 import_assets::{ImportCandidate, LocatedImport},
6 mod_path_to_ast, 6 item_name, mod_path_to_ast,
7}; 7};
8use ide_db::RootDatabase; 8use ide_db::RootDatabase;
9use syntax::{ 9use syntax::{
@@ -48,7 +48,7 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
48 48
49 let qualify_candidate = match candidate { 49 let qualify_candidate = match candidate {
50 ImportCandidate::Path(candidate) => { 50 ImportCandidate::Path(candidate) => {
51 if !matches!(candidate.qualifier, Qualifier::Absent) { 51 if candidate.qualifier.is_some() {
52 cov_mark::hit!(qualify_path_qualifier_start); 52 cov_mark::hit!(qualify_path_qualifier_start);
53 let path = ast::Path::cast(syntax_under_caret)?; 53 let path = ast::Path::cast(syntax_under_caret)?;
54 let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?); 54 let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
@@ -191,20 +191,22 @@ fn item_as_trait(db: &RootDatabase, item: hir::ItemInNs) -> Option<hir::Trait> {
191fn group_label(candidate: &ImportCandidate) -> GroupLabel { 191fn group_label(candidate: &ImportCandidate) -> GroupLabel {
192 let name = match candidate { 192 let name = match candidate {
193 ImportCandidate::Path(it) => &it.name, 193 ImportCandidate::Path(it) => &it.name,
194 ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name, 194 ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => {
195 &it.assoc_item_name
196 }
195 } 197 }
196 .text(); 198 .text();
197 GroupLabel(format!("Qualify {}", name)) 199 GroupLabel(format!("Qualify {}", name))
198} 200}
199 201
200fn label(db: &RootDatabase, candidate: &ImportCandidate, import: &LocatedImport) -> String { 202fn label(db: &RootDatabase, candidate: &ImportCandidate, import: &LocatedImport) -> String {
201 let display_path = match import.original_item_name(db) { 203 let display_path = match item_name(db, import.original_item) {
202 Some(display_path) => display_path.to_string(), 204 Some(display_path) => display_path.to_string(),
203 None => "{unknown}".to_string(), 205 None => "{unknown}".to_string(),
204 }; 206 };
205 match candidate { 207 match candidate {
206 ImportCandidate::Path(candidate) => { 208 ImportCandidate::Path(candidate) => {
207 if !matches!(candidate.qualifier, Qualifier::Absent) { 209 if candidate.qualifier.is_some() {
208 format!("Qualify with `{}`", display_path) 210 format!("Qualify with `{}`", display_path)
209 } else { 211 } else {
210 format!("Qualify as `{}`", display_path) 212 format!("Qualify as `{}`", display_path)
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs
index f2866af13..3d9df463d 100644
--- a/crates/ide_db/src/helpers/import_assets.rs
+++ b/crates/ide_db/src/helpers/import_assets.rs
@@ -1,7 +1,7 @@
1//! Look up accessible paths for items. 1//! Look up accessible paths for items.
2use hir::{ 2use hir::{
3 AsAssocItem, AssocItem, AssocItemContainer, Crate, ItemInNs, MacroDef, ModPath, Module, 3 AsAssocItem, AssocItem, AssocItemContainer, Crate, ItemInNs, MacroDef, ModPath, Module,
4 ModuleDef, Name, PathResolution, PrefixKind, ScopeDef, Semantics, Type, 4 ModuleDef, PathResolution, PrefixKind, ScopeDef, Semantics, Type,
5}; 5};
6use itertools::Itertools; 6use itertools::Itertools;
7use rustc_hash::FxHashSet; 7use rustc_hash::FxHashSet;
@@ -14,11 +14,16 @@ use crate::{
14 14
15use super::item_name; 15use super::item_name;
16 16
17/// A candidate for import, derived during various IDE activities:
18/// * completion with imports on the fly proposals
19/// * completion edit resolve requests
20/// * assists
21/// * etc.
17#[derive(Debug)] 22#[derive(Debug)]
18pub enum ImportCandidate { 23pub enum ImportCandidate {
19 // A path, qualified (`std::collections::HashMap`) or not (`HashMap`). 24 /// A path, qualified (`std::collections::HashMap`) or not (`HashMap`).
20 Path(PathImportCandidate), 25 Path(PathImportCandidate),
21 /// A trait associated function (with no self parameter) or associated constant. 26 /// A trait associated function (with no self parameter) or an associated constant.
22 /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type 27 /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type
23 /// and `name` is the `test_function` 28 /// and `name` is the `test_function`
24 TraitAssocItem(TraitImportCandidate), 29 TraitAssocItem(TraitImportCandidate),
@@ -28,27 +33,40 @@ pub enum ImportCandidate {
28 TraitMethod(TraitImportCandidate), 33 TraitMethod(TraitImportCandidate),
29} 34}
30 35
36/// A trait import needed for a given associated item access.
37/// For `some::path::SomeStruct::ASSOC_`, contains the
38/// type of `some::path::SomeStruct` and `ASSOC_` as the item name.
31#[derive(Debug)] 39#[derive(Debug)]
32pub struct TraitImportCandidate { 40pub struct TraitImportCandidate {
41 /// A type of the item that has the associated item accessed at.
33 pub receiver_ty: Type, 42 pub receiver_ty: Type,
34 pub name: NameToImport, 43 /// The associated item name that the trait to import should contain.
44 pub assoc_item_name: NameToImport,
35} 45}
36 46
47/// Path import for a given name, qualified or not.
37#[derive(Debug)] 48#[derive(Debug)]
38pub struct PathImportCandidate { 49pub struct PathImportCandidate {
39 pub qualifier: Qualifier, 50 /// Optional qualifier before name.
51 pub qualifier: Option<FirstSegmentUnresolved>,
52 /// The name the item (struct, trait, enum, etc.) should have.
40 pub name: NameToImport, 53 pub name: NameToImport,
41} 54}
42 55
56/// A qualifier that has a first segment and it's unresolved.
43#[derive(Debug)] 57#[derive(Debug)]
44pub enum Qualifier { 58pub struct FirstSegmentUnresolved {
45 Absent, 59 fist_segment: ast::NameRef,
46 FirstSegmentUnresolved(ast::NameRef, ModPath), 60 full_qualifier: ModPath,
47} 61}
48 62
63/// A name that will be used during item lookups.
49#[derive(Debug)] 64#[derive(Debug)]
50pub enum NameToImport { 65pub enum NameToImport {
66 /// Requires items with names that exactly match the given string, case-sensitive.
51 Exact(String), 67 Exact(String),
68 /// Requires items with names that case-insensitively contain all letters from the string,
69 /// in the same order, but not necessary adjacent.
52 Fuzzy(String), 70 Fuzzy(String),
53} 71}
54 72
@@ -61,6 +79,7 @@ impl NameToImport {
61 } 79 }
62} 80}
63 81
82/// A struct to find imports in the project, given a certain name (or its part) and the context.
64#[derive(Debug)] 83#[derive(Debug)]
65pub struct ImportAssets { 84pub struct ImportAssets {
66 import_candidate: ImportCandidate, 85 import_candidate: ImportCandidate,
@@ -119,7 +138,7 @@ impl ImportAssets {
119 Some(Self { 138 Some(Self {
120 import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate { 139 import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
121 receiver_ty, 140 receiver_ty,
122 name: NameToImport::Fuzzy(fuzzy_method_name), 141 assoc_item_name: NameToImport::Fuzzy(fuzzy_method_name),
123 }), 142 }),
124 module_with_candidate: module_with_method_call, 143 module_with_candidate: module_with_method_call,
125 candidate_node, 144 candidate_node,
@@ -127,11 +146,22 @@ impl ImportAssets {
127 } 146 }
128} 147}
129 148
149/// An import (not necessary the only one) that corresponds a certain given [`PathImportCandidate`].
150/// (the structure is not entirely correct, since there can be situations requiring two imports, see FIXME below for the details)
130#[derive(Debug, Clone, PartialEq, Eq, Hash)] 151#[derive(Debug, Clone, PartialEq, Eq, Hash)]
131pub struct LocatedImport { 152pub struct LocatedImport {
153 /// The path to use in the `use` statement for a given candidate to be imported.
132 pub import_path: ModPath, 154 pub import_path: ModPath,
155 /// An item that will be imported with the import path given.
133 pub item_to_import: ItemInNs, 156 pub item_to_import: ItemInNs,
157 /// The path import candidate, resolved.
158 ///
159 /// Not necessary matches the import:
160 /// For any associated constant from the trait, we try to access as `some::path::SomeStruct::ASSOC_`
161 /// the original item is the associated constant, but the import has to be a trait that
162 /// defines this constant.
134 pub original_item: ItemInNs, 163 pub original_item: ItemInNs,
164 /// A path of the original item.
135 pub original_path: Option<ModPath>, 165 pub original_path: Option<ModPath>,
136} 166}
137 167
@@ -144,15 +174,6 @@ impl LocatedImport {
144 ) -> Self { 174 ) -> Self {
145 Self { import_path, item_to_import, original_item, original_path } 175 Self { import_path, item_to_import, original_item, original_path }
146 } 176 }
147
148 pub fn original_item_name(&self, db: &RootDatabase) -> Option<Name> {
149 match self.original_item {
150 ItemInNs::Types(module_def_id) | ItemInNs::Values(module_def_id) => {
151 ModuleDef::from(module_def_id).name(db)
152 }
153 ItemInNs::Macros(macro_def_id) => MacroDef::from(macro_def_id).name(db),
154 }
155 }
156} 177}
157 178
158impl ImportAssets { 179impl ImportAssets {
@@ -229,7 +250,7 @@ impl ImportAssets {
229 match &self.import_candidate { 250 match &self.import_candidate {
230 ImportCandidate::Path(candidate) => &candidate.name, 251 ImportCandidate::Path(candidate) => &candidate.name,
231 ImportCandidate::TraitAssocItem(candidate) 252 ImportCandidate::TraitAssocItem(candidate)
232 | ImportCandidate::TraitMethod(candidate) => &candidate.name, 253 | ImportCandidate::TraitMethod(candidate) => &candidate.assoc_item_name,
233 } 254 }
234 } 255 }
235 256
@@ -279,7 +300,7 @@ fn path_applicable_imports(
279 let _p = profile::span("import_assets::path_applicable_imports"); 300 let _p = profile::span("import_assets::path_applicable_imports");
280 301
281 let (unresolved_first_segment, unresolved_qualifier) = match &path_candidate.qualifier { 302 let (unresolved_first_segment, unresolved_qualifier) = match &path_candidate.qualifier {
282 Qualifier::Absent => { 303 None => {
283 return items_with_candidate_name 304 return items_with_candidate_name
284 .into_iter() 305 .into_iter()
285 .filter_map(|item| { 306 .filter_map(|item| {
@@ -287,9 +308,10 @@ fn path_applicable_imports(
287 }) 308 })
288 .collect(); 309 .collect();
289 } 310 }
290 Qualifier::FirstSegmentUnresolved(first_segment, qualifier) => { 311 Some(first_segment_unresolved) => (
291 (first_segment.to_string(), qualifier.to_string()) 312 first_segment_unresolved.fist_segment.to_string(),
292 } 313 first_segment_unresolved.full_qualifier.to_string(),
314 ),
293 }; 315 };
294 316
295 items_with_candidate_name 317 items_with_candidate_name
@@ -516,7 +538,7 @@ impl ImportCandidate {
516 Some(_) => None, 538 Some(_) => None,
517 None => Some(Self::TraitMethod(TraitImportCandidate { 539 None => Some(Self::TraitMethod(TraitImportCandidate {
518 receiver_ty: sema.type_of_expr(&method_call.receiver()?)?, 540 receiver_ty: sema.type_of_expr(&method_call.receiver()?)?,
519 name: NameToImport::Exact(method_call.name_ref()?.to_string()), 541 assoc_item_name: NameToImport::Exact(method_call.name_ref()?.to_string()),
520 })), 542 })),
521 } 543 }
522 } 544 }
@@ -559,10 +581,10 @@ fn path_import_candidate(
559 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?; 581 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
560 if sema.resolve_path(&qualifier_start_path).is_none() { 582 if sema.resolve_path(&qualifier_start_path).is_none() {
561 ImportCandidate::Path(PathImportCandidate { 583 ImportCandidate::Path(PathImportCandidate {
562 qualifier: Qualifier::FirstSegmentUnresolved( 584 qualifier: Some(FirstSegmentUnresolved {
563 qualifier_start, 585 fist_segment: qualifier_start,
564 ModPath::from_src_unhygienic(qualifier)?, 586 full_qualifier: ModPath::from_src_unhygienic(qualifier)?,
565 ), 587 }),
566 name, 588 name,
567 }) 589 })
568 } else { 590 } else {
@@ -572,12 +594,12 @@ fn path_import_candidate(
572 Some(PathResolution::Def(ModuleDef::Adt(assoc_item_path))) => { 594 Some(PathResolution::Def(ModuleDef::Adt(assoc_item_path))) => {
573 ImportCandidate::TraitAssocItem(TraitImportCandidate { 595 ImportCandidate::TraitAssocItem(TraitImportCandidate {
574 receiver_ty: assoc_item_path.ty(sema.db), 596 receiver_ty: assoc_item_path.ty(sema.db),
575 name, 597 assoc_item_name: name,
576 }) 598 })
577 } 599 }
578 Some(_) => return None, 600 Some(_) => return None,
579 }, 601 },
580 None => ImportCandidate::Path(PathImportCandidate { qualifier: Qualifier::Absent, name }), 602 None => ImportCandidate::Path(PathImportCandidate { qualifier: None, name }),
581 }) 603 })
582} 604}
583 605