aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/ast_transform.rs
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-01-11 22:42:39 +0000
committerGitHub <[email protected]>2020-01-11 22:42:39 +0000
commitbcfd297f4910bbf2305ec859d7cf42b7dca25f57 (patch)
treec6b53cd774e7baacf213e91b295734852a83f40b /crates/ra_assists/src/ast_transform.rs
parente90aa86fbfa716c4028f38d0d22654065011a964 (diff)
parentccb75f7c979b56bc62b61fadd81903e11a7f5d74 (diff)
Merge #2727
2727: Qualify paths in 'add impl members' r=flodiebold a=flodiebold This makes the 'add impl members' assist qualify paths, so that they should resolve to the same thing as in the definition. To do that, it adds an algorithm that finds a path to refer to any item from any module (if possible), which is actually probably the more important part of this PR :smile: It handles visibility, reexports, renamed crates, prelude etc.; I think the only thing that's missing is support for local items. I'm not sure about the performance, since it takes into account every location where the target item has been `pub use`d, and then recursively goes up the module tree; there's probably potential for optimization by memoizing more, but I think the general shape of the algorithm is necessary to handle every case in Rust's module system. ~The 'find path' part is actually pretty complete, I think; I'm still working on the assist (hence the failing tests).~ Fixes #1943. Co-authored-by: Florian Diebold <[email protected]> Co-authored-by: Florian Diebold <[email protected]>
Diffstat (limited to 'crates/ra_assists/src/ast_transform.rs')
-rw-r--r--crates/ra_assists/src/ast_transform.rs179
1 files changed, 179 insertions, 0 deletions
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}