aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-01-11 22:42:39 +0000
committerGitHub <[email protected]>2020-01-11 22:42:39 +0000
commitbcfd297f4910bbf2305ec859d7cf42b7dca25f57 (patch)
treec6b53cd774e7baacf213e91b295734852a83f40b /crates/ra_assists
parente90aa86fbfa716c4028f38d0d22654065011a964 (diff)
parentccb75f7c979b56bc62b61fadd81903e11a7f5d74 (diff)
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 :smile: 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 <[email protected]> Co-authored-by: Florian Diebold <[email protected]>
Diffstat (limited to 'crates/ra_assists')
-rw-r--r--crates/ra_assists/Cargo.toml1
-rw-r--r--crates/ra_assists/src/assists/add_missing_impl_members.rs251
-rw-r--r--crates/ra_assists/src/ast_transform.rs179
-rw-r--r--crates/ra_assists/src/lib.rs1
4 files changed, 365 insertions, 67 deletions
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml
index 434e6656c..50be8d9bc 100644
--- a/crates/ra_assists/Cargo.toml
+++ b/crates/ra_assists/Cargo.toml
@@ -10,6 +10,7 @@ doctest = false
10[dependencies] 10[dependencies]
11format-buf = "1.0.0" 11format-buf = "1.0.0"
12join_to_string = "0.1.3" 12join_to_string = "0.1.3"
13rustc-hash = "1.0"
13itertools = "0.8.0" 14itertools = "0.8.0"
14 15
15ra_syntax = { path = "../ra_syntax" } 16ra_syntax = { path = "../ra_syntax" }
diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/assists/add_missing_impl_members.rs
index bc49e71fe..bf1136193 100644
--- a/crates/ra_assists/src/assists/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs
@@ -1,12 +1,13 @@
1use std::collections::HashMap; 1use hir::{db::HirDatabase, HasSource, InFile};
2
3use hir::{db::HirDatabase, HasSource};
4use ra_syntax::{ 2use ra_syntax::{
5 ast::{self, edit, make, AstNode, NameOwner}, 3 ast::{self, edit, make, AstNode, NameOwner},
6 SmolStr, 4 SmolStr,
7}; 5};
8 6
9use crate::{Assist, AssistCtx, AssistId}; 7use crate::{
8 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
9 Assist, AssistCtx, AssistId,
10};
10 11
11#[derive(PartialEq)] 12#[derive(PartialEq)]
12enum AddMissingImplMembersMode { 13enum AddMissingImplMembersMode {
@@ -134,25 +135,23 @@ fn add_missing_impl_members_inner(
134 return None; 135 return None;
135 } 136 }
136 137
137 let file_id = ctx.frange.file_id;
138 let db = ctx.db; 138 let db = ctx.db;
139 let file_id = ctx.frange.file_id;
140 let trait_file_id = trait_.source(db).file_id;
139 141
140 ctx.add_assist(AssistId(assist_id), label, |edit| { 142 ctx.add_assist(AssistId(assist_id), label, |edit| {
141 let n_existing_items = impl_item_list.impl_items().count(); 143 let n_existing_items = impl_item_list.impl_items().count();
142 let substs = get_syntactic_substs(impl_node).unwrap_or_default(); 144 let module = hir::SourceAnalyzer::new(
143 let generic_def: hir::GenericDef = trait_.into(); 145 db,
144 let substs_by_param: HashMap<_, _> = generic_def 146 hir::InFile::new(file_id.into(), impl_node.syntax()),
145 .params(db) 147 None,
146 .into_iter() 148 )
147 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky 149 .module();
148 .skip(1) 150 let ast_transform = QualifyPaths::new(db, module)
149 .zip(substs.into_iter()) 151 .or(SubstituteTypeParams::for_trait_impl(db, trait_, impl_node));
150 .collect();
151 let items = missing_items 152 let items = missing_items
152 .into_iter() 153 .into_iter()
153 .map(|it| { 154 .map(|it| ast_transform::apply(&*ast_transform, InFile::new(trait_file_id, it)))
154 substitute_type_params(db, hir::InFile::new(file_id.into(), it), &substs_by_param)
155 })
156 .map(|it| match it { 155 .map(|it| match it {
157 ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)), 156 ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)),
158 _ => it, 157 _ => it,
@@ -177,56 +176,6 @@ fn add_body(fn_def: ast::FnDef) -> ast::FnDef {
177 } 176 }
178} 177}
179 178
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)
183fn 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)
199fn 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
230/// Given an `ast::ImplBlock`, resolves the target trait (the one being 179/// Given an `ast::ImplBlock`, resolves the target trait (the one being
231/// implemented) to a `ast::TraitDef`. 180/// implemented) to a `ast::TraitDef`.
232fn resolve_target_trait_def( 181fn resolve_target_trait_def(
@@ -401,6 +350,174 @@ impl Foo for S {
401 } 350 }
402 351
403 #[test] 352 #[test]
353 fn test_qualify_path_1() {
354 check_assist(
355 add_missing_impl_members,
356 "
357mod foo {
358 pub struct Bar;
359 trait Foo { fn foo(&self, bar: Bar); }
360}
361struct S;
362impl foo::Foo for S { <|> }",
363 "
364mod foo {
365 pub struct Bar;
366 trait Foo { fn foo(&self, bar: Bar); }
367}
368struct S;
369impl 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 "
380mod foo {
381 pub struct Bar<T>;
382 trait Foo { fn foo(&self, bar: Bar<u32>); }
383}
384struct S;
385impl foo::Foo for S { <|> }",
386 "
387mod foo {
388 pub struct Bar<T>;
389 trait Foo { fn foo(&self, bar: Bar<u32>); }
390}
391struct S;
392impl 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 "
403mod foo {
404 pub struct Bar<T>;
405 trait Foo<T> { fn foo(&self, bar: Bar<T>); }
406}
407struct S;
408impl foo::Foo<u32> for S { <|> }",
409 "
410mod foo {
411 pub struct Bar<T>;
412 trait Foo<T> { fn foo(&self, bar: Bar<T>); }
413}
414struct S;
415impl 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 "
427mod foo {
428 trait Foo<T> { fn foo(&self, bar: T); }
429 pub struct Param;
430}
431struct Param;
432struct S;
433impl foo::Foo<Param> for S { <|> }",
434 "
435mod foo {
436 trait Foo<T> { fn foo(&self, bar: T); }
437 pub struct Param;
438}
439struct Param;
440struct S;
441impl 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 "
452mod foo {
453 pub struct Bar<T>;
454 impl Bar<T> { type Assoc = u32; }
455 trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
456}
457struct S;
458impl foo::Foo for S { <|> }",
459 "
460mod foo {
461 pub struct Bar<T>;
462 impl Bar<T> { type Assoc = u32; }
463 trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
464}
465struct S;
466impl 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 "
477mod foo {
478 pub struct Bar<T>;
479 pub struct Baz;
480 trait Foo { fn foo(&self, bar: Bar<Baz>); }
481}
482struct S;
483impl foo::Foo for S { <|> }",
484 "
485mod foo {
486 pub struct Bar<T>;
487 pub struct Baz;
488 trait Foo { fn foo(&self, bar: Bar<Baz>); }
489}
490struct S;
491impl 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 "
502mod foo {
503 pub trait Fn<Args> { type Output; }
504 trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
505}
506struct S;
507impl foo::Foo for S { <|> }",
508 "
509mod foo {
510 pub trait Fn<Args> { type Output; }
511 trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
512}
513struct S;
514impl foo::Foo for S {
515 <|>fn foo(&self, bar: dyn Fn(u32) -> i32) { unimplemented!() }
516}",
517 );
518 }
519
520 #[test]
404 fn test_empty_trait() { 521 fn test_empty_trait() {
405 check_assist_not_applicable( 522 check_assist_not_applicable(
406 add_missing_impl_members, 523 add_missing_impl_members,
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs
new file mode 100644
index 000000000..cbddc50ac
--- /dev/null
+++ b/crates/ra_assists/src/ast_transform.rs
@@ -0,0 +1,179 @@
1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined.
2use rustc_hash::FxHashMap;
3
4use hir::{db::HirDatabase, InFile, PathResolution};
5use ra_syntax::ast::{self, make, AstNode};
6
7pub trait AstTransform<'a> {
8 fn get_substitution(
9 &self,
10 node: InFile<&ra_syntax::SyntaxNode>,
11 ) -> Option<ra_syntax::SyntaxNode>;
12
13 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a>;
14 fn or<T: AstTransform<'a> + 'a>(self, other: T) -> Box<dyn AstTransform<'a> + 'a>
15 where
16 Self: Sized + 'a,
17 {
18 self.chain_before(Box::new(other))
19 }
20}
21
22struct NullTransformer;
23
24impl<'a> AstTransform<'a> for NullTransformer {
25 fn get_substitution(
26 &self,
27 _node: InFile<&ra_syntax::SyntaxNode>,
28 ) -> Option<ra_syntax::SyntaxNode> {
29 None
30 }
31 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
32 other
33 }
34}
35
36pub struct SubstituteTypeParams<'a, DB: HirDatabase> {
37 db: &'a DB,
38 substs: FxHashMap<hir::TypeParam, ast::TypeRef>,
39 previous: Box<dyn AstTransform<'a> + 'a>,
40}
41
42impl<'a, DB: HirDatabase> SubstituteTypeParams<'a, DB> {
43 pub fn for_trait_impl(
44 db: &'a DB,
45 trait_: hir::Trait,
46 impl_block: ast::ImplBlock,
47 ) -> SubstituteTypeParams<'a, DB> {
48 let substs = get_syntactic_substs(impl_block).unwrap_or_default();
49 let generic_def: hir::GenericDef = trait_.into();
50 let substs_by_param: FxHashMap<_, _> = generic_def
51 .params(db)
52 .into_iter()
53 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
54 .skip(1)
55 .zip(substs.into_iter())
56 .collect();
57 return SubstituteTypeParams {
58 db,
59 substs: substs_by_param,
60 previous: Box::new(NullTransformer),
61 };
62
63 // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
64 // trait ref, and then go from the types in the substs back to the syntax)
65 fn get_syntactic_substs(impl_block: ast::ImplBlock) -> Option<Vec<ast::TypeRef>> {
66 let target_trait = impl_block.target_trait()?;
67 let path_type = match target_trait {
68 ast::TypeRef::PathType(path) => path,
69 _ => return None,
70 };
71 let type_arg_list = path_type.path()?.segment()?.type_arg_list()?;
72 let mut result = Vec::new();
73 for type_arg in type_arg_list.type_args() {
74 let type_arg: ast::TypeArg = type_arg;
75 result.push(type_arg.type_ref()?);
76 }
77 Some(result)
78 }
79 }
80 fn get_substitution_inner(
81 &self,
82 node: InFile<&ra_syntax::SyntaxNode>,
83 ) -> Option<ra_syntax::SyntaxNode> {
84 let type_ref = ast::TypeRef::cast(node.value.clone())?;
85 let path = match &type_ref {
86 ast::TypeRef::PathType(path_type) => path_type.path()?,
87 _ => return None,
88 };
89 let analyzer = hir::SourceAnalyzer::new(self.db, node, None);
90 let resolution = analyzer.resolve_path(self.db, &path)?;
91 match resolution {
92 hir::PathResolution::TypeParam(tp) => Some(self.substs.get(&tp)?.syntax().clone()),
93 _ => None,
94 }
95 }
96}
97
98impl<'a, DB: HirDatabase> AstTransform<'a> for SubstituteTypeParams<'a, DB> {
99 fn get_substitution(
100 &self,
101 node: InFile<&ra_syntax::SyntaxNode>,
102 ) -> Option<ra_syntax::SyntaxNode> {
103 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
104 }
105 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
106 Box::new(SubstituteTypeParams { previous: other, ..self })
107 }
108}
109
110pub struct QualifyPaths<'a, DB: HirDatabase> {
111 db: &'a DB,
112 from: Option<hir::Module>,
113 previous: Box<dyn AstTransform<'a> + 'a>,
114}
115
116impl<'a, DB: HirDatabase> QualifyPaths<'a, DB> {
117 pub fn new(db: &'a DB, from: Option<hir::Module>) -> Self {
118 Self { db, from, previous: Box::new(NullTransformer) }
119 }
120
121 fn get_substitution_inner(
122 &self,
123 node: InFile<&ra_syntax::SyntaxNode>,
124 ) -> Option<ra_syntax::SyntaxNode> {
125 // FIXME handle value ns?
126 let from = self.from?;
127 let p = ast::Path::cast(node.value.clone())?;
128 if p.segment().and_then(|s| s.param_list()).is_some() {
129 // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
130 return None;
131 }
132 let analyzer = hir::SourceAnalyzer::new(self.db, node, None);
133 let resolution = analyzer.resolve_path(self.db, &p)?;
134 match resolution {
135 PathResolution::Def(def) => {
136 let found_path = from.find_use_path(self.db, def)?;
137 let args = p
138 .segment()
139 .and_then(|s| s.type_arg_list())
140 .map(|arg_list| apply(self, node.with_value(arg_list)));
141 Some(make::path_with_type_arg_list(path_to_ast(found_path), args).syntax().clone())
142 }
143 PathResolution::Local(_)
144 | PathResolution::TypeParam(_)
145 | PathResolution::SelfType(_) => None,
146 PathResolution::Macro(_) => None,
147 PathResolution::AssocItem(_) => None,
148 }
149 }
150}
151
152pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: InFile<N>) -> N {
153 let syntax = node.value.syntax();
154 let result = ra_syntax::algo::replace_descendants(syntax, &|element| match element {
155 ra_syntax::SyntaxElement::Node(n) => {
156 let replacement = transformer.get_substitution(node.with_value(&n))?;
157 Some(replacement.into())
158 }
159 _ => None,
160 });
161 N::cast(result).unwrap()
162}
163
164impl<'a, DB: HirDatabase> AstTransform<'a> for QualifyPaths<'a, DB> {
165 fn get_substitution(
166 &self,
167 node: InFile<&ra_syntax::SyntaxNode>,
168 ) -> Option<ra_syntax::SyntaxNode> {
169 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
170 }
171 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
172 Box::new(QualifyPaths { previous: other, ..self })
173 }
174}
175
176fn path_to_ast(path: hir::ModPath) -> ast::Path {
177 let parse = ast::SourceFile::parse(&path.to_string());
178 parse.tree().syntax().descendants().find_map(ast::Path::cast).unwrap()
179}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 98fb20b22..712ff6f6a 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -11,6 +11,7 @@ mod marks;
11mod doc_tests; 11mod doc_tests;
12#[cfg(test)] 12#[cfg(test)]
13mod test_db; 13mod test_db;
14pub mod ast_transform;
14 15
15use hir::db::HirDatabase; 16use hir::db::HirDatabase;
16use ra_db::FileRange; 17use ra_db::FileRange;