Skip to content

Commit bcfd297

Browse files
Merge #2727
2727: Qualify paths in 'add impl members' r=flodiebold a=flodiebold This makes the 'add impl members' assist qualify paths, so that they should resolve to the same thing as in the definition. To do that, it adds an algorithm that finds a path to refer to any item from any module (if possible), which is actually probably the more important part of this PR 😄 It handles visibility, reexports, renamed crates, prelude etc.; I think the only thing that's missing is support for local items. I'm not sure about the performance, since it takes into account every location where the target item has been `pub use`d, and then recursively goes up the module tree; there's probably potential for optimization by memoizing more, but I think the general shape of the algorithm is necessary to handle every case in Rust's module system. ~The 'find path' part is actually pretty complete, I think; I'm still working on the assist (hence the failing tests).~ Fixes #1943. Co-authored-by: Florian Diebold <flodiebold@gmail.com> Co-authored-by: Florian Diebold <florian.diebold@freiheit.com>
2 parents e90aa86 + ccb75f7 commit bcfd297

File tree

19 files changed

+995
-85
lines changed

19 files changed

+995
-85
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ra_assists/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ doctest = false
1010
[dependencies]
1111
format-buf = "1.0.0"
1212
join_to_string = "0.1.3"
13+
rustc-hash = "1.0"
1314
itertools = "0.8.0"
1415

1516
ra_syntax = { path = "../ra_syntax" }

crates/ra_assists/src/assists/add_missing_impl_members.rs

Lines changed: 184 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
use std::collections::HashMap;
2-
3-
use hir::{db::HirDatabase, HasSource};
1+
use hir::{db::HirDatabase, HasSource, InFile};
42
use ra_syntax::{
53
ast::{self, edit, make, AstNode, NameOwner},
64
SmolStr,
75
};
86

9-
use crate::{Assist, AssistCtx, AssistId};
7+
use crate::{
8+
ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
9+
Assist, AssistCtx, AssistId,
10+
};
1011

1112
#[derive(PartialEq)]
1213
enum AddMissingImplMembersMode {
@@ -134,25 +135,23 @@ fn add_missing_impl_members_inner(
134135
return None;
135136
}
136137

137-
let file_id = ctx.frange.file_id;
138138
let db = ctx.db;
139+
let file_id = ctx.frange.file_id;
140+
let trait_file_id = trait_.source(db).file_id;
139141

140142
ctx.add_assist(AssistId(assist_id), label, |edit| {
141143
let n_existing_items = impl_item_list.impl_items().count();
142-
let substs = get_syntactic_substs(impl_node).unwrap_or_default();
143-
let generic_def: hir::GenericDef = trait_.into();
144-
let substs_by_param: HashMap<_, _> = generic_def
145-
.params(db)
146-
.into_iter()
147-
// this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
148-
.skip(1)
149-
.zip(substs.into_iter())
150-
.collect();
144+
let module = hir::SourceAnalyzer::new(
145+
db,
146+
hir::InFile::new(file_id.into(), impl_node.syntax()),
147+
None,
148+
)
149+
.module();
150+
let ast_transform = QualifyPaths::new(db, module)
151+
.or(SubstituteTypeParams::for_trait_impl(db, trait_, impl_node));
151152
let items = missing_items
152153
.into_iter()
153-
.map(|it| {
154-
substitute_type_params(db, hir::InFile::new(file_id.into(), it), &substs_by_param)
155-
})
154+
.map(|it| ast_transform::apply(&*ast_transform, InFile::new(trait_file_id, it)))
156155
.map(|it| match it {
157156
ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)),
158157
_ => it,
@@ -177,56 +176,6 @@ fn add_body(fn_def: ast::FnDef) -> ast::FnDef {
177176
}
178177
}
179178

180-
// FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
181-
// trait ref, and then go from the types in the substs back to the syntax)
182-
// FIXME: This should be a general utility (not even just for assists)
183-
fn get_syntactic_substs(impl_block: ast::ImplBlock) -> Option<Vec<ast::TypeRef>> {
184-
let target_trait = impl_block.target_trait()?;
185-
let path_type = match target_trait {
186-
ast::TypeRef::PathType(path) => path,
187-
_ => return None,
188-
};
189-
let type_arg_list = path_type.path()?.segment()?.type_arg_list()?;
190-
let mut result = Vec::new();
191-
for type_arg in type_arg_list.type_args() {
192-
let type_arg: ast::TypeArg = type_arg;
193-
result.push(type_arg.type_ref()?);
194-
}
195-
Some(result)
196-
}
197-
198-
// FIXME: This should be a general utility (not even just for assists)
199-
fn substitute_type_params<N: AstNode>(
200-
db: &impl HirDatabase,
201-
node: hir::InFile<N>,
202-
substs: &HashMap<hir::TypeParam, ast::TypeRef>,
203-
) -> N {
204-
let type_param_replacements = node
205-
.value
206-
.syntax()
207-
.descendants()
208-
.filter_map(ast::TypeRef::cast)
209-
.filter_map(|n| {
210-
let path = match &n {
211-
ast::TypeRef::PathType(path_type) => path_type.path()?,
212-
_ => return None,
213-
};
214-
let analyzer = hir::SourceAnalyzer::new(db, node.with_value(n.syntax()), None);
215-
let resolution = analyzer.resolve_path(db, &path)?;
216-
match resolution {
217-
hir::PathResolution::TypeParam(tp) => Some((n, substs.get(&tp)?.clone())),
218-
_ => None,
219-
}
220-
})
221-
.collect::<Vec<_>>();
222-
223-
if type_param_replacements.is_empty() {
224-
node.value
225-
} else {
226-
edit::replace_descendants(&node.value, type_param_replacements.into_iter())
227-
}
228-
}
229-
230179
/// Given an `ast::ImplBlock`, resolves the target trait (the one being
231180
/// implemented) to a `ast::TraitDef`.
232181
fn resolve_target_trait_def(
@@ -400,6 +349,174 @@ impl Foo for S {
400349
)
401350
}
402351

352+
#[test]
353+
fn test_qualify_path_1() {
354+
check_assist(
355+
add_missing_impl_members,
356+
"
357+
mod foo {
358+
pub struct Bar;
359+
trait Foo { fn foo(&self, bar: Bar); }
360+
}
361+
struct S;
362+
impl foo::Foo for S { <|> }",
363+
"
364+
mod foo {
365+
pub struct Bar;
366+
trait Foo { fn foo(&self, bar: Bar); }
367+
}
368+
struct S;
369+
impl foo::Foo for S {
370+
<|>fn foo(&self, bar: foo::Bar) { unimplemented!() }
371+
}",
372+
);
373+
}
374+
375+
#[test]
376+
fn test_qualify_path_generic() {
377+
check_assist(
378+
add_missing_impl_members,
379+
"
380+
mod foo {
381+
pub struct Bar<T>;
382+
trait Foo { fn foo(&self, bar: Bar<u32>); }
383+
}
384+
struct S;
385+
impl foo::Foo for S { <|> }",
386+
"
387+
mod foo {
388+
pub struct Bar<T>;
389+
trait Foo { fn foo(&self, bar: Bar<u32>); }
390+
}
391+
struct S;
392+
impl foo::Foo for S {
393+
<|>fn foo(&self, bar: foo::Bar<u32>) { unimplemented!() }
394+
}",
395+
);
396+
}
397+
398+
#[test]
399+
fn test_qualify_path_and_substitute_param() {
400+
check_assist(
401+
add_missing_impl_members,
402+
"
403+
mod foo {
404+
pub struct Bar<T>;
405+
trait Foo<T> { fn foo(&self, bar: Bar<T>); }
406+
}
407+
struct S;
408+
impl foo::Foo<u32> for S { <|> }",
409+
"
410+
mod foo {
411+
pub struct Bar<T>;
412+
trait Foo<T> { fn foo(&self, bar: Bar<T>); }
413+
}
414+
struct S;
415+
impl foo::Foo<u32> for S {
416+
<|>fn foo(&self, bar: foo::Bar<u32>) { unimplemented!() }
417+
}",
418+
);
419+
}
420+
421+
#[test]
422+
fn test_substitute_param_no_qualify() {
423+
// when substituting params, the substituted param should not be qualified!
424+
check_assist(
425+
add_missing_impl_members,
426+
"
427+
mod foo {
428+
trait Foo<T> { fn foo(&self, bar: T); }
429+
pub struct Param;
430+
}
431+
struct Param;
432+
struct S;
433+
impl foo::Foo<Param> for S { <|> }",
434+
"
435+
mod foo {
436+
trait Foo<T> { fn foo(&self, bar: T); }
437+
pub struct Param;
438+
}
439+
struct Param;
440+
struct S;
441+
impl foo::Foo<Param> for S {
442+
<|>fn foo(&self, bar: Param) { unimplemented!() }
443+
}",
444+
);
445+
}
446+
447+
#[test]
448+
fn test_qualify_path_associated_item() {
449+
check_assist(
450+
add_missing_impl_members,
451+
"
452+
mod foo {
453+
pub struct Bar<T>;
454+
impl Bar<T> { type Assoc = u32; }
455+
trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
456+
}
457+
struct S;
458+
impl foo::Foo for S { <|> }",
459+
"
460+
mod foo {
461+
pub struct Bar<T>;
462+
impl Bar<T> { type Assoc = u32; }
463+
trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
464+
}
465+
struct S;
466+
impl foo::Foo for S {
467+
<|>fn foo(&self, bar: foo::Bar<u32>::Assoc) { unimplemented!() }
468+
}",
469+
);
470+
}
471+
472+
#[test]
473+
fn test_qualify_path_nested() {
474+
check_assist(
475+
add_missing_impl_members,
476+
"
477+
mod foo {
478+
pub struct Bar<T>;
479+
pub struct Baz;
480+
trait Foo { fn foo(&self, bar: Bar<Baz>); }
481+
}
482+
struct S;
483+
impl foo::Foo for S { <|> }",
484+
"
485+
mod foo {
486+
pub struct Bar<T>;
487+
pub struct Baz;
488+
trait Foo { fn foo(&self, bar: Bar<Baz>); }
489+
}
490+
struct S;
491+
impl foo::Foo for S {
492+
<|>fn foo(&self, bar: foo::Bar<foo::Baz>) { unimplemented!() }
493+
}",
494+
);
495+
}
496+
497+
#[test]
498+
fn test_qualify_path_fn_trait_notation() {
499+
check_assist(
500+
add_missing_impl_members,
501+
"
502+
mod foo {
503+
pub trait Fn<Args> { type Output; }
504+
trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
505+
}
506+
struct S;
507+
impl foo::Foo for S { <|> }",
508+
"
509+
mod foo {
510+
pub trait Fn<Args> { type Output; }
511+
trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
512+
}
513+
struct S;
514+
impl foo::Foo for S {
515+
<|>fn foo(&self, bar: dyn Fn(u32) -> i32) { unimplemented!() }
516+
}",
517+
);
518+
}
519+
403520
#[test]
404521
fn test_empty_trait() {
405522
check_assist_not_applicable(

0 commit comments

Comments
 (0)