aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2021-05-18 12:42:41 +0100
committerAleksey Kladov <[email protected]>2021-05-22 13:27:32 +0100
commit47d7434dde215460fc95916f2703c6925f58dcce (patch)
tree2eb017e9a32da4fb6f729b6b9c617c2516dafc40 /crates/ide_assists
parent3cfe2d0a5d663d29c3d196f9d16e91964780792a (diff)
internal: replace AstTransformer with mutable syntax trees
Diffstat (limited to 'crates/ide_assists')
-rw-r--r--crates/ide_assists/src/ast_transform.rs236
-rw-r--r--crates/ide_assists/src/utils.rs22
2 files changed, 104 insertions, 154 deletions
diff --git a/crates/ide_assists/src/ast_transform.rs b/crates/ide_assists/src/ast_transform.rs
index e5ae718c9..5d9cc5551 100644
--- a/crates/ide_assists/src/ast_transform.rs
+++ b/crates/ide_assists/src/ast_transform.rs
@@ -1,31 +1,12 @@
1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined. 1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined.
2use hir::{HirDisplay, PathResolution, SemanticsScope}; 2use hir::{HirDisplay, SemanticsScope};
3use ide_db::helpers::mod_path_to_ast; 3use ide_db::helpers::mod_path_to_ast;
4use rustc_hash::FxHashMap; 4use rustc_hash::FxHashMap;
5use syntax::{ 5use syntax::{
6 ast::{self, AstNode}, 6 ast::{self, AstNode},
7 ted, SyntaxNode, 7 ted,
8}; 8};
9 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. 10/// `AstTransform` helps with applying bulk transformations to syntax nodes.
30/// 11///
31/// This is mostly useful for IDE code generation. If you paste some existing 12/// This is mostly useful for IDE code generation. If you paste some existing
@@ -35,8 +16,8 @@ pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: &N) {
35/// 16///
36/// ``` 17/// ```
37/// mod x { 18/// mod x {
38/// pub struct A; 19/// pub struct A<V>;
39/// pub trait T<U> { fn foo(&self, _: U) -> A; } 20/// pub trait T<U> { fn foo(&self, _: U) -> A<U>; }
40/// } 21/// }
41/// 22///
42/// mod y { 23/// mod y {
@@ -45,7 +26,7 @@ pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: &N) {
45/// impl T<()> for () { 26/// impl T<()> for () {
46/// // If we invoke **Add Missing Members** here, we want to copy-paste `foo`. 27/// // If we invoke **Add Missing Members** here, we want to copy-paste `foo`.
47/// // But we want a slightly-modified version of it: 28/// // But we want a slightly-modified version of it:
48/// fn foo(&self, _: ()) -> x::A {} 29/// fn foo(&self, _: ()) -> x::A<()> {}
49/// } 30/// }
50/// } 31/// }
51/// ``` 32/// ```
@@ -54,49 +35,27 @@ pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: &N) {
54/// `SyntaxNode`. Note that the API here is a bit too high-order and high-brow. 35/// `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 36/// We'd want to somehow express this concept simpler, but so far nobody got to
56/// simplifying this! 37/// simplifying this!
57pub trait AstTransform<'a> { 38pub(crate) struct AstTransform<'a> {
58 fn get_substitution( 39 pub(crate) subst: (hir::Trait, ast::Impl),
59 &self, 40 pub(crate) target_scope: &'a SemanticsScope<'a>,
60 node: &SyntaxNode, 41 pub(crate) source_scope: &'a SemanticsScope<'a>,
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} 42}
71 43
72struct Or<'a>(Box<dyn AstTransform<'a> + 'a>, Box<dyn AstTransform<'a> + 'a>); 44impl<'a> AstTransform<'a> {
73 45 pub(crate) fn apply(&self, item: ast::AssocItem) {
74impl<'a> AstTransform<'a> for Or<'a> { 46 if let Some(ctx) = self.build_ctx() {
75 fn get_substitution( 47 ctx.apply(item)
76 &self, 48 }
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 } 49 }
82} 50 fn build_ctx(&self) -> Option<Ctx<'a>> {
83 51 let db = self.source_scope.db;
84pub struct SubstituteTypeParams<'a> { 52 let target_module = self.target_scope.module()?;
85 source_scope: &'a SemanticsScope<'a>, 53 let source_module = self.source_scope.module()?;
86 substs: FxHashMap<hir::TypeParam, ast::Type>,
87}
88 54
89impl<'a> SubstituteTypeParams<'a> { 55 let substs = get_syntactic_substs(self.subst.1.clone()).unwrap_or_default();
90 pub fn for_trait_impl( 56 let generic_def: hir::GenericDef = self.subst.0.into();
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 57 let substs_by_param: FxHashMap<_, _> = generic_def
99 .type_params(source_scope.db) 58 .type_params(db)
100 .into_iter() 59 .into_iter()
101 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky 60 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
102 .skip(1) 61 .skip(1)
@@ -110,109 +69,96 @@ impl<'a> SubstituteTypeParams<'a> {
110 .filter_map(|(k, v)| match v { 69 .filter_map(|(k, v)| match v {
111 Some(v) => Some((k, v)), 70 Some(v) => Some((k, v)),
112 None => { 71 None => {
113 let default = k.default(source_scope.db)?; 72 let default = k.default(db)?;
114 Some(( 73 Some((
115 k, 74 k,
116 ast::make::ty( 75 ast::make::ty(&default.display_source_code(db, source_module.into()).ok()?),
117 &default
118 .display_source_code(source_scope.db, source_scope.module()?.into())
119 .ok()?,
120 ),
121 )) 76 ))
122 } 77 }
123 }) 78 })
124 .collect(); 79 .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 80
147 Some(result) 81 let res = Ctx { substs: substs_by_param, target_module, source_scope: self.source_scope };
148 } 82 Some(res)
149 } 83 }
150} 84}
151 85
152impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> { 86struct Ctx<'a> {
153 fn get_substitution( 87 substs: FxHashMap<hir::TypeParam, ast::Type>,
154 &self, 88 target_module: hir::Module,
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>, 89 source_scope: &'a SemanticsScope<'a>,
174} 90}
175 91
176impl<'a> QualifyPaths<'a> { 92impl<'a> Ctx<'a> {
177 pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self { 93 fn apply(&self, item: ast::AssocItem) {
178 Self { target_scope, source_scope } 94 for event in item.syntax().preorder() {
95 let node = match event {
96 syntax::WalkEvent::Enter(_) => continue,
97 syntax::WalkEvent::Leave(it) => it,
98 };
99 if let Some(path) = ast::Path::cast(node.clone()) {
100 self.transform_path(path);
101 }
102 }
179 } 103 }
180} 104 fn transform_path(&self, path: ast::Path) -> Option<()> {
181 105 if path.qualifier().is_some() {
182impl<'a> AstTransform<'a> for QualifyPaths<'a> { 106 return None;
183 fn get_substitution( 107 }
184 &self, 108 if path.segment().and_then(|s| s.param_list()).is_some() {
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 109 // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
193 return None; 110 return None;
194 } 111 }
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 112
201 let type_args = p.segment().and_then(|s| s.generic_arg_list()); 113 let resolution = self.source_scope.speculative_resolve(&path)?;
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 114
208 Some(path.syntax().clone()) 115 match resolution {
116 hir::PathResolution::TypeParam(tp) => {
117 if let Some(subst) = self.substs.get(&tp) {
118 ted::replace(path.syntax(), subst.clone_subtree().clone_for_update().syntax())
119 }
120 }
121 hir::PathResolution::Def(def) => {
122 let found_path =
123 self.target_module.find_use_path(self.source_scope.db.upcast(), def)?;
124 let res = mod_path_to_ast(&found_path).clone_for_update();
125 if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) {
126 if let Some(segment) = res.segment() {
127 let old = segment.get_or_create_generic_arg_list();
128 ted::replace(old.syntax(), args.clone_subtree().syntax().clone_for_update())
129 }
130 }
131 ted::replace(path.syntax(), res.syntax())
209 } 132 }
210 PathResolution::Local(_) 133 hir::PathResolution::Local(_)
211 | PathResolution::TypeParam(_) 134 | hir::PathResolution::ConstParam(_)
212 | PathResolution::SelfType(_) 135 | hir::PathResolution::SelfType(_)
213 | PathResolution::ConstParam(_) => None, 136 | hir::PathResolution::Macro(_)
214 PathResolution::Macro(_) => None, 137 | hir::PathResolution::AssocItem(_) => (),
215 PathResolution::AssocItem(_) => None,
216 } 138 }
139 Some(())
217 } 140 }
218} 141}
142
143// FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
144// trait ref, and then go from the types in the substs back to the syntax).
145fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::Type>> {
146 let target_trait = impl_def.trait_()?;
147 let path_type = match target_trait {
148 ast::Type::PathType(path) => path,
149 _ => return None,
150 };
151 let generic_arg_list = path_type.path()?.segment()?.generic_arg_list()?;
152
153 let mut result = Vec::new();
154 for generic_arg in generic_arg_list.generic_args() {
155 match generic_arg {
156 ast::GenericArg::TypeArg(type_arg) => result.push(type_arg.ty()?),
157 ast::GenericArg::AssocTypeArg(_)
158 | ast::GenericArg::LifetimeArg(_)
159 | ast::GenericArg::ConstArg(_) => (),
160 }
161 }
162
163 Some(result)
164}
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs
index fc7caee04..2e79a3aed 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 ast_transform::AstTransform,
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 = AstTransform {
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