aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/ast_transform.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/ast_transform.rs')
-rw-r--r--crates/ide_assists/src/ast_transform.rs218
1 files changed, 0 insertions, 218 deletions
diff --git a/crates/ide_assists/src/ast_transform.rs b/crates/ide_assists/src/ast_transform.rs
deleted file mode 100644
index e5ae718c9..000000000
--- a/crates/ide_assists/src/ast_transform.rs
+++ /dev/null
@@ -1,218 +0,0 @@
1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined.
2use hir::{HirDisplay, PathResolution, SemanticsScope};
3use ide_db::helpers::mod_path_to_ast;
4use rustc_hash::FxHashMap;
5use syntax::{
6 ast::{self, AstNode},
7 ted, SyntaxNode,
8};
9
10pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: &N) {
11 let mut skip_to = None;
12 for event in node.syntax().preorder() {
13 match event {
14 syntax::WalkEvent::Enter(node) if skip_to.is_none() => {
15 skip_to = transformer.get_substitution(&node, transformer).zip(Some(node));
16 }
17 syntax::WalkEvent::Enter(_) => (),
18 syntax::WalkEvent::Leave(node) => match &skip_to {
19 Some((replacement, skip_target)) if *skip_target == node => {
20 ted::replace(node, replacement.clone_for_update());
21 skip_to.take();
22 }
23 _ => (),
24 },
25 }
26 }
27}
28
29/// `AstTransform` helps with applying bulk transformations to syntax nodes.
30///
31/// This is mostly useful for IDE code generation. If you paste some existing
32/// code into a new context (for example, to add method overrides to an `impl`
33/// block), you generally want to appropriately qualify the names, and sometimes
34/// you might want to substitute generic parameters as well:
35///
36/// ```
37/// mod x {
38/// pub struct A;
39/// pub trait T<U> { fn foo(&self, _: U) -> A; }
40/// }
41///
42/// mod y {
43/// use x::T;
44///
45/// impl T<()> for () {
46/// // If we invoke **Add Missing Members** here, we want to copy-paste `foo`.
47/// // But we want a slightly-modified version of it:
48/// fn foo(&self, _: ()) -> x::A {}
49/// }
50/// }
51/// ```
52///
53/// So, a single `AstTransform` describes such function from `SyntaxNode` to
54/// `SyntaxNode`. Note that the API here is a bit too high-order and high-brow.
55/// We'd want to somehow express this concept simpler, but so far nobody got to
56/// simplifying this!
57pub trait AstTransform<'a> {
58 fn get_substitution(
59 &self,
60 node: &SyntaxNode,
61 recur: &dyn AstTransform<'a>,
62 ) -> Option<SyntaxNode>;
63
64 fn or<T: AstTransform<'a> + 'a>(self, other: T) -> Box<dyn AstTransform<'a> + 'a>
65 where
66 Self: Sized + 'a,
67 {
68 Box::new(Or(Box::new(self), Box::new(other)))
69 }
70}
71
72struct Or<'a>(Box<dyn AstTransform<'a> + 'a>, Box<dyn AstTransform<'a> + 'a>);
73
74impl<'a> AstTransform<'a> for Or<'a> {
75 fn get_substitution(
76 &self,
77 node: &SyntaxNode,
78 recur: &dyn AstTransform<'a>,
79 ) -> Option<SyntaxNode> {
80 self.0.get_substitution(node, recur).or_else(|| self.1.get_substitution(node, recur))
81 }
82}
83
84pub struct SubstituteTypeParams<'a> {
85 source_scope: &'a SemanticsScope<'a>,
86 substs: FxHashMap<hir::TypeParam, ast::Type>,
87}
88
89impl<'a> SubstituteTypeParams<'a> {
90 pub fn for_trait_impl(
91 source_scope: &'a SemanticsScope<'a>,
92 // FIXME: there's implicit invariant that `trait_` and `source_scope` match...
93 trait_: hir::Trait,
94 impl_def: ast::Impl,
95 ) -> SubstituteTypeParams<'a> {
96 let substs = get_syntactic_substs(impl_def).unwrap_or_default();
97 let generic_def: hir::GenericDef = trait_.into();
98 let substs_by_param: FxHashMap<_, _> = generic_def
99 .type_params(source_scope.db)
100 .into_iter()
101 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
102 .skip(1)
103 // The actual list of trait type parameters may be longer than the one
104 // used in the `impl` block due to trailing default type parameters.
105 // For that case we extend the `substs` with an empty iterator so we
106 // can still hit those trailing values and check if they actually have
107 // a default type. If they do, go for that type from `hir` to `ast` so
108 // the resulting change can be applied correctly.
109 .zip(substs.into_iter().map(Some).chain(std::iter::repeat(None)))
110 .filter_map(|(k, v)| match v {
111 Some(v) => Some((k, v)),
112 None => {
113 let default = k.default(source_scope.db)?;
114 Some((
115 k,
116 ast::make::ty(
117 &default
118 .display_source_code(source_scope.db, source_scope.module()?.into())
119 .ok()?,
120 ),
121 ))
122 }
123 })
124 .collect();
125 return SubstituteTypeParams { source_scope, substs: substs_by_param };
126
127 // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
128 // trait ref, and then go from the types in the substs back to the syntax).
129 fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::Type>> {
130 let target_trait = impl_def.trait_()?;
131 let path_type = match target_trait {
132 ast::Type::PathType(path) => path,
133 _ => return None,
134 };
135 let generic_arg_list = path_type.path()?.segment()?.generic_arg_list()?;
136
137 let mut result = Vec::new();
138 for generic_arg in generic_arg_list.generic_args() {
139 match generic_arg {
140 ast::GenericArg::TypeArg(type_arg) => result.push(type_arg.ty()?),
141 ast::GenericArg::AssocTypeArg(_)
142 | ast::GenericArg::LifetimeArg(_)
143 | ast::GenericArg::ConstArg(_) => (),
144 }
145 }
146
147 Some(result)
148 }
149 }
150}
151
152impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
153 fn get_substitution(
154 &self,
155 node: &SyntaxNode,
156 _recur: &dyn AstTransform<'a>,
157 ) -> Option<SyntaxNode> {
158 let type_ref = ast::Type::cast(node.clone())?;
159 let path = match &type_ref {
160 ast::Type::PathType(path_type) => path_type.path()?,
161 _ => return None,
162 };
163 let resolution = self.source_scope.speculative_resolve(&path)?;
164 match resolution {
165 hir::PathResolution::TypeParam(tp) => Some(self.substs.get(&tp)?.syntax().clone()),
166 _ => None,
167 }
168 }
169}
170
171pub struct QualifyPaths<'a> {
172 target_scope: &'a SemanticsScope<'a>,
173 source_scope: &'a SemanticsScope<'a>,
174}
175
176impl<'a> QualifyPaths<'a> {
177 pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self {
178 Self { target_scope, source_scope }
179 }
180}
181
182impl<'a> AstTransform<'a> for QualifyPaths<'a> {
183 fn get_substitution(
184 &self,
185 node: &SyntaxNode,
186 recur: &dyn AstTransform<'a>,
187 ) -> Option<SyntaxNode> {
188 // FIXME handle value ns?
189 let from = self.target_scope.module()?;
190 let p = ast::Path::cast(node.clone())?;
191 if p.segment().and_then(|s| s.param_list()).is_some() {
192 // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
193 return None;
194 }
195 let resolution = self.source_scope.speculative_resolve(&p)?;
196 match resolution {
197 PathResolution::Def(def) => {
198 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
199 let mut path = mod_path_to_ast(&found_path);
200
201 let type_args = p.segment().and_then(|s| s.generic_arg_list());
202 if let Some(type_args) = type_args {
203 apply(recur, &type_args);
204 let last_segment = path.segment().unwrap();
205 path = path.with_segment(last_segment.with_generic_args(type_args))
206 }
207
208 Some(path.syntax().clone())
209 }
210 PathResolution::Local(_)
211 | PathResolution::TypeParam(_)
212 | PathResolution::SelfType(_)
213 | PathResolution::ConstParam(_) => None,
214 PathResolution::Macro(_) => None,
215 PathResolution::AssocItem(_) => None,
216 }
217 }
218}