aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-05-22 14:30:32 +0100
committerGitHub <[email protected]>2021-05-22 14:30:32 +0100
commit542337eca49986d785db2318bfe9c70809d4a229 (patch)
tree8a19fddbb72a4871a1840a45321c3c3b3af0876c /crates/ide_assists
parent057e2ed574bb54afeca35f032774bb8b94aaa1d1 (diff)
parente6776c3e1b66c8946873d20e1e3bc1d743c952fe (diff)
Merge #8868
8868: internal: replace AstTransformer with mutable syntax trees r=matklad a=matklad Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ide_assists')
-rw-r--r--crates/ide_assists/src/ast_transform.rs218
-rw-r--r--crates/ide_assists/src/lib.rs2
-rw-r--r--crates/ide_assists/src/path_transform.rs159
-rw-r--r--crates/ide_assists/src/utils.rs22
4 files changed, 173 insertions, 228 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}
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index 2e0c58504..05644b6ff 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -15,7 +15,7 @@ mod assist_context;
15#[cfg(test)] 15#[cfg(test)]
16mod tests; 16mod tests;
17pub mod utils; 17pub mod utils;
18pub mod ast_transform; 18pub mod path_transform;
19 19
20use std::str::FromStr; 20use std::str::FromStr;
21 21
diff --git a/crates/ide_assists/src/path_transform.rs b/crates/ide_assists/src/path_transform.rs
new file mode 100644
index 000000000..6ec318c4c
--- /dev/null
+++ b/crates/ide_assists/src/path_transform.rs
@@ -0,0 +1,159 @@
1//! See `PathTransform`
2use hir::{HirDisplay, SemanticsScope};
3use ide_db::helpers::mod_path_to_ast;
4use rustc_hash::FxHashMap;
5use syntax::{
6 ast::{self, AstNode},
7 ted,
8};
9
10/// `PathTransform` substitutes path in SyntaxNodes in bulk.
11///
12/// This is mostly useful for IDE code generation. If you paste some existing
13/// code into a new context (for example, to add method overrides to an `impl`
14/// block), you generally want to appropriately qualify the names, and sometimes
15/// you might want to substitute generic parameters as well:
16///
17/// ```
18/// mod x {
19/// pub struct A<V>;
20/// pub trait T<U> { fn foo(&self, _: U) -> A<U>; }
21/// }
22///
23/// mod y {
24/// use x::T;
25///
26/// impl T<()> for () {
27/// // If we invoke **Add Missing Members** here, we want to copy-paste `foo`.
28/// // But we want a slightly-modified version of it:
29/// fn foo(&self, _: ()) -> x::A<()> {}
30/// }
31/// }
32/// ```
33pub(crate) struct PathTransform<'a> {
34 pub(crate) subst: (hir::Trait, ast::Impl),
35 pub(crate) target_scope: &'a SemanticsScope<'a>,
36 pub(crate) source_scope: &'a SemanticsScope<'a>,
37}
38
39impl<'a> PathTransform<'a> {
40 pub(crate) fn apply(&self, item: ast::AssocItem) {
41 if let Some(ctx) = self.build_ctx() {
42 ctx.apply(item)
43 }
44 }
45 fn build_ctx(&self) -> Option<Ctx<'a>> {
46 let db = self.source_scope.db;
47 let target_module = self.target_scope.module()?;
48 let source_module = self.source_scope.module()?;
49
50 let substs = get_syntactic_substs(self.subst.1.clone()).unwrap_or_default();
51 let generic_def: hir::GenericDef = self.subst.0.into();
52 let substs_by_param: FxHashMap<_, _> = generic_def
53 .type_params(db)
54 .into_iter()
55 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
56 .skip(1)
57 // The actual list of trait type parameters may be longer than the one
58 // used in the `impl` block due to trailing default type parameters.
59 // For that case we extend the `substs` with an empty iterator so we
60 // can still hit those trailing values and check if they actually have
61 // a default type. If they do, go for that type from `hir` to `ast` so
62 // the resulting change can be applied correctly.
63 .zip(substs.into_iter().map(Some).chain(std::iter::repeat(None)))
64 .filter_map(|(k, v)| match v {
65 Some(v) => Some((k, v)),
66 None => {
67 let default = k.default(db)?;
68 Some((
69 k,
70 ast::make::ty(&default.display_source_code(db, source_module.into()).ok()?),
71 ))
72 }
73 })
74 .collect();
75
76 let res = Ctx { substs: substs_by_param, target_module, source_scope: self.source_scope };
77 Some(res)
78 }
79}
80
81struct Ctx<'a> {
82 substs: FxHashMap<hir::TypeParam, ast::Type>,
83 target_module: hir::Module,
84 source_scope: &'a SemanticsScope<'a>,
85}
86
87impl<'a> Ctx<'a> {
88 fn apply(&self, item: ast::AssocItem) {
89 for event in item.syntax().preorder() {
90 let node = match event {
91 syntax::WalkEvent::Enter(_) => continue,
92 syntax::WalkEvent::Leave(it) => it,
93 };
94 if let Some(path) = ast::Path::cast(node.clone()) {
95 self.transform_path(path);
96 }
97 }
98 }
99 fn transform_path(&self, path: ast::Path) -> Option<()> {
100 if path.qualifier().is_some() {
101 return None;
102 }
103 if path.segment().and_then(|s| s.param_list()).is_some() {
104 // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
105 return None;
106 }
107
108 let resolution = self.source_scope.speculative_resolve(&path)?;
109
110 match resolution {
111 hir::PathResolution::TypeParam(tp) => {
112 if let Some(subst) = self.substs.get(&tp) {
113 ted::replace(path.syntax(), subst.clone_subtree().clone_for_update().syntax())
114 }
115 }
116 hir::PathResolution::Def(def) => {
117 let found_path =
118 self.target_module.find_use_path(self.source_scope.db.upcast(), def)?;
119 let res = mod_path_to_ast(&found_path).clone_for_update();
120 if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) {
121 if let Some(segment) = res.segment() {
122 let old = segment.get_or_create_generic_arg_list();
123 ted::replace(old.syntax(), args.clone_subtree().syntax().clone_for_update())
124 }
125 }
126 ted::replace(path.syntax(), res.syntax())
127 }
128 hir::PathResolution::Local(_)
129 | hir::PathResolution::ConstParam(_)
130 | hir::PathResolution::SelfType(_)
131 | hir::PathResolution::Macro(_)
132 | hir::PathResolution::AssocItem(_) => (),
133 }
134 Some(())
135 }
136}
137
138// FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
139// trait ref, and then go from the types in the substs back to the syntax).
140fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::Type>> {
141 let target_trait = impl_def.trait_()?;
142 let path_type = match target_trait {
143 ast::Type::PathType(path) => path,
144 _ => return None,
145 };
146 let generic_arg_list = path_type.path()?.segment()?.generic_arg_list()?;
147
148 let mut result = Vec::new();
149 for generic_arg in generic_arg_list.generic_args() {
150 match generic_arg {
151 ast::GenericArg::TypeArg(type_arg) => result.push(type_arg.ty()?),
152 ast::GenericArg::AssocTypeArg(_)
153 | ast::GenericArg::LifetimeArg(_)
154 | ast::GenericArg::ConstArg(_) => (),
155 }
156 }
157
158 Some(result)
159}
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs
index fc7caee04..30128a24a 100644
--- a/crates/ide_assists/src/utils.rs
+++ b/crates/ide_assists/src/utils.rs
@@ -24,7 +24,7 @@ use syntax::{
24 24
25use crate::{ 25use crate::{
26 assist_context::{AssistBuilder, AssistContext}, 26 assist_context::{AssistBuilder, AssistContext},
27 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, 27 path_transform::PathTransform,
28}; 28};
29 29
30pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { 30pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr {
@@ -132,14 +132,18 @@ pub fn add_trait_assoc_items_to_impl(
132 target_scope: hir::SemanticsScope, 132 target_scope: hir::SemanticsScope,
133) -> (ast::Impl, ast::AssocItem) { 133) -> (ast::Impl, ast::AssocItem) {
134 let source_scope = sema.scope_for_def(trait_); 134 let source_scope = sema.scope_for_def(trait_);
135 let ast_transform = QualifyPaths::new(&target_scope, &source_scope) 135
136 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_.clone())); 136 let transform = PathTransform {
137 137 subst: (trait_, impl_.clone()),
138 let items = items 138 source_scope: &source_scope,
139 .into_iter() 139 target_scope: &target_scope,
140 .map(|it| it.clone_for_update()) 140 };
141 .inspect(|it| ast_transform::apply(&*ast_transform, it)) 141
142 .map(|it| edit::remove_attrs_and_docs(&it).clone_subtree().clone_for_update()); 142 let items = items.into_iter().map(|assoc_item| {
143 let assoc_item = assoc_item.clone_for_update();
144 transform.apply(assoc_item.clone());
145 edit::remove_attrs_and_docs(&assoc_item).clone_subtree().clone_for_update()
146 });
143 147
144 let res = impl_.clone_for_update(); 148 let res = impl_.clone_for_update();
145 149