aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-01-11 22:42:39 +0000
committerGitHub <[email protected]>2020-01-11 22:42:39 +0000
commitbcfd297f4910bbf2305ec859d7cf42b7dca25f57 (patch)
treec6b53cd774e7baacf213e91b295734852a83f40b /crates
parente90aa86fbfa716c4028f38d0d22654065011a964 (diff)
parentccb75f7c979b56bc62b61fadd81903e11a7f5d74 (diff)
Merge #2727
2727: Qualify paths in 'add impl members' r=flodiebold a=flodiebold This makes the 'add impl members' assist qualify paths, so that they should resolve to the same thing as in the definition. To do that, it adds an algorithm that finds a path to refer to any item from any module (if possible), which is actually probably the more important part of this PR :smile: It handles visibility, reexports, renamed crates, prelude etc.; I think the only thing that's missing is support for local items. I'm not sure about the performance, since it takes into account every location where the target item has been `pub use`d, and then recursively goes up the module tree; there's probably potential for optimization by memoizing more, but I think the general shape of the algorithm is necessary to handle every case in Rust's module system. ~The 'find path' part is actually pretty complete, I think; I'm still working on the assist (hence the failing tests).~ Fixes #1943. Co-authored-by: Florian Diebold <[email protected]> Co-authored-by: Florian Diebold <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/Cargo.toml1
-rw-r--r--crates/ra_assists/src/assists/add_missing_impl_members.rs251
-rw-r--r--crates/ra_assists/src/ast_transform.rs179
-rw-r--r--crates/ra_assists/src/lib.rs1
-rw-r--r--crates/ra_hir/src/code_model.rs15
-rw-r--r--crates/ra_hir/src/from_id.rs16
-rw-r--r--crates/ra_hir/src/source_binder.rs4
-rw-r--r--crates/ra_hir_def/src/find_path.rs455
-rw-r--r--crates/ra_hir_def/src/item_scope.rs39
-rw-r--r--crates/ra_hir_def/src/lib.rs1
-rw-r--r--crates/ra_hir_def/src/path.rs42
-rw-r--r--crates/ra_hir_def/src/resolver.rs15
-rw-r--r--crates/ra_hir_def/src/test_db.rs13
-rw-r--r--crates/ra_hir_expand/src/lib.rs10
-rw-r--r--crates/ra_ide/src/expand_macro.rs7
-rw-r--r--crates/ra_syntax/src/algo.rs6
-rw-r--r--crates/ra_syntax/src/ast/edit.rs8
-rw-r--r--crates/ra_syntax/src/ast/make.rs16
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]
11format-buf = "1.0.0" 11format-buf = "1.0.0"
12join_to_string = "0.1.3" 12join_to_string = "0.1.3"
13rustc-hash = "1.0"
13itertools = "0.8.0" 14itertools = "0.8.0"
14 15
15ra_syntax = { path = "../ra_syntax" } 16ra_syntax = { path = "../ra_syntax" }
diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/assists/add_missing_impl_members.rs
index bc49e71fe..bf1136193 100644
--- a/crates/ra_assists/src/assists/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs
@@ -1,12 +1,13 @@
1use std::collections::HashMap; 1use hir::{db::HirDatabase, HasSource, InFile};
2
3use hir::{db::HirDatabase, HasSource};
4use ra_syntax::{ 2use ra_syntax::{
5 ast::{self, edit, make, AstNode, NameOwner}, 3 ast::{self, edit, make, AstNode, NameOwner},
6 SmolStr, 4 SmolStr,
7}; 5};
8 6
9use crate::{Assist, AssistCtx, AssistId}; 7use crate::{
8 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
9 Assist, AssistCtx, AssistId,
10};
10 11
11#[derive(PartialEq)] 12#[derive(PartialEq)]
12enum AddMissingImplMembersMode { 13enum AddMissingImplMembersMode {
@@ -134,25 +135,23 @@ fn add_missing_impl_members_inner(
134 return None; 135 return None;
135 } 136 }
136 137
137 let file_id = ctx.frange.file_id;
138 let db = ctx.db; 138 let db = ctx.db;
139 let file_id = ctx.frange.file_id;
140 let trait_file_id = trait_.source(db).file_id;
139 141
140 ctx.add_assist(AssistId(assist_id), label, |edit| { 142 ctx.add_assist(AssistId(assist_id), label, |edit| {
141 let n_existing_items = impl_item_list.impl_items().count(); 143 let n_existing_items = impl_item_list.impl_items().count();
142 let substs = get_syntactic_substs(impl_node).unwrap_or_default(); 144 let module = hir::SourceAnalyzer::new(
143 let generic_def: hir::GenericDef = trait_.into(); 145 db,
144 let substs_by_param: HashMap<_, _> = generic_def 146 hir::InFile::new(file_id.into(), impl_node.syntax()),
145 .params(db) 147 None,
146 .into_iter() 148 )
147 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky 149 .module();
148 .skip(1) 150 let ast_transform = QualifyPaths::new(db, module)
149 .zip(substs.into_iter()) 151 .or(SubstituteTypeParams::for_trait_impl(db, trait_, impl_node));
150 .collect();
151 let items = missing_items 152 let items = missing_items
152 .into_iter() 153 .into_iter()
153 .map(|it| { 154 .map(|it| ast_transform::apply(&*ast_transform, InFile::new(trait_file_id, it)))
154 substitute_type_params(db, hir::InFile::new(file_id.into(), it), &substs_by_param)
155 })
156 .map(|it| match it { 155 .map(|it| match it {
157 ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)), 156 ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)),
158 _ => it, 157 _ => it,
@@ -177,56 +176,6 @@ fn add_body(fn_def: ast::FnDef) -> ast::FnDef {
177 } 176 }
178} 177}
179 178
180// FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
181// trait ref, and then go from the types in the substs back to the syntax)
182// FIXME: This should be a general utility (not even just for assists)
183fn get_syntactic_substs(impl_block: ast::ImplBlock) -> Option<Vec<ast::TypeRef>> {
184 let target_trait = impl_block.target_trait()?;
185 let path_type = match target_trait {
186 ast::TypeRef::PathType(path) => path,
187 _ => return None,
188 };
189 let type_arg_list = path_type.path()?.segment()?.type_arg_list()?;
190 let mut result = Vec::new();
191 for type_arg in type_arg_list.type_args() {
192 let type_arg: ast::TypeArg = type_arg;
193 result.push(type_arg.type_ref()?);
194 }
195 Some(result)
196}
197
198// FIXME: This should be a general utility (not even just for assists)
199fn substitute_type_params<N: AstNode>(
200 db: &impl HirDatabase,
201 node: hir::InFile<N>,
202 substs: &HashMap<hir::TypeParam, ast::TypeRef>,
203) -> N {
204 let type_param_replacements = node
205 .value
206 .syntax()
207 .descendants()
208 .filter_map(ast::TypeRef::cast)
209 .filter_map(|n| {
210 let path = match &n {
211 ast::TypeRef::PathType(path_type) => path_type.path()?,
212 _ => return None,
213 };
214 let analyzer = hir::SourceAnalyzer::new(db, node.with_value(n.syntax()), None);
215 let resolution = analyzer.resolve_path(db, &path)?;
216 match resolution {
217 hir::PathResolution::TypeParam(tp) => Some((n, substs.get(&tp)?.clone())),
218 _ => None,
219 }
220 })
221 .collect::<Vec<_>>();
222
223 if type_param_replacements.is_empty() {
224 node.value
225 } else {
226 edit::replace_descendants(&node.value, type_param_replacements.into_iter())
227 }
228}
229
230/// Given an `ast::ImplBlock`, resolves the target trait (the one being 179/// Given an `ast::ImplBlock`, resolves the target trait (the one being
231/// implemented) to a `ast::TraitDef`. 180/// implemented) to a `ast::TraitDef`.
232fn resolve_target_trait_def( 181fn resolve_target_trait_def(
@@ -401,6 +350,174 @@ impl Foo for S {
401 } 350 }
402 351
403 #[test] 352 #[test]
353 fn test_qualify_path_1() {
354 check_assist(
355 add_missing_impl_members,
356 "
357mod foo {
358 pub struct Bar;
359 trait Foo { fn foo(&self, bar: Bar); }
360}
361struct S;
362impl foo::Foo for S { <|> }",
363 "
364mod foo {
365 pub struct Bar;
366 trait Foo { fn foo(&self, bar: Bar); }
367}
368struct S;
369impl foo::Foo for S {
370 <|>fn foo(&self, bar: foo::Bar) { unimplemented!() }
371}",
372 );
373 }
374
375 #[test]
376 fn test_qualify_path_generic() {
377 check_assist(
378 add_missing_impl_members,
379 "
380mod foo {
381 pub struct Bar<T>;
382 trait Foo { fn foo(&self, bar: Bar<u32>); }
383}
384struct S;
385impl foo::Foo for S { <|> }",
386 "
387mod foo {
388 pub struct Bar<T>;
389 trait Foo { fn foo(&self, bar: Bar<u32>); }
390}
391struct S;
392impl foo::Foo for S {
393 <|>fn foo(&self, bar: foo::Bar<u32>) { unimplemented!() }
394}",
395 );
396 }
397
398 #[test]
399 fn test_qualify_path_and_substitute_param() {
400 check_assist(
401 add_missing_impl_members,
402 "
403mod foo {
404 pub struct Bar<T>;
405 trait Foo<T> { fn foo(&self, bar: Bar<T>); }
406}
407struct S;
408impl foo::Foo<u32> for S { <|> }",
409 "
410mod foo {
411 pub struct Bar<T>;
412 trait Foo<T> { fn foo(&self, bar: Bar<T>); }
413}
414struct S;
415impl foo::Foo<u32> for S {
416 <|>fn foo(&self, bar: foo::Bar<u32>) { unimplemented!() }
417}",
418 );
419 }
420
421 #[test]
422 fn test_substitute_param_no_qualify() {
423 // when substituting params, the substituted param should not be qualified!
424 check_assist(
425 add_missing_impl_members,
426 "
427mod foo {
428 trait Foo<T> { fn foo(&self, bar: T); }
429 pub struct Param;
430}
431struct Param;
432struct S;
433impl foo::Foo<Param> for S { <|> }",
434 "
435mod foo {
436 trait Foo<T> { fn foo(&self, bar: T); }
437 pub struct Param;
438}
439struct Param;
440struct S;
441impl foo::Foo<Param> for S {
442 <|>fn foo(&self, bar: Param) { unimplemented!() }
443}",
444 );
445 }
446
447 #[test]
448 fn test_qualify_path_associated_item() {
449 check_assist(
450 add_missing_impl_members,
451 "
452mod foo {
453 pub struct Bar<T>;
454 impl Bar<T> { type Assoc = u32; }
455 trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
456}
457struct S;
458impl foo::Foo for S { <|> }",
459 "
460mod foo {
461 pub struct Bar<T>;
462 impl Bar<T> { type Assoc = u32; }
463 trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
464}
465struct S;
466impl foo::Foo for S {
467 <|>fn foo(&self, bar: foo::Bar<u32>::Assoc) { unimplemented!() }
468}",
469 );
470 }
471
472 #[test]
473 fn test_qualify_path_nested() {
474 check_assist(
475 add_missing_impl_members,
476 "
477mod foo {
478 pub struct Bar<T>;
479 pub struct Baz;
480 trait Foo { fn foo(&self, bar: Bar<Baz>); }
481}
482struct S;
483impl foo::Foo for S { <|> }",
484 "
485mod foo {
486 pub struct Bar<T>;
487 pub struct Baz;
488 trait Foo { fn foo(&self, bar: Bar<Baz>); }
489}
490struct S;
491impl foo::Foo for S {
492 <|>fn foo(&self, bar: foo::Bar<foo::Baz>) { unimplemented!() }
493}",
494 );
495 }
496
497 #[test]
498 fn test_qualify_path_fn_trait_notation() {
499 check_assist(
500 add_missing_impl_members,
501 "
502mod foo {
503 pub trait Fn<Args> { type Output; }
504 trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
505}
506struct S;
507impl foo::Foo for S { <|> }",
508 "
509mod foo {
510 pub trait Fn<Args> { type Output; }
511 trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
512}
513struct S;
514impl foo::Foo for S {
515 <|>fn foo(&self, bar: dyn Fn(u32) -> i32) { unimplemented!() }
516}",
517 );
518 }
519
520 #[test]
404 fn test_empty_trait() { 521 fn test_empty_trait() {
405 check_assist_not_applicable( 522 check_assist_not_applicable(
406 add_missing_impl_members, 523 add_missing_impl_members,
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs
new file mode 100644
index 000000000..cbddc50ac
--- /dev/null
+++ b/crates/ra_assists/src/ast_transform.rs
@@ -0,0 +1,179 @@
1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined.
2use rustc_hash::FxHashMap;
3
4use hir::{db::HirDatabase, InFile, PathResolution};
5use ra_syntax::ast::{self, make, AstNode};
6
7pub trait AstTransform<'a> {
8 fn get_substitution(
9 &self,
10 node: InFile<&ra_syntax::SyntaxNode>,
11 ) -> Option<ra_syntax::SyntaxNode>;
12
13 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a>;
14 fn or<T: AstTransform<'a> + 'a>(self, other: T) -> Box<dyn AstTransform<'a> + 'a>
15 where
16 Self: Sized + 'a,
17 {
18 self.chain_before(Box::new(other))
19 }
20}
21
22struct NullTransformer;
23
24impl<'a> AstTransform<'a> for NullTransformer {
25 fn get_substitution(
26 &self,
27 _node: InFile<&ra_syntax::SyntaxNode>,
28 ) -> Option<ra_syntax::SyntaxNode> {
29 None
30 }
31 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
32 other
33 }
34}
35
36pub struct SubstituteTypeParams<'a, DB: HirDatabase> {
37 db: &'a DB,
38 substs: FxHashMap<hir::TypeParam, ast::TypeRef>,
39 previous: Box<dyn AstTransform<'a> + 'a>,
40}
41
42impl<'a, DB: HirDatabase> SubstituteTypeParams<'a, DB> {
43 pub fn for_trait_impl(
44 db: &'a DB,
45 trait_: hir::Trait,
46 impl_block: ast::ImplBlock,
47 ) -> SubstituteTypeParams<'a, DB> {
48 let substs = get_syntactic_substs(impl_block).unwrap_or_default();
49 let generic_def: hir::GenericDef = trait_.into();
50 let substs_by_param: FxHashMap<_, _> = generic_def
51 .params(db)
52 .into_iter()
53 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
54 .skip(1)
55 .zip(substs.into_iter())
56 .collect();
57 return SubstituteTypeParams {
58 db,
59 substs: substs_by_param,
60 previous: Box::new(NullTransformer),
61 };
62
63 // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
64 // trait ref, and then go from the types in the substs back to the syntax)
65 fn get_syntactic_substs(impl_block: ast::ImplBlock) -> Option<Vec<ast::TypeRef>> {
66 let target_trait = impl_block.target_trait()?;
67 let path_type = match target_trait {
68 ast::TypeRef::PathType(path) => path,
69 _ => return None,
70 };
71 let type_arg_list = path_type.path()?.segment()?.type_arg_list()?;
72 let mut result = Vec::new();
73 for type_arg in type_arg_list.type_args() {
74 let type_arg: ast::TypeArg = type_arg;
75 result.push(type_arg.type_ref()?);
76 }
77 Some(result)
78 }
79 }
80 fn get_substitution_inner(
81 &self,
82 node: InFile<&ra_syntax::SyntaxNode>,
83 ) -> Option<ra_syntax::SyntaxNode> {
84 let type_ref = ast::TypeRef::cast(node.value.clone())?;
85 let path = match &type_ref {
86 ast::TypeRef::PathType(path_type) => path_type.path()?,
87 _ => return None,
88 };
89 let analyzer = hir::SourceAnalyzer::new(self.db, node, None);
90 let resolution = analyzer.resolve_path(self.db, &path)?;
91 match resolution {
92 hir::PathResolution::TypeParam(tp) => Some(self.substs.get(&tp)?.syntax().clone()),
93 _ => None,
94 }
95 }
96}
97
98impl<'a, DB: HirDatabase> AstTransform<'a> for SubstituteTypeParams<'a, DB> {
99 fn get_substitution(
100 &self,
101 node: InFile<&ra_syntax::SyntaxNode>,
102 ) -> Option<ra_syntax::SyntaxNode> {
103 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
104 }
105 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
106 Box::new(SubstituteTypeParams { previous: other, ..self })
107 }
108}
109
110pub struct QualifyPaths<'a, DB: HirDatabase> {
111 db: &'a DB,
112 from: Option<hir::Module>,
113 previous: Box<dyn AstTransform<'a> + 'a>,
114}
115
116impl<'a, DB: HirDatabase> QualifyPaths<'a, DB> {
117 pub fn new(db: &'a DB, from: Option<hir::Module>) -> Self {
118 Self { db, from, previous: Box::new(NullTransformer) }
119 }
120
121 fn get_substitution_inner(
122 &self,
123 node: InFile<&ra_syntax::SyntaxNode>,
124 ) -> Option<ra_syntax::SyntaxNode> {
125 // FIXME handle value ns?
126 let from = self.from?;
127 let p = ast::Path::cast(node.value.clone())?;
128 if p.segment().and_then(|s| s.param_list()).is_some() {
129 // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
130 return None;
131 }
132 let analyzer = hir::SourceAnalyzer::new(self.db, node, None);
133 let resolution = analyzer.resolve_path(self.db, &p)?;
134 match resolution {
135 PathResolution::Def(def) => {
136 let found_path = from.find_use_path(self.db, def)?;
137 let args = p
138 .segment()
139 .and_then(|s| s.type_arg_list())
140 .map(|arg_list| apply(self, node.with_value(arg_list)));
141 Some(make::path_with_type_arg_list(path_to_ast(found_path), args).syntax().clone())
142 }
143 PathResolution::Local(_)
144 | PathResolution::TypeParam(_)
145 | PathResolution::SelfType(_) => None,
146 PathResolution::Macro(_) => None,
147 PathResolution::AssocItem(_) => None,
148 }
149 }
150}
151
152pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: InFile<N>) -> N {
153 let syntax = node.value.syntax();
154 let result = ra_syntax::algo::replace_descendants(syntax, &|element| match element {
155 ra_syntax::SyntaxElement::Node(n) => {
156 let replacement = transformer.get_substitution(node.with_value(&n))?;
157 Some(replacement.into())
158 }
159 _ => None,
160 });
161 N::cast(result).unwrap()
162}
163
164impl<'a, DB: HirDatabase> AstTransform<'a> for QualifyPaths<'a, DB> {
165 fn get_substitution(
166 &self,
167 node: InFile<&ra_syntax::SyntaxNode>,
168 ) -> Option<ra_syntax::SyntaxNode> {
169 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
170 }
171 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
172 Box::new(QualifyPaths { previous: other, ..self })
173 }
174}
175
176fn path_to_ast(path: hir::ModPath) -> ast::Path {
177 let parse = ast::SourceFile::parse(&path.to_string());
178 parse.tree().syntax().descendants().find_map(ast::Path::cast).unwrap()
179}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 98fb20b22..712ff6f6a 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -11,6 +11,7 @@ mod marks;
11mod doc_tests; 11mod doc_tests;
12#[cfg(test)] 12#[cfg(test)]
13mod test_db; 13mod test_db;
14pub mod ast_transform;
14 15
15use hir::db::HirDatabase; 16use hir::db::HirDatabase;
16use ra_db::FileRange; 17use ra_db::FileRange;
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
94impl 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
94impl From<DefWithBody> for DefWithBodyId { 110impl 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
3use crate::{
4 db::DefDatabase,
5 item_scope::ItemInNs,
6 path::{ModPath, PathKind},
7 visibility::Visibility,
8 CrateId, ModuleDefId, ModuleId,
9};
10use hir_expand::name::Name;
11
12const 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.
18pub fn find_path(db: &impl DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> {
19 find_path_inner(db, item, from, MAX_PATH_LEN)
20}
21
22fn 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
123fn 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
134fn 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.
164fn 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)]
198mod 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)]
187pub enum ItemInNs {
188 Types(ModuleDefId),
189 Values(ModuleDefId),
190 Macros(MacroDefId),
191}
192
193impl 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;
37pub mod child_by_source; 37pub mod child_by_source;
38 38
39pub mod visibility; 39pub mod visibility;
40pub mod find_path;
40 41
41#[cfg(test)] 42#[cfg(test)]
42mod test_db; 43mod 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`.
2mod lower; 2mod lower;
3 3
4use std::{iter, sync::Arc}; 4use std::{
5 fmt::{self, Display},
6 iter,
7 sync::Arc,
8};
5 9
6use hir_expand::{ 10use hir_expand::{
7 hygiene::Hygiene, 11 hygiene::Hygiene,
@@ -248,6 +252,42 @@ impl From<Name> for ModPath {
248 } 252 }
249} 253}
250 254
255impl 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
251pub use hir_expand::name as __name; 291pub 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
8use crate::db::DefDatabase;
8use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath}; 9use 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
56impl TestDB { 57impl 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
326impl<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
8use ra_syntax::{ 8use 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
14pub struct ExpandedMacro { 13pub 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.
185pub fn replace_descendants( 185pub 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.
3use itertools::Itertools; 3use itertools::Itertools;
4 4
5use crate::{ast, AstNode, SourceFile}; 5use crate::{algo, ast, AstNode, SourceFile};
6 6
7pub fn name(text: &str) -> ast::Name { 7pub 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 {
21fn path_from_text(text: &str) -> ast::Path { 21fn path_from_text(text: &str) -> ast::Path {
22 ast_from_text(text) 22 ast_from_text(text)
23} 23}
24pub 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
25pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordField { 39pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordField {
26 return match expr { 40 return match expr {