Skip to content

Commit 4c6ca69

Browse files
Veykrilbnjjj
authored andcommitted
Properly qualify trait methods in qualify_path assist
1 parent e397b28 commit 4c6ca69

File tree

5 files changed

+118
-70
lines changed

5 files changed

+118
-70
lines changed

crates/assists/src/handlers/auto_import.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
100100
let group = import_group_message(import_assets.import_candidate());
101101
let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?;
102102
let syntax = scope.as_syntax_node();
103-
for import in proposed_imports {
103+
for (import, _) in proposed_imports {
104104
acc.add_group(
105105
&group,
106106
AssistId("auto_import", AssistKind::QuickFix),

crates/assists/src/handlers/qualify_path.rs

+78-46
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
use std::collections::BTreeSet;
2-
3-
use syntax::{ast, AstNode, TextRange};
1+
use std::iter;
2+
3+
use hir::AsName;
4+
use ide_db::RootDatabase;
5+
use syntax::{
6+
ast,
7+
ast::{make, ArgListOwner},
8+
AstNode, TextRange,
9+
};
410
use test_utils::mark;
511

612
use crate::{
@@ -10,6 +16,8 @@ use crate::{
1016
AssistId, AssistKind, GroupLabel,
1117
};
1218

19+
const ASSIST_ID: AssistId = AssistId("qualify_path", AssistKind::QuickFix);
20+
1321
// Assist: qualify_path
1422
//
1523
// If the name is unresolved, provides all possible qualified paths for it.
@@ -53,30 +61,14 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
5361
ImportCandidate::UnqualifiedName(candidate) => {
5462
qualify_path_unqualified_name(acc, proposed_imports, range, &candidate.name)
5563
}
56-
ImportCandidate::TraitAssocItem(candidate) => {
64+
ImportCandidate::TraitAssocItem(_) => {
5765
let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
5866
let (qualifier, segment) = (path.qualifier()?, path.segment()?);
59-
qualify_path_trait_assoc_item(
60-
acc,
61-
proposed_imports,
62-
range,
63-
qualifier,
64-
segment,
65-
&candidate.name,
66-
)
67+
qualify_path_trait_assoc_item(acc, proposed_imports, range, qualifier, segment)
6768
}
68-
ImportCandidate::TraitMethod(candidate) => {
69+
ImportCandidate::TraitMethod(_) => {
6970
let mcall_expr = ast::MethodCallExpr::cast(import_assets.syntax_under_caret().clone())?;
70-
let receiver = mcall_expr.receiver()?;
71-
let name_ref = mcall_expr.name_ref()?;
72-
qualify_path_trait_method(
73-
acc,
74-
proposed_imports,
75-
range,
76-
receiver,
77-
name_ref,
78-
&candidate.name,
79-
)
71+
qualify_path_trait_method(acc, ctx.sema.db, proposed_imports, range, mcall_expr)?;
8072
}
8173
};
8274
Some(())
@@ -85,17 +77,17 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
8577
// a test that covers this -> `associated_struct_const`
8678
fn qualify_path_qualifier_start(
8779
acc: &mut Assists,
88-
proposed_imports: BTreeSet<hir::ModPath>,
80+
proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>,
8981
range: TextRange,
9082
segment: ast::PathSegment,
91-
qualifier_start: &str,
83+
qualifier_start: &ast::NameRef,
9284
) {
9385
mark::hit!(qualify_path_qualifier_start);
9486
let group_label = GroupLabel(format!("Qualify {}", qualifier_start));
95-
for import in proposed_imports {
87+
for (import, _) in proposed_imports {
9688
acc.add_group(
9789
&group_label,
98-
AssistId("qualify_path", AssistKind::QuickFix),
90+
ASSIST_ID,
9991
format!("Qualify with `{}`", &import),
10092
range,
10193
|builder| {
@@ -109,16 +101,16 @@ fn qualify_path_qualifier_start(
109101
// a test that covers this -> `applicable_when_found_an_import_partial`
110102
fn qualify_path_unqualified_name(
111103
acc: &mut Assists,
112-
proposed_imports: BTreeSet<hir::ModPath>,
104+
proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>,
113105
range: TextRange,
114-
name: &str,
106+
name: &ast::NameRef,
115107
) {
116108
mark::hit!(qualify_path_unqualified_name);
117109
let group_label = GroupLabel(format!("Qualify {}", name));
118-
for import in proposed_imports {
110+
for (import, _) in proposed_imports {
119111
acc.add_group(
120112
&group_label,
121-
AssistId("qualify_path", AssistKind::QuickFix),
113+
ASSIST_ID,
122114
format!("Qualify as `{}`", &import),
123115
range,
124116
|builder| builder.replace(range, mod_path_to_ast(&import).to_string()),
@@ -129,18 +121,17 @@ fn qualify_path_unqualified_name(
129121
// a test that covers this -> `associated_trait_const`
130122
fn qualify_path_trait_assoc_item(
131123
acc: &mut Assists,
132-
proposed_imports: BTreeSet<hir::ModPath>,
124+
proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>,
133125
range: TextRange,
134126
qualifier: ast::Path,
135127
segment: ast::PathSegment,
136-
trait_assoc_item_name: &str,
137128
) {
138129
mark::hit!(qualify_path_trait_assoc_item);
139-
let group_label = GroupLabel(format!("Qualify {}", trait_assoc_item_name));
140-
for import in proposed_imports {
130+
let group_label = GroupLabel(format!("Qualify {}", &segment));
131+
for (import, _) in proposed_imports {
141132
acc.add_group(
142133
&group_label,
143-
AssistId("qualify_path", AssistKind::QuickFix),
134+
ASSIST_ID,
144135
format!("Qualify with cast as `{}`", &import),
145136
range,
146137
|builder| {
@@ -154,33 +145,74 @@ fn qualify_path_trait_assoc_item(
154145
// a test that covers this -> `trait_method`
155146
fn qualify_path_trait_method(
156147
acc: &mut Assists,
157-
proposed_imports: BTreeSet<hir::ModPath>,
148+
db: &RootDatabase,
149+
proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>,
158150
range: TextRange,
159-
receiver: ast::Expr,
160-
name_ref: ast::NameRef,
161-
trait_method_name: &str,
162-
) {
151+
mcall_expr: ast::MethodCallExpr,
152+
) -> Option<()> {
163153
mark::hit!(qualify_path_trait_method);
154+
155+
let receiver = mcall_expr.receiver()?;
156+
let trait_method_name = mcall_expr.name_ref()?;
157+
let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args());
164158
let group_label = GroupLabel(format!("Qualify {}", trait_method_name));
165-
for import in proposed_imports {
159+
let find_method = |item: &hir::AssocItem| {
160+
item.name(db).map(|name| name == trait_method_name.as_name()).unwrap_or(false)
161+
};
162+
for (import, trait_) in proposed_imports.into_iter().filter_map(filter_trait) {
166163
acc.add_group(
167164
&group_label,
168-
AssistId("qualify_path", AssistKind::QuickFix), // < Does this still count as quickfix?
165+
ASSIST_ID,
169166
format!("Qualify `{}`", &import),
170167
range,
171168
|builder| {
172169
let import = mod_path_to_ast(&import);
173-
// TODO: check the receiver self type and emit refs accordingly, don't discard other function parameters
174-
builder.replace(range, format!("{}::{}(&{})", import, name_ref, receiver));
170+
if let Some(hir::AssocItem::Function(method)) =
171+
trait_.items(db).into_iter().find(find_method)
172+
{
173+
if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) {
174+
let receiver = receiver.clone();
175+
let receiver = match self_access {
176+
hir::Access::Shared => make::expr_ref(receiver, false),
177+
hir::Access::Exclusive => make::expr_ref(receiver, true),
178+
hir::Access::Owned => receiver,
179+
};
180+
builder.replace(
181+
range,
182+
format!(
183+
"{}::{}{}",
184+
import,
185+
trait_method_name,
186+
match arg_list.clone() {
187+
Some(args) => make::arg_list(iter::once(receiver).chain(args)),
188+
None => make::arg_list(iter::once(receiver)),
189+
}
190+
),
191+
);
192+
}
193+
}
175194
},
176195
);
177196
}
197+
Some(())
198+
}
199+
200+
fn filter_trait(
201+
(import, trait_): (hir::ModPath, hir::ItemInNs),
202+
) -> Option<(hir::ModPath, hir::Trait)> {
203+
if let hir::ModuleDef::Trait(trait_) = hir::ModuleDef::from(trait_.as_module_def_id()?) {
204+
Some((import, trait_))
205+
} else {
206+
None
207+
}
178208
}
179209

180210
#[cfg(test)]
181211
mod tests {
182-
use super::*;
183212
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
213+
214+
use super::*;
215+
184216
#[test]
185217
fn applicable_when_found_an_import_partial() {
186218
mark::check!(qualify_path_unqualified_name);

crates/assists/src/tests/generated.rs

+19
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,25 @@ fn handle(action: Action) {
712712
)
713713
}
714714

715+
#[test]
716+
fn doctest_qualify_path() {
717+
check_doc_test(
718+
"qualify_path",
719+
r#####"
720+
fn main() {
721+
let map = HashMap<|>::new();
722+
}
723+
pub mod std { pub mod collections { pub struct HashMap { } } }
724+
"#####,
725+
r#####"
726+
fn main() {
727+
let map = std::collections::HashMap::new();
728+
}
729+
pub mod std { pub mod collections { pub struct HashMap { } } }
730+
"#####,
731+
)
732+
}
733+
715734
#[test]
716735
fn doctest_remove_dbg() {
717736
check_doc_test(

crates/assists/src/utils/import_assets.rs

+17-23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
//! Look up accessible paths for items.
2-
use std::collections::BTreeSet;
3-
42
use either::Either;
53
use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics};
64
use ide_db::{imports_locator, RootDatabase};
@@ -29,12 +27,12 @@ pub(crate) enum ImportCandidate {
2927
#[derive(Debug)]
3028
pub(crate) struct TraitImportCandidate {
3129
pub ty: hir::Type,
32-
pub name: String,
30+
pub name: ast::NameRef,
3331
}
3432

3533
#[derive(Debug)]
3634
pub(crate) struct PathImportCandidate {
37-
pub name: String,
35+
pub name: ast::NameRef,
3836
}
3937

4038
#[derive(Debug)]
@@ -86,17 +84,17 @@ impl ImportAssets {
8684
fn get_search_query(&self) -> &str {
8785
match &self.import_candidate {
8886
ImportCandidate::UnqualifiedName(candidate)
89-
| ImportCandidate::QualifierStart(candidate) => &candidate.name,
87+
| ImportCandidate::QualifierStart(candidate) => candidate.name.text(),
9088
ImportCandidate::TraitAssocItem(candidate)
91-
| ImportCandidate::TraitMethod(candidate) => &candidate.name,
89+
| ImportCandidate::TraitMethod(candidate) => candidate.name.text(),
9290
}
9391
}
9492

9593
pub(crate) fn search_for_imports(
9694
&self,
9795
sema: &Semantics<RootDatabase>,
9896
config: &InsertUseConfig,
99-
) -> BTreeSet<hir::ModPath> {
97+
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
10098
let _p = profile::span("import_assists::search_for_imports");
10199
self.search_for(sema, Some(config.prefix_kind))
102100
}
@@ -106,7 +104,7 @@ impl ImportAssets {
106104
pub(crate) fn search_for_relative_paths(
107105
&self,
108106
sema: &Semantics<RootDatabase>,
109-
) -> BTreeSet<hir::ModPath> {
107+
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
110108
let _p = profile::span("import_assists::search_for_relative_paths");
111109
self.search_for(sema, None)
112110
}
@@ -115,7 +113,7 @@ impl ImportAssets {
115113
&self,
116114
sema: &Semantics<RootDatabase>,
117115
prefixed: Option<hir::PrefixKind>,
118-
) -> BTreeSet<hir::ModPath> {
116+
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
119117
let db = sema.db;
120118
let mut trait_candidates = FxHashSet::default();
121119
let current_crate = self.module_with_name_to_import.krate();
@@ -181,7 +179,7 @@ impl ImportAssets {
181179
}
182180
};
183181

184-
imports_locator::find_imports(sema, current_crate, &self.get_search_query())
182+
let mut res = imports_locator::find_imports(sema, current_crate, &self.get_search_query())
185183
.into_iter()
186184
.filter_map(filter)
187185
.filter_map(|candidate| {
@@ -191,10 +189,13 @@ impl ImportAssets {
191189
} else {
192190
self.module_with_name_to_import.find_use_path(db, item)
193191
}
192+
.map(|path| (path, item))
194193
})
195-
.filter(|use_path| !use_path.segments.is_empty())
194+
.filter(|(use_path, _)| !use_path.segments.is_empty())
196195
.take(20)
197-
.collect::<BTreeSet<_>>()
196+
.collect::<Vec<_>>();
197+
res.sort_by_key(|(path, _)| path.clone());
198+
res
198199
}
199200

200201
fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> {
@@ -215,7 +216,7 @@ impl ImportCandidate {
215216
Some(_) => None,
216217
None => Some(Self::TraitMethod(TraitImportCandidate {
217218
ty: sema.type_of_expr(&method_call.receiver()?)?,
218-
name: method_call.name_ref()?.syntax().to_string(),
219+
name: method_call.name_ref()?,
219220
})),
220221
}
221222
}
@@ -243,24 +244,17 @@ impl ImportCandidate {
243244
hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
244245
ImportCandidate::TraitAssocItem(TraitImportCandidate {
245246
ty: assoc_item_path.ty(sema.db),
246-
name: segment.syntax().to_string(),
247+
name: segment.name_ref()?,
247248
})
248249
}
249250
_ => return None,
250251
}
251252
} else {
252-
ImportCandidate::QualifierStart(PathImportCandidate {
253-
name: qualifier_start.syntax().to_string(),
254-
})
253+
ImportCandidate::QualifierStart(PathImportCandidate { name: qualifier_start })
255254
}
256255
} else {
257256
ImportCandidate::UnqualifiedName(PathImportCandidate {
258-
name: segment
259-
.syntax()
260-
.descendants()
261-
.find_map(ast::NameRef::cast)?
262-
.syntax()
263-
.to_string(),
257+
name: segment.syntax().descendants().find_map(ast::NameRef::cast)?,
264258
})
265259
};
266260
Some(candidate)

crates/syntax/src/ast/make.rs

+3
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ pub fn expr_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr {
172172
pub fn expr_method_call(receiver: ast::Expr, method: &str, arg_list: ast::ArgList) -> ast::Expr {
173173
expr_from_text(&format!("{}.{}{}", receiver, method, arg_list))
174174
}
175+
pub fn expr_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr {
176+
expr_from_text(&if exclusive { format!("&mut {}", expr) } else { format!("&{}", expr) })
177+
}
175178
fn expr_from_text(text: &str) -> ast::Expr {
176179
ast_from_text(&format!("const C: () = {};", text))
177180
}

0 commit comments

Comments
 (0)