aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/ast_transform.rs
diff options
context:
space:
mode:
authorZac Pullar-Strecker <[email protected]>2020-08-24 10:19:53 +0100
committerZac Pullar-Strecker <[email protected]>2020-08-24 10:20:13 +0100
commit7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch)
treebdb47765991cb973b2cd5481a088fac636bd326c /crates/assists/src/ast_transform.rs
parentca464650eeaca6195891199a93f4f76cf3e7e697 (diff)
parente65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (diff)
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Diffstat (limited to 'crates/assists/src/ast_transform.rs')
-rw-r--r--crates/assists/src/ast_transform.rs200
1 files changed, 200 insertions, 0 deletions
diff --git a/crates/assists/src/ast_transform.rs b/crates/assists/src/ast_transform.rs
new file mode 100644
index 000000000..5216862ba
--- /dev/null
+++ b/crates/assists/src/ast_transform.rs
@@ -0,0 +1,200 @@
1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined.
2use rustc_hash::FxHashMap;
3
4use hir::{HirDisplay, PathResolution, SemanticsScope};
5use syntax::{
6 algo::SyntaxRewriter,
7 ast::{self, AstNode},
8};
9
10pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
11 SyntaxRewriter::from_fn(|element| match element {
12 syntax::SyntaxElement::Node(n) => {
13 let replacement = transformer.get_substitution(&n)?;
14 Some(replacement.into())
15 }
16 _ => None,
17 })
18 .rewrite_ast(&node)
19}
20
21pub trait AstTransform<'a> {
22 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode>;
23
24 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a>;
25 fn or<T: AstTransform<'a> + 'a>(self, other: T) -> Box<dyn AstTransform<'a> + 'a>
26 where
27 Self: Sized + 'a,
28 {
29 self.chain_before(Box::new(other))
30 }
31}
32
33struct NullTransformer;
34
35impl<'a> AstTransform<'a> for NullTransformer {
36 fn get_substitution(&self, _node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
37 None
38 }
39 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
40 other
41 }
42}
43
44pub struct SubstituteTypeParams<'a> {
45 source_scope: &'a SemanticsScope<'a>,
46 substs: FxHashMap<hir::TypeParam, ast::Type>,
47 previous: Box<dyn AstTransform<'a> + 'a>,
48}
49
50impl<'a> SubstituteTypeParams<'a> {
51 pub fn for_trait_impl(
52 source_scope: &'a SemanticsScope<'a>,
53 // FIXME: there's implicit invariant that `trait_` and `source_scope` match...
54 trait_: hir::Trait,
55 impl_def: ast::Impl,
56 ) -> SubstituteTypeParams<'a> {
57 let substs = get_syntactic_substs(impl_def).unwrap_or_default();
58 let generic_def: hir::GenericDef = trait_.into();
59 let substs_by_param: FxHashMap<_, _> = generic_def
60 .params(source_scope.db)
61 .into_iter()
62 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
63 .skip(1)
64 // The actual list of trait type parameters may be longer than the one
65 // used in the `impl` block due to trailing default type parameters.
66 // For that case we extend the `substs` with an empty iterator so we
67 // can still hit those trailing values and check if they actually have
68 // a default type. If they do, go for that type from `hir` to `ast` so
69 // the resulting change can be applied correctly.
70 .zip(substs.into_iter().map(Some).chain(std::iter::repeat(None)))
71 .filter_map(|(k, v)| match v {
72 Some(v) => Some((k, v)),
73 None => {
74 let default = k.default(source_scope.db)?;
75 Some((
76 k,
77 ast::make::ty(
78 &default
79 .display_source_code(source_scope.db, source_scope.module()?.into())
80 .ok()?,
81 ),
82 ))
83 }
84 })
85 .collect();
86 return SubstituteTypeParams {
87 source_scope,
88 substs: substs_by_param,
89 previous: Box::new(NullTransformer),
90 };
91
92 // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
93 // trait ref, and then go from the types in the substs back to the syntax).
94 fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::Type>> {
95 let target_trait = impl_def.trait_()?;
96 let path_type = match target_trait {
97 ast::Type::PathType(path) => path,
98 _ => return None,
99 };
100 let generic_arg_list = path_type.path()?.segment()?.generic_arg_list()?;
101
102 let mut result = Vec::new();
103 for generic_arg in generic_arg_list.generic_args() {
104 match generic_arg {
105 ast::GenericArg::TypeArg(type_arg) => result.push(type_arg.ty()?),
106 ast::GenericArg::AssocTypeArg(_)
107 | ast::GenericArg::LifetimeArg(_)
108 | ast::GenericArg::ConstArg(_) => (),
109 }
110 }
111
112 Some(result)
113 }
114 }
115 fn get_substitution_inner(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
116 let type_ref = ast::Type::cast(node.clone())?;
117 let path = match &type_ref {
118 ast::Type::PathType(path_type) => path_type.path()?,
119 _ => return None,
120 };
121 let resolution = self.source_scope.speculative_resolve(&path)?;
122 match resolution {
123 hir::PathResolution::TypeParam(tp) => Some(self.substs.get(&tp)?.syntax().clone()),
124 _ => None,
125 }
126 }
127}
128
129impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
130 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
131 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
132 }
133 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
134 Box::new(SubstituteTypeParams { previous: other, ..self })
135 }
136}
137
138pub struct QualifyPaths<'a> {
139 target_scope: &'a SemanticsScope<'a>,
140 source_scope: &'a SemanticsScope<'a>,
141 previous: Box<dyn AstTransform<'a> + 'a>,
142}
143
144impl<'a> QualifyPaths<'a> {
145 pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self {
146 Self { target_scope, source_scope, previous: Box::new(NullTransformer) }
147 }
148
149 fn get_substitution_inner(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
150 // FIXME handle value ns?
151 let from = self.target_scope.module()?;
152 let p = ast::Path::cast(node.clone())?;
153 if p.segment().and_then(|s| s.param_list()).is_some() {
154 // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
155 return None;
156 }
157 let resolution = self.source_scope.speculative_resolve(&p)?;
158 match resolution {
159 PathResolution::Def(def) => {
160 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
161 let mut path = path_to_ast(found_path);
162
163 let type_args = p
164 .segment()
165 .and_then(|s| s.generic_arg_list())
166 .map(|arg_list| apply(self, arg_list));
167 if let Some(type_args) = type_args {
168 let last_segment = path.segment().unwrap();
169 path = path.with_segment(last_segment.with_type_args(type_args))
170 }
171
172 Some(path.syntax().clone())
173 }
174 PathResolution::Local(_)
175 | PathResolution::TypeParam(_)
176 | PathResolution::SelfType(_) => None,
177 PathResolution::Macro(_) => None,
178 PathResolution::AssocItem(_) => None,
179 }
180 }
181}
182
183impl<'a> AstTransform<'a> for QualifyPaths<'a> {
184 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
185 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
186 }
187 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
188 Box::new(QualifyPaths { previous: other, ..self })
189 }
190}
191
192pub(crate) fn path_to_ast(path: hir::ModPath) -> ast::Path {
193 let parse = ast::SourceFile::parse(&path.to_string());
194 parse
195 .tree()
196 .syntax()
197 .descendants()
198 .find_map(ast::Path::cast)
199 .unwrap_or_else(|| panic!("failed to parse path {:?}, `{}`", path, path))
200}