diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-01-11 22:42:39 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-01-11 22:42:39 +0000 |
commit | bcfd297f4910bbf2305ec859d7cf42b7dca25f57 (patch) | |
tree | c6b53cd774e7baacf213e91b295734852a83f40b /crates | |
parent | e90aa86fbfa716c4028f38d0d22654065011a964 (diff) | |
parent | ccb75f7c979b56bc62b61fadd81903e11a7f5d74 (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')
-rw-r--r-- | crates/ra_assists/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_assists/src/assists/add_missing_impl_members.rs | 251 | ||||
-rw-r--r-- | crates/ra_assists/src/ast_transform.rs | 179 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/ra_hir/src/code_model.rs | 15 | ||||
-rw-r--r-- | crates/ra_hir/src/from_id.rs | 16 | ||||
-rw-r--r-- | crates/ra_hir/src/source_binder.rs | 4 | ||||
-rw-r--r-- | crates/ra_hir_def/src/find_path.rs | 455 | ||||
-rw-r--r-- | crates/ra_hir_def/src/item_scope.rs | 39 | ||||
-rw-r--r-- | crates/ra_hir_def/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/ra_hir_def/src/path.rs | 42 | ||||
-rw-r--r-- | crates/ra_hir_def/src/resolver.rs | 15 | ||||
-rw-r--r-- | crates/ra_hir_def/src/test_db.rs | 13 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/lib.rs | 10 | ||||
-rw-r--r-- | crates/ra_ide/src/expand_macro.rs | 7 | ||||
-rw-r--r-- | crates/ra_syntax/src/algo.rs | 6 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/edit.rs | 8 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 16 |
18 files changed, 994 insertions, 85 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] |
11 | format-buf = "1.0.0" | 11 | format-buf = "1.0.0" |
12 | join_to_string = "0.1.3" | 12 | join_to_string = "0.1.3" |
13 | rustc-hash = "1.0" | ||
13 | itertools = "0.8.0" | 14 | itertools = "0.8.0" |
14 | 15 | ||
15 | ra_syntax = { path = "../ra_syntax" } | 16 | ra_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 @@ | |||
1 | use std::collections::HashMap; | 1 | use hir::{db::HirDatabase, HasSource, InFile}; |
2 | |||
3 | use hir::{db::HirDatabase, HasSource}; | ||
4 | use ra_syntax::{ | 2 | use ra_syntax::{ |
5 | ast::{self, edit, make, AstNode, NameOwner}, | 3 | ast::{self, edit, make, AstNode, NameOwner}, |
6 | SmolStr, | 4 | SmolStr, |
7 | }; | 5 | }; |
8 | 6 | ||
9 | use crate::{Assist, AssistCtx, AssistId}; | 7 | use crate::{ |
8 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | ||
9 | Assist, AssistCtx, AssistId, | ||
10 | }; | ||
10 | 11 | ||
11 | #[derive(PartialEq)] | 12 | #[derive(PartialEq)] |
12 | enum AddMissingImplMembersMode { | 13 | enum 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) | ||
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 | |||
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`. |
232 | fn resolve_target_trait_def( | 181 | fn 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 | " | ||
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 | |||
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. | ||
2 | use rustc_hash::FxHashMap; | ||
3 | |||
4 | use hir::{db::HirDatabase, InFile, PathResolution}; | ||
5 | use ra_syntax::ast::{self, make, AstNode}; | ||
6 | |||
7 | pub 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 | |||
22 | struct NullTransformer; | ||
23 | |||
24 | impl<'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 | |||
36 | pub 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 | |||
42 | impl<'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 | |||
98 | impl<'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 | |||
110 | pub struct QualifyPaths<'a, DB: HirDatabase> { | ||
111 | db: &'a DB, | ||
112 | from: Option<hir::Module>, | ||
113 | previous: Box<dyn AstTransform<'a> + 'a>, | ||
114 | } | ||
115 | |||
116 | impl<'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 | |||
152 | pub 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 | |||
164 | impl<'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 | |||
176 | fn 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; | |||
11 | mod doc_tests; | 11 | mod doc_tests; |
12 | #[cfg(test)] | 12 | #[cfg(test)] |
13 | mod test_db; | 13 | mod test_db; |
14 | pub mod ast_transform; | ||
14 | 15 | ||
15 | use hir::db::HirDatabase; | 16 | use hir::db::HirDatabase; |
16 | use ra_db::FileRange; | 17 | use ra_db::FileRange; |
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index cc42068a1..df9c151e5 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs | |||
@@ -227,6 +227,21 @@ impl Module { | |||
227 | pub(crate) fn with_module_id(self, module_id: LocalModuleId) -> Module { | 227 | pub(crate) fn with_module_id(self, module_id: LocalModuleId) -> Module { |
228 | Module::new(self.krate(), module_id) | 228 | Module::new(self.krate(), module_id) |
229 | } | 229 | } |
230 | |||
231 | /// Finds a path that can be used to refer to the given item from within | ||
232 | /// this module, if possible. | ||
233 | pub fn find_use_path( | ||
234 | self, | ||
235 | db: &impl DefDatabase, | ||
236 | item: ModuleDef, | ||
237 | ) -> Option<hir_def::path::ModPath> { | ||
238 | // FIXME expose namespace choice | ||
239 | hir_def::find_path::find_path( | ||
240 | db, | ||
241 | hir_def::item_scope::ItemInNs::Types(item.into()), | ||
242 | self.into(), | ||
243 | ) | ||
244 | } | ||
230 | } | 245 | } |
231 | 246 | ||
232 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | 247 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
diff --git a/crates/ra_hir/src/from_id.rs b/crates/ra_hir/src/from_id.rs index 75a1a7772..c16c17072 100644 --- a/crates/ra_hir/src/from_id.rs +++ b/crates/ra_hir/src/from_id.rs | |||
@@ -91,6 +91,22 @@ impl From<ModuleDefId> for ModuleDef { | |||
91 | } | 91 | } |
92 | } | 92 | } |
93 | 93 | ||
94 | impl From<ModuleDef> for ModuleDefId { | ||
95 | fn from(id: ModuleDef) -> Self { | ||
96 | match id { | ||
97 | ModuleDef::Module(it) => ModuleDefId::ModuleId(it.into()), | ||
98 | ModuleDef::Function(it) => ModuleDefId::FunctionId(it.into()), | ||
99 | ModuleDef::Adt(it) => ModuleDefId::AdtId(it.into()), | ||
100 | ModuleDef::EnumVariant(it) => ModuleDefId::EnumVariantId(it.into()), | ||
101 | ModuleDef::Const(it) => ModuleDefId::ConstId(it.into()), | ||
102 | ModuleDef::Static(it) => ModuleDefId::StaticId(it.into()), | ||
103 | ModuleDef::Trait(it) => ModuleDefId::TraitId(it.into()), | ||
104 | ModuleDef::TypeAlias(it) => ModuleDefId::TypeAliasId(it.into()), | ||
105 | ModuleDef::BuiltinType(it) => ModuleDefId::BuiltinType(it), | ||
106 | } | ||
107 | } | ||
108 | } | ||
109 | |||
94 | impl From<DefWithBody> for DefWithBodyId { | 110 | impl From<DefWithBody> for DefWithBodyId { |
95 | fn from(def: DefWithBody) -> Self { | 111 | fn from(def: DefWithBody) -> Self { |
96 | match def { | 112 | match def { |
diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index 2c422af8b..a2a9d968c 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs | |||
@@ -205,6 +205,10 @@ impl SourceAnalyzer { | |||
205 | } | 205 | } |
206 | } | 206 | } |
207 | 207 | ||
208 | pub fn module(&self) -> Option<crate::code_model::Module> { | ||
209 | Some(crate::code_model::Module { id: self.resolver.module()? }) | ||
210 | } | ||
211 | |||
208 | fn expr_id(&self, expr: &ast::Expr) -> Option<ExprId> { | 212 | fn expr_id(&self, expr: &ast::Expr) -> Option<ExprId> { |
209 | let src = InFile { file_id: self.file_id, value: expr }; | 213 | let src = InFile { file_id: self.file_id, value: expr }; |
210 | self.body_source_map.as_ref()?.node_expr(src) | 214 | self.body_source_map.as_ref()?.node_expr(src) |
diff --git a/crates/ra_hir_def/src/find_path.rs b/crates/ra_hir_def/src/find_path.rs new file mode 100644 index 000000000..f7dc8acb7 --- /dev/null +++ b/crates/ra_hir_def/src/find_path.rs | |||
@@ -0,0 +1,455 @@ | |||
1 | //! An algorithm to find a path to refer to a certain item. | ||
2 | |||
3 | use crate::{ | ||
4 | db::DefDatabase, | ||
5 | item_scope::ItemInNs, | ||
6 | path::{ModPath, PathKind}, | ||
7 | visibility::Visibility, | ||
8 | CrateId, ModuleDefId, ModuleId, | ||
9 | }; | ||
10 | use hir_expand::name::Name; | ||
11 | |||
12 | const MAX_PATH_LEN: usize = 15; | ||
13 | |||
14 | // FIXME: handle local items | ||
15 | |||
16 | /// Find a path that can be used to refer to a certain item. This can depend on | ||
17 | /// *from where* you're referring to the item, hence the `from` parameter. | ||
18 | pub fn find_path(db: &impl DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> { | ||
19 | find_path_inner(db, item, from, MAX_PATH_LEN) | ||
20 | } | ||
21 | |||
22 | fn find_path_inner( | ||
23 | db: &impl DefDatabase, | ||
24 | item: ItemInNs, | ||
25 | from: ModuleId, | ||
26 | max_len: usize, | ||
27 | ) -> Option<ModPath> { | ||
28 | if max_len == 0 { | ||
29 | return None; | ||
30 | } | ||
31 | |||
32 | // Base cases: | ||
33 | |||
34 | // - if the item is already in scope, return the name under which it is | ||
35 | let def_map = db.crate_def_map(from.krate); | ||
36 | let from_scope: &crate::item_scope::ItemScope = &def_map.modules[from.local_id].scope; | ||
37 | if let Some((name, _)) = from_scope.name_of(item) { | ||
38 | return Some(ModPath::from_simple_segments(PathKind::Plain, vec![name.clone()])); | ||
39 | } | ||
40 | |||
41 | // - if the item is the crate root, return `crate` | ||
42 | if item | ||
43 | == ItemInNs::Types(ModuleDefId::ModuleId(ModuleId { | ||
44 | krate: from.krate, | ||
45 | local_id: def_map.root, | ||
46 | })) | ||
47 | { | ||
48 | return Some(ModPath::from_simple_segments(PathKind::Crate, Vec::new())); | ||
49 | } | ||
50 | |||
51 | // - if the item is the module we're in, use `self` | ||
52 | if item == ItemInNs::Types(from.into()) { | ||
53 | return Some(ModPath::from_simple_segments(PathKind::Super(0), Vec::new())); | ||
54 | } | ||
55 | |||
56 | // - if the item is the parent module, use `super` (this is not used recursively, since `super::super` is ugly) | ||
57 | if let Some(parent_id) = def_map.modules[from.local_id].parent { | ||
58 | if item | ||
59 | == ItemInNs::Types(ModuleDefId::ModuleId(ModuleId { | ||
60 | krate: from.krate, | ||
61 | local_id: parent_id, | ||
62 | })) | ||
63 | { | ||
64 | return Some(ModPath::from_simple_segments(PathKind::Super(1), Vec::new())); | ||
65 | } | ||
66 | } | ||
67 | |||
68 | // - if the item is the crate root of a dependency crate, return the name from the extern prelude | ||
69 | for (name, def_id) in &def_map.extern_prelude { | ||
70 | if item == ItemInNs::Types(*def_id) { | ||
71 | return Some(ModPath::from_simple_segments(PathKind::Plain, vec![name.clone()])); | ||
72 | } | ||
73 | } | ||
74 | |||
75 | // - if the item is in the prelude, return the name from there | ||
76 | if let Some(prelude_module) = def_map.prelude { | ||
77 | let prelude_def_map = db.crate_def_map(prelude_module.krate); | ||
78 | let prelude_scope: &crate::item_scope::ItemScope = | ||
79 | &prelude_def_map.modules[prelude_module.local_id].scope; | ||
80 | if let Some((name, vis)) = prelude_scope.name_of(item) { | ||
81 | if vis.is_visible_from(db, from) { | ||
82 | return Some(ModPath::from_simple_segments(PathKind::Plain, vec![name.clone()])); | ||
83 | } | ||
84 | } | ||
85 | } | ||
86 | |||
87 | // Recursive case: | ||
88 | // - if the item is an enum variant, refer to it via the enum | ||
89 | if let Some(ModuleDefId::EnumVariantId(variant)) = item.as_module_def_id() { | ||
90 | if let Some(mut path) = find_path(db, ItemInNs::Types(variant.parent.into()), from) { | ||
91 | let data = db.enum_data(variant.parent); | ||
92 | path.segments.push(data.variants[variant.local_id].name.clone()); | ||
93 | return Some(path); | ||
94 | } | ||
95 | // If this doesn't work, it seems we have no way of referring to the | ||
96 | // enum; that's very weird, but there might still be a reexport of the | ||
97 | // variant somewhere | ||
98 | } | ||
99 | |||
100 | // - otherwise, look for modules containing (reexporting) it and import it from one of those | ||
101 | let importable_locations = find_importable_locations(db, item, from); | ||
102 | let mut best_path = None; | ||
103 | let mut best_path_len = max_len; | ||
104 | for (module_id, name) in importable_locations { | ||
105 | let mut path = match find_path_inner( | ||
106 | db, | ||
107 | ItemInNs::Types(ModuleDefId::ModuleId(module_id)), | ||
108 | from, | ||
109 | best_path_len - 1, | ||
110 | ) { | ||
111 | None => continue, | ||
112 | Some(path) => path, | ||
113 | }; | ||
114 | path.segments.push(name); | ||
115 | if path_len(&path) < best_path_len { | ||
116 | best_path_len = path_len(&path); | ||
117 | best_path = Some(path); | ||
118 | } | ||
119 | } | ||
120 | best_path | ||
121 | } | ||
122 | |||
123 | fn path_len(path: &ModPath) -> usize { | ||
124 | path.segments.len() | ||
125 | + match path.kind { | ||
126 | PathKind::Plain => 0, | ||
127 | PathKind::Super(i) => i as usize, | ||
128 | PathKind::Crate => 1, | ||
129 | PathKind::Abs => 0, | ||
130 | PathKind::DollarCrate(_) => 1, | ||
131 | } | ||
132 | } | ||
133 | |||
134 | fn find_importable_locations( | ||
135 | db: &impl DefDatabase, | ||
136 | item: ItemInNs, | ||
137 | from: ModuleId, | ||
138 | ) -> Vec<(ModuleId, Name)> { | ||
139 | let crate_graph = db.crate_graph(); | ||
140 | let mut result = Vec::new(); | ||
141 | // We only look in the crate from which we are importing, and the direct | ||
142 | // dependencies. We cannot refer to names from transitive dependencies | ||
143 | // directly (only through reexports in direct dependencies). | ||
144 | for krate in Some(from.krate) | ||
145 | .into_iter() | ||
146 | .chain(crate_graph.dependencies(from.krate).map(|dep| dep.crate_id)) | ||
147 | { | ||
148 | result.extend( | ||
149 | importable_locations_in_crate(db, item, krate) | ||
150 | .iter() | ||
151 | .filter(|(_, _, vis)| vis.is_visible_from(db, from)) | ||
152 | .map(|(m, n, _)| (*m, n.clone())), | ||
153 | ); | ||
154 | } | ||
155 | result | ||
156 | } | ||
157 | |||
158 | /// Collects all locations from which we might import the item in a particular | ||
159 | /// crate. These include the original definition of the item, and any | ||
160 | /// non-private `use`s. | ||
161 | /// | ||
162 | /// Note that the crate doesn't need to be the one in which the item is defined; | ||
163 | /// it might be re-exported in other crates. | ||
164 | fn importable_locations_in_crate( | ||
165 | db: &impl DefDatabase, | ||
166 | item: ItemInNs, | ||
167 | krate: CrateId, | ||
168 | ) -> Vec<(ModuleId, Name, Visibility)> { | ||
169 | let def_map = db.crate_def_map(krate); | ||
170 | let mut result = Vec::new(); | ||
171 | for (local_id, data) in def_map.modules.iter() { | ||
172 | if let Some((name, vis)) = data.scope.name_of(item) { | ||
173 | let is_private = if let Visibility::Module(private_to) = vis { | ||
174 | private_to.local_id == local_id | ||
175 | } else { | ||
176 | false | ||
177 | }; | ||
178 | let is_original_def = if let Some(module_def_id) = item.as_module_def_id() { | ||
179 | data.scope.declarations().any(|it| it == module_def_id) | ||
180 | } else { | ||
181 | false | ||
182 | }; | ||
183 | if is_private && !is_original_def { | ||
184 | // Ignore private imports. these could be used if we are | ||
185 | // in a submodule of this module, but that's usually not | ||
186 | // what the user wants; and if this module can import | ||
187 | // the item and we're a submodule of it, so can we. | ||
188 | // Also this keeps the cached data smaller. | ||
189 | continue; | ||
190 | } | ||
191 | result.push((ModuleId { krate, local_id }, name.clone(), vis)); | ||
192 | } | ||
193 | } | ||
194 | result | ||
195 | } | ||
196 | |||
197 | #[cfg(test)] | ||
198 | mod tests { | ||
199 | use super::*; | ||
200 | use crate::test_db::TestDB; | ||
201 | use hir_expand::hygiene::Hygiene; | ||
202 | use ra_db::fixture::WithFixture; | ||
203 | use ra_syntax::ast::AstNode; | ||
204 | |||
205 | /// `code` needs to contain a cursor marker; checks that `find_path` for the | ||
206 | /// item the `path` refers to returns that same path when called from the | ||
207 | /// module the cursor is in. | ||
208 | fn check_found_path(code: &str, path: &str) { | ||
209 | let (db, pos) = TestDB::with_position(code); | ||
210 | let module = db.module_for_file(pos.file_id); | ||
211 | let parsed_path_file = ra_syntax::SourceFile::parse(&format!("use {};", path)); | ||
212 | let ast_path = parsed_path_file | ||
213 | .syntax_node() | ||
214 | .descendants() | ||
215 | .find_map(ra_syntax::ast::Path::cast) | ||
216 | .unwrap(); | ||
217 | let mod_path = ModPath::from_src(ast_path, &Hygiene::new_unhygienic()).unwrap(); | ||
218 | |||
219 | let crate_def_map = db.crate_def_map(module.krate); | ||
220 | let resolved = crate_def_map | ||
221 | .resolve_path( | ||
222 | &db, | ||
223 | module.local_id, | ||
224 | &mod_path, | ||
225 | crate::item_scope::BuiltinShadowMode::Module, | ||
226 | ) | ||
227 | .0 | ||
228 | .take_types() | ||
229 | .unwrap(); | ||
230 | |||
231 | let found_path = find_path(&db, ItemInNs::Types(resolved), module); | ||
232 | |||
233 | assert_eq!(found_path, Some(mod_path)); | ||
234 | } | ||
235 | |||
236 | #[test] | ||
237 | fn same_module() { | ||
238 | let code = r#" | ||
239 | //- /main.rs | ||
240 | struct S; | ||
241 | <|> | ||
242 | "#; | ||
243 | check_found_path(code, "S"); | ||
244 | } | ||
245 | |||
246 | #[test] | ||
247 | fn enum_variant() { | ||
248 | let code = r#" | ||
249 | //- /main.rs | ||
250 | enum E { A } | ||
251 | <|> | ||
252 | "#; | ||
253 | check_found_path(code, "E::A"); | ||
254 | } | ||
255 | |||
256 | #[test] | ||
257 | fn sub_module() { | ||
258 | let code = r#" | ||
259 | //- /main.rs | ||
260 | mod foo { | ||
261 | pub struct S; | ||
262 | } | ||
263 | <|> | ||
264 | "#; | ||
265 | check_found_path(code, "foo::S"); | ||
266 | } | ||
267 | |||
268 | #[test] | ||
269 | fn super_module() { | ||
270 | let code = r#" | ||
271 | //- /main.rs | ||
272 | mod foo; | ||
273 | //- /foo.rs | ||
274 | mod bar; | ||
275 | struct S; | ||
276 | //- /foo/bar.rs | ||
277 | <|> | ||
278 | "#; | ||
279 | check_found_path(code, "super::S"); | ||
280 | } | ||
281 | |||
282 | #[test] | ||
283 | fn self_module() { | ||
284 | let code = r#" | ||
285 | //- /main.rs | ||
286 | mod foo; | ||
287 | //- /foo.rs | ||
288 | <|> | ||
289 | "#; | ||
290 | check_found_path(code, "self"); | ||
291 | } | ||
292 | |||
293 | #[test] | ||
294 | fn crate_root() { | ||
295 | let code = r#" | ||
296 | //- /main.rs | ||
297 | mod foo; | ||
298 | //- /foo.rs | ||
299 | <|> | ||
300 | "#; | ||
301 | check_found_path(code, "crate"); | ||
302 | } | ||
303 | |||
304 | #[test] | ||
305 | fn same_crate() { | ||
306 | let code = r#" | ||
307 | //- /main.rs | ||
308 | mod foo; | ||
309 | struct S; | ||
310 | //- /foo.rs | ||
311 | <|> | ||
312 | "#; | ||
313 | check_found_path(code, "crate::S"); | ||
314 | } | ||
315 | |||
316 | #[test] | ||
317 | fn different_crate() { | ||
318 | let code = r#" | ||
319 | //- /main.rs crate:main deps:std | ||
320 | <|> | ||
321 | //- /std.rs crate:std | ||
322 | pub struct S; | ||
323 | "#; | ||
324 | check_found_path(code, "std::S"); | ||
325 | } | ||
326 | |||
327 | #[test] | ||
328 | fn different_crate_renamed() { | ||
329 | let code = r#" | ||
330 | //- /main.rs crate:main deps:std | ||
331 | extern crate std as std_renamed; | ||
332 | <|> | ||
333 | //- /std.rs crate:std | ||
334 | pub struct S; | ||
335 | "#; | ||
336 | check_found_path(code, "std_renamed::S"); | ||
337 | } | ||
338 | |||
339 | #[test] | ||
340 | fn same_crate_reexport() { | ||
341 | let code = r#" | ||
342 | //- /main.rs | ||
343 | mod bar { | ||
344 | mod foo { pub(super) struct S; } | ||
345 | pub(crate) use foo::*; | ||
346 | } | ||
347 | <|> | ||
348 | "#; | ||
349 | check_found_path(code, "bar::S"); | ||
350 | } | ||
351 | |||
352 | #[test] | ||
353 | fn same_crate_reexport_rename() { | ||
354 | let code = r#" | ||
355 | //- /main.rs | ||
356 | mod bar { | ||
357 | mod foo { pub(super) struct S; } | ||
358 | pub(crate) use foo::S as U; | ||
359 | } | ||
360 | <|> | ||
361 | "#; | ||
362 | check_found_path(code, "bar::U"); | ||
363 | } | ||
364 | |||
365 | #[test] | ||
366 | fn different_crate_reexport() { | ||
367 | let code = r#" | ||
368 | //- /main.rs crate:main deps:std | ||
369 | <|> | ||
370 | //- /std.rs crate:std deps:core | ||
371 | pub use core::S; | ||
372 | //- /core.rs crate:core | ||
373 | pub struct S; | ||
374 | "#; | ||
375 | check_found_path(code, "std::S"); | ||
376 | } | ||
377 | |||
378 | #[test] | ||
379 | fn prelude() { | ||
380 | let code = r#" | ||
381 | //- /main.rs crate:main deps:std | ||
382 | <|> | ||
383 | //- /std.rs crate:std | ||
384 | pub mod prelude { pub struct S; } | ||
385 | #[prelude_import] | ||
386 | pub use prelude::*; | ||
387 | "#; | ||
388 | check_found_path(code, "S"); | ||
389 | } | ||
390 | |||
391 | #[test] | ||
392 | fn enum_variant_from_prelude() { | ||
393 | let code = r#" | ||
394 | //- /main.rs crate:main deps:std | ||
395 | <|> | ||
396 | //- /std.rs crate:std | ||
397 | pub mod prelude { | ||
398 | pub enum Option<T> { Some(T), None } | ||
399 | pub use Option::*; | ||
400 | } | ||
401 | #[prelude_import] | ||
402 | pub use prelude::*; | ||
403 | "#; | ||
404 | check_found_path(code, "None"); | ||
405 | check_found_path(code, "Some"); | ||
406 | } | ||
407 | |||
408 | #[test] | ||
409 | fn shortest_path() { | ||
410 | let code = r#" | ||
411 | //- /main.rs | ||
412 | pub mod foo; | ||
413 | pub mod baz; | ||
414 | struct S; | ||
415 | <|> | ||
416 | //- /foo.rs | ||
417 | pub mod bar { pub struct S; } | ||
418 | //- /baz.rs | ||
419 | pub use crate::foo::bar::S; | ||
420 | "#; | ||
421 | check_found_path(code, "baz::S"); | ||
422 | } | ||
423 | |||
424 | #[test] | ||
425 | fn discount_private_imports() { | ||
426 | let code = r#" | ||
427 | //- /main.rs | ||
428 | mod foo; | ||
429 | pub mod bar { pub struct S; } | ||
430 | use bar::S; | ||
431 | //- /foo.rs | ||
432 | <|> | ||
433 | "#; | ||
434 | // crate::S would be shorter, but using private imports seems wrong | ||
435 | check_found_path(code, "crate::bar::S"); | ||
436 | } | ||
437 | |||
438 | #[test] | ||
439 | fn import_cycle() { | ||
440 | let code = r#" | ||
441 | //- /main.rs | ||
442 | pub mod foo; | ||
443 | pub mod bar; | ||
444 | pub mod baz; | ||
445 | //- /bar.rs | ||
446 | <|> | ||
447 | //- /foo.rs | ||
448 | pub use super::baz; | ||
449 | pub struct S; | ||
450 | //- /baz.rs | ||
451 | pub use super::foo; | ||
452 | "#; | ||
453 | check_found_path(code, "crate::foo::S"); | ||
454 | } | ||
455 | } | ||
diff --git a/crates/ra_hir_def/src/item_scope.rs b/crates/ra_hir_def/src/item_scope.rs index fe7bb9779..d74a1cef2 100644 --- a/crates/ra_hir_def/src/item_scope.rs +++ b/crates/ra_hir_def/src/item_scope.rs | |||
@@ -104,6 +104,15 @@ impl ItemScope { | |||
104 | } | 104 | } |
105 | } | 105 | } |
106 | 106 | ||
107 | pub(crate) fn name_of(&self, item: ItemInNs) -> Option<(&Name, Visibility)> { | ||
108 | for (name, per_ns) in &self.visible { | ||
109 | if let Some(vis) = item.match_with(*per_ns) { | ||
110 | return Some((name, vis)); | ||
111 | } | ||
112 | } | ||
113 | None | ||
114 | } | ||
115 | |||
107 | pub(crate) fn traits<'a>(&'a self) -> impl Iterator<Item = TraitId> + 'a { | 116 | pub(crate) fn traits<'a>(&'a self) -> impl Iterator<Item = TraitId> + 'a { |
108 | self.visible.values().filter_map(|def| match def.take_types() { | 117 | self.visible.values().filter_map(|def| match def.take_types() { |
109 | Some(ModuleDefId::TraitId(t)) => Some(t), | 118 | Some(ModuleDefId::TraitId(t)) => Some(t), |
@@ -173,3 +182,33 @@ impl PerNs { | |||
173 | } | 182 | } |
174 | } | 183 | } |
175 | } | 184 | } |
185 | |||
186 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] | ||
187 | pub enum ItemInNs { | ||
188 | Types(ModuleDefId), | ||
189 | Values(ModuleDefId), | ||
190 | Macros(MacroDefId), | ||
191 | } | ||
192 | |||
193 | impl ItemInNs { | ||
194 | fn match_with(self, per_ns: PerNs) -> Option<Visibility> { | ||
195 | match self { | ||
196 | ItemInNs::Types(def) => { | ||
197 | per_ns.types.filter(|(other_def, _)| *other_def == def).map(|(_, vis)| vis) | ||
198 | } | ||
199 | ItemInNs::Values(def) => { | ||
200 | per_ns.values.filter(|(other_def, _)| *other_def == def).map(|(_, vis)| vis) | ||
201 | } | ||
202 | ItemInNs::Macros(def) => { | ||
203 | per_ns.macros.filter(|(other_def, _)| *other_def == def).map(|(_, vis)| vis) | ||
204 | } | ||
205 | } | ||
206 | } | ||
207 | |||
208 | pub fn as_module_def_id(self) -> Option<ModuleDefId> { | ||
209 | match self { | ||
210 | ItemInNs::Types(id) | ItemInNs::Values(id) => Some(id), | ||
211 | ItemInNs::Macros(_) => None, | ||
212 | } | ||
213 | } | ||
214 | } | ||
diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs index 61f044ecf..ebc12e891 100644 --- a/crates/ra_hir_def/src/lib.rs +++ b/crates/ra_hir_def/src/lib.rs | |||
@@ -37,6 +37,7 @@ pub mod src; | |||
37 | pub mod child_by_source; | 37 | pub mod child_by_source; |
38 | 38 | ||
39 | pub mod visibility; | 39 | pub mod visibility; |
40 | pub mod find_path; | ||
40 | 41 | ||
41 | #[cfg(test)] | 42 | #[cfg(test)] |
42 | mod test_db; | 43 | mod test_db; |
diff --git a/crates/ra_hir_def/src/path.rs b/crates/ra_hir_def/src/path.rs index 82cfa67a9..9f93a5424 100644 --- a/crates/ra_hir_def/src/path.rs +++ b/crates/ra_hir_def/src/path.rs | |||
@@ -1,7 +1,11 @@ | |||
1 | //! A desugared representation of paths like `crate::foo` or `<Type as Trait>::bar`. | 1 | //! A desugared representation of paths like `crate::foo` or `<Type as Trait>::bar`. |
2 | mod lower; | 2 | mod lower; |
3 | 3 | ||
4 | use std::{iter, sync::Arc}; | 4 | use std::{ |
5 | fmt::{self, Display}, | ||
6 | iter, | ||
7 | sync::Arc, | ||
8 | }; | ||
5 | 9 | ||
6 | use hir_expand::{ | 10 | use hir_expand::{ |
7 | hygiene::Hygiene, | 11 | hygiene::Hygiene, |
@@ -248,6 +252,42 @@ impl From<Name> for ModPath { | |||
248 | } | 252 | } |
249 | } | 253 | } |
250 | 254 | ||
255 | impl Display for ModPath { | ||
256 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
257 | let mut first_segment = true; | ||
258 | let mut add_segment = |s| { | ||
259 | if !first_segment { | ||
260 | f.write_str("::")?; | ||
261 | } | ||
262 | first_segment = false; | ||
263 | f.write_str(s)?; | ||
264 | Ok(()) | ||
265 | }; | ||
266 | match self.kind { | ||
267 | PathKind::Plain => {} | ||
268 | PathKind::Super(n) => { | ||
269 | if n == 0 { | ||
270 | add_segment("self")?; | ||
271 | } | ||
272 | for _ in 0..n { | ||
273 | add_segment("super")?; | ||
274 | } | ||
275 | } | ||
276 | PathKind::Crate => add_segment("crate")?, | ||
277 | PathKind::Abs => add_segment("")?, | ||
278 | PathKind::DollarCrate(_) => add_segment("$crate")?, | ||
279 | } | ||
280 | for segment in &self.segments { | ||
281 | if !first_segment { | ||
282 | f.write_str("::")?; | ||
283 | } | ||
284 | first_segment = false; | ||
285 | write!(f, "{}", segment)?; | ||
286 | } | ||
287 | Ok(()) | ||
288 | } | ||
289 | } | ||
290 | |||
251 | pub use hir_expand::name as __name; | 291 | pub use hir_expand::name as __name; |
252 | 292 | ||
253 | #[macro_export] | 293 | #[macro_export] |
diff --git a/crates/ra_hir_def/src/resolver.rs b/crates/ra_hir_def/src/resolver.rs index 5d16dd087..f7bac5801 100644 --- a/crates/ra_hir_def/src/resolver.rs +++ b/crates/ra_hir_def/src/resolver.rs | |||
@@ -128,7 +128,7 @@ impl Resolver { | |||
128 | path: &ModPath, | 128 | path: &ModPath, |
129 | shadow: BuiltinShadowMode, | 129 | shadow: BuiltinShadowMode, |
130 | ) -> PerNs { | 130 | ) -> PerNs { |
131 | let (item_map, module) = match self.module() { | 131 | let (item_map, module) = match self.module_scope() { |
132 | Some(it) => it, | 132 | Some(it) => it, |
133 | None => return PerNs::none(), | 133 | None => return PerNs::none(), |
134 | }; | 134 | }; |
@@ -239,7 +239,7 @@ impl Resolver { | |||
239 | ) -> Option<Visibility> { | 239 | ) -> Option<Visibility> { |
240 | match visibility { | 240 | match visibility { |
241 | RawVisibility::Module(_) => { | 241 | RawVisibility::Module(_) => { |
242 | let (item_map, module) = match self.module() { | 242 | let (item_map, module) = match self.module_scope() { |
243 | Some(it) => it, | 243 | Some(it) => it, |
244 | None => return None, | 244 | None => return None, |
245 | }; | 245 | }; |
@@ -379,7 +379,7 @@ impl Resolver { | |||
379 | db: &impl DefDatabase, | 379 | db: &impl DefDatabase, |
380 | path: &ModPath, | 380 | path: &ModPath, |
381 | ) -> Option<MacroDefId> { | 381 | ) -> Option<MacroDefId> { |
382 | let (item_map, module) = self.module()?; | 382 | let (item_map, module) = self.module_scope()?; |
383 | item_map.resolve_path(db, module, &path, BuiltinShadowMode::Other).0.take_macros() | 383 | item_map.resolve_path(db, module, &path, BuiltinShadowMode::Other).0.take_macros() |
384 | } | 384 | } |
385 | 385 | ||
@@ -403,7 +403,7 @@ impl Resolver { | |||
403 | traits | 403 | traits |
404 | } | 404 | } |
405 | 405 | ||
406 | fn module(&self) -> Option<(&CrateDefMap, LocalModuleId)> { | 406 | fn module_scope(&self) -> Option<(&CrateDefMap, LocalModuleId)> { |
407 | self.scopes.iter().rev().find_map(|scope| match scope { | 407 | self.scopes.iter().rev().find_map(|scope| match scope { |
408 | Scope::ModuleScope(m) => Some((&*m.crate_def_map, m.module_id)), | 408 | Scope::ModuleScope(m) => Some((&*m.crate_def_map, m.module_id)), |
409 | 409 | ||
@@ -411,8 +411,13 @@ impl Resolver { | |||
411 | }) | 411 | }) |
412 | } | 412 | } |
413 | 413 | ||
414 | pub fn module(&self) -> Option<ModuleId> { | ||
415 | let (def_map, local_id) = self.module_scope()?; | ||
416 | Some(ModuleId { krate: def_map.krate, local_id }) | ||
417 | } | ||
418 | |||
414 | pub fn krate(&self) -> Option<CrateId> { | 419 | pub fn krate(&self) -> Option<CrateId> { |
415 | self.module().map(|t| t.0.krate) | 420 | self.module_scope().map(|t| t.0.krate) |
416 | } | 421 | } |
417 | 422 | ||
418 | pub fn where_predicates_in_scope<'a>( | 423 | pub fn where_predicates_in_scope<'a>( |
diff --git a/crates/ra_hir_def/src/test_db.rs b/crates/ra_hir_def/src/test_db.rs index 54e3a84bd..1568820e9 100644 --- a/crates/ra_hir_def/src/test_db.rs +++ b/crates/ra_hir_def/src/test_db.rs | |||
@@ -5,6 +5,7 @@ use std::{ | |||
5 | sync::{Arc, Mutex}, | 5 | sync::{Arc, Mutex}, |
6 | }; | 6 | }; |
7 | 7 | ||
8 | use crate::db::DefDatabase; | ||
8 | use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath}; | 9 | use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath}; |
9 | 10 | ||
10 | #[salsa::database( | 11 | #[salsa::database( |
@@ -54,6 +55,18 @@ impl FileLoader for TestDB { | |||
54 | } | 55 | } |
55 | 56 | ||
56 | impl TestDB { | 57 | impl TestDB { |
58 | pub fn module_for_file(&self, file_id: FileId) -> crate::ModuleId { | ||
59 | for &krate in self.relevant_crates(file_id).iter() { | ||
60 | let crate_def_map = self.crate_def_map(krate); | ||
61 | for (local_id, data) in crate_def_map.modules.iter() { | ||
62 | if data.origin.file_id() == Some(file_id) { | ||
63 | return crate::ModuleId { krate, local_id }; | ||
64 | } | ||
65 | } | ||
66 | } | ||
67 | panic!("Can't find module for file") | ||
68 | } | ||
69 | |||
57 | pub fn log(&self, f: impl FnOnce()) -> Vec<salsa::Event<TestDB>> { | 70 | pub fn log(&self, f: impl FnOnce()) -> Vec<salsa::Event<TestDB>> { |
58 | *self.events.lock().unwrap() = Some(Vec::new()); | 71 | *self.events.lock().unwrap() = Some(Vec::new()); |
59 | f(); | 72 | f(); |
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs index 2fa5d5140..51c5f9623 100644 --- a/crates/ra_hir_expand/src/lib.rs +++ b/crates/ra_hir_expand/src/lib.rs | |||
@@ -322,3 +322,13 @@ impl InFile<SyntaxNode> { | |||
322 | }) | 322 | }) |
323 | } | 323 | } |
324 | } | 324 | } |
325 | |||
326 | impl<N: AstNode> InFile<N> { | ||
327 | pub fn descendants<T: AstNode>(self) -> impl Iterator<Item = InFile<T>> { | ||
328 | self.value.syntax().descendants().filter_map(T::cast).map(move |n| self.with_value(n)) | ||
329 | } | ||
330 | |||
331 | pub fn syntax(&self) -> InFile<&SyntaxNode> { | ||
332 | self.with_value(self.value.syntax()) | ||
333 | } | ||
334 | } | ||
diff --git a/crates/ra_ide/src/expand_macro.rs b/crates/ra_ide/src/expand_macro.rs index bdbc31704..0f7b6e875 100644 --- a/crates/ra_ide/src/expand_macro.rs +++ b/crates/ra_ide/src/expand_macro.rs | |||
@@ -7,8 +7,7 @@ use rustc_hash::FxHashMap; | |||
7 | 7 | ||
8 | use ra_syntax::{ | 8 | use ra_syntax::{ |
9 | algo::{find_node_at_offset, replace_descendants}, | 9 | algo::{find_node_at_offset, replace_descendants}, |
10 | ast::{self}, | 10 | ast, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, WalkEvent, T, |
11 | AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, T, | ||
12 | }; | 11 | }; |
13 | 12 | ||
14 | pub struct ExpandedMacro { | 13 | pub struct ExpandedMacro { |
@@ -43,7 +42,7 @@ fn expand_macro_recur( | |||
43 | let mut expanded: SyntaxNode = db.parse_or_expand(macro_file_id)?; | 42 | let mut expanded: SyntaxNode = db.parse_or_expand(macro_file_id)?; |
44 | 43 | ||
45 | let children = expanded.descendants().filter_map(ast::MacroCall::cast); | 44 | let children = expanded.descendants().filter_map(ast::MacroCall::cast); |
46 | let mut replaces = FxHashMap::default(); | 45 | let mut replaces: FxHashMap<SyntaxElement, SyntaxElement> = FxHashMap::default(); |
47 | 46 | ||
48 | for child in children.into_iter() { | 47 | for child in children.into_iter() { |
49 | let node = hir::InFile::new(macro_file_id, &child); | 48 | let node = hir::InFile::new(macro_file_id, &child); |
@@ -59,7 +58,7 @@ fn expand_macro_recur( | |||
59 | } | 58 | } |
60 | } | 59 | } |
61 | 60 | ||
62 | Some(replace_descendants(&expanded, &replaces)) | 61 | Some(replace_descendants(&expanded, &|n| replaces.get(n).cloned())) |
63 | } | 62 | } |
64 | 63 | ||
65 | // FIXME: It would also be cool to share logic here and in the mbe tests, | 64 | // FIXME: It would also be cool to share logic here and in the mbe tests, |
diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs index 2b2b295f9..30a479f01 100644 --- a/crates/ra_syntax/src/algo.rs +++ b/crates/ra_syntax/src/algo.rs | |||
@@ -184,17 +184,17 @@ pub fn replace_children( | |||
184 | /// to create a type-safe abstraction on top of it instead. | 184 | /// to create a type-safe abstraction on top of it instead. |
185 | pub fn replace_descendants( | 185 | pub fn replace_descendants( |
186 | parent: &SyntaxNode, | 186 | parent: &SyntaxNode, |
187 | map: &FxHashMap<SyntaxElement, SyntaxElement>, | 187 | map: &impl Fn(&SyntaxElement) -> Option<SyntaxElement>, |
188 | ) -> SyntaxNode { | 188 | ) -> SyntaxNode { |
189 | // FIXME: this could be made much faster. | 189 | // FIXME: this could be made much faster. |
190 | let new_children = parent.children_with_tokens().map(|it| go(map, it)).collect::<Vec<_>>(); | 190 | let new_children = parent.children_with_tokens().map(|it| go(map, it)).collect::<Vec<_>>(); |
191 | return with_children(parent, new_children); | 191 | return with_children(parent, new_children); |
192 | 192 | ||
193 | fn go( | 193 | fn go( |
194 | map: &FxHashMap<SyntaxElement, SyntaxElement>, | 194 | map: &impl Fn(&SyntaxElement) -> Option<SyntaxElement>, |
195 | element: SyntaxElement, | 195 | element: SyntaxElement, |
196 | ) -> NodeOrToken<rowan::GreenNode, rowan::GreenToken> { | 196 | ) -> NodeOrToken<rowan::GreenNode, rowan::GreenToken> { |
197 | if let Some(replacement) = map.get(&element) { | 197 | if let Some(replacement) = map(&element) { |
198 | return match replacement { | 198 | return match replacement { |
199 | NodeOrToken::Node(it) => NodeOrToken::Node(it.green().clone()), | 199 | NodeOrToken::Node(it) => NodeOrToken::Node(it.green().clone()), |
200 | NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()), | 200 | NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()), |
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index ae5d63927..b736098ac 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs | |||
@@ -236,8 +236,8 @@ pub fn replace_descendants<N: AstNode, D: AstNode>( | |||
236 | ) -> N { | 236 | ) -> N { |
237 | let map = replacement_map | 237 | let map = replacement_map |
238 | .map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into())) | 238 | .map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into())) |
239 | .collect::<FxHashMap<_, _>>(); | 239 | .collect::<FxHashMap<SyntaxElement, _>>(); |
240 | let new_syntax = algo::replace_descendants(parent.syntax(), &map); | 240 | let new_syntax = algo::replace_descendants(parent.syntax(), &|n| map.get(n).cloned()); |
241 | N::cast(new_syntax).unwrap() | 241 | N::cast(new_syntax).unwrap() |
242 | } | 242 | } |
243 | 243 | ||
@@ -292,7 +292,7 @@ impl IndentLevel { | |||
292 | ) | 292 | ) |
293 | }) | 293 | }) |
294 | .collect(); | 294 | .collect(); |
295 | algo::replace_descendants(&node, &replacements) | 295 | algo::replace_descendants(&node, &|n| replacements.get(n).cloned()) |
296 | } | 296 | } |
297 | 297 | ||
298 | pub fn decrease_indent<N: AstNode>(self, node: N) -> N { | 298 | pub fn decrease_indent<N: AstNode>(self, node: N) -> N { |
@@ -320,7 +320,7 @@ impl IndentLevel { | |||
320 | ) | 320 | ) |
321 | }) | 321 | }) |
322 | .collect(); | 322 | .collect(); |
323 | algo::replace_descendants(&node, &replacements) | 323 | algo::replace_descendants(&node, &|n| replacements.get(n).cloned()) |
324 | } | 324 | } |
325 | } | 325 | } |
326 | 326 | ||
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index 04a5408fe..9781b748f 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | //! of smaller pieces. | 2 | //! of smaller pieces. |
3 | use itertools::Itertools; | 3 | use itertools::Itertools; |
4 | 4 | ||
5 | use crate::{ast, AstNode, SourceFile}; | 5 | use crate::{algo, ast, AstNode, SourceFile}; |
6 | 6 | ||
7 | pub fn name(text: &str) -> ast::Name { | 7 | pub fn name(text: &str) -> ast::Name { |
8 | ast_from_text(&format!("mod {};", text)) | 8 | ast_from_text(&format!("mod {};", text)) |
@@ -21,6 +21,20 @@ pub fn path_qualified(qual: ast::Path, name_ref: ast::NameRef) -> ast::Path { | |||
21 | fn path_from_text(text: &str) -> ast::Path { | 21 | fn path_from_text(text: &str) -> ast::Path { |
22 | ast_from_text(text) | 22 | ast_from_text(text) |
23 | } | 23 | } |
24 | pub fn path_with_type_arg_list(path: ast::Path, args: Option<ast::TypeArgList>) -> ast::Path { | ||
25 | if let Some(args) = args { | ||
26 | let syntax = path.syntax(); | ||
27 | // FIXME: remove existing type args | ||
28 | let new_syntax = algo::insert_children( | ||
29 | syntax, | ||
30 | crate::algo::InsertPosition::Last, | ||
31 | &mut Some(args).into_iter().map(|n| n.syntax().clone().into()), | ||
32 | ); | ||
33 | ast::Path::cast(new_syntax).unwrap() | ||
34 | } else { | ||
35 | path | ||
36 | } | ||
37 | } | ||
24 | 38 | ||
25 | pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordField { | 39 | pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordField { |
26 | return match expr { | 40 | return match expr { |