diff options
-rw-r--r-- | Cargo.lock | 1 | ||||
-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 |
19 files changed, 995 insertions, 85 deletions
diff --git a/Cargo.lock b/Cargo.lock index ba9a201b9..90c505f40 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -872,6 +872,7 @@ dependencies = [ | |||
872 | "ra_hir 0.1.0", | 872 | "ra_hir 0.1.0", |
873 | "ra_syntax 0.1.0", | 873 | "ra_syntax 0.1.0", |
874 | "ra_text_edit 0.1.0", | 874 | "ra_text_edit 0.1.0", |
875 | "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||
875 | "test_utils 0.1.0", | 876 | "test_utils 0.1.0", |
876 | ] | 877 | ] |
877 | 878 | ||
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 { |