aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide_assists/src/ast_transform.rs236
-rw-r--r--crates/ide_assists/src/utils.rs22
-rw-r--r--crates/syntax/src/ast/edit.rs56
-rw-r--r--crates/syntax/src/ast/edit_in_place.rs10
-rw-r--r--crates/syntax/src/ast/make.rs4
-rw-r--r--crates/syntax/src/ted.rs3
6 files changed, 123 insertions, 208 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
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 61952377f..19107ee38 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -6,14 +6,12 @@ use std::{
6 ops::{self, RangeInclusive}, 6 ops::{self, RangeInclusive},
7}; 7};
8 8
9use arrayvec::ArrayVec;
10
11use crate::{ 9use crate::{
12 algo, 10 algo,
13 ast::{self, make, AstNode}, 11 ast::{self, make, AstNode},
14 ted, AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxKind, 12 ted, AstToken, NodeOrToken, SyntaxElement, SyntaxKind,
15 SyntaxKind::{ATTR, COMMENT, WHITESPACE}, 13 SyntaxKind::{ATTR, COMMENT, WHITESPACE},
16 SyntaxNode, SyntaxToken, T, 14 SyntaxNode, SyntaxToken,
17}; 15};
18 16
19impl ast::BinExpr { 17impl ast::BinExpr {
@@ -25,46 +23,6 @@ impl ast::BinExpr {
25 } 23 }
26} 24}
27 25
28impl ast::Path {
29 #[must_use]
30 pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path {
31 if let Some(old) = self.segment() {
32 return self.replace_children(
33 single_node(old.syntax().clone()),
34 iter::once(segment.syntax().clone().into()),
35 );
36 }
37 self.clone()
38 }
39}
40
41impl ast::PathSegment {
42 #[must_use]
43 pub fn with_generic_args(&self, type_args: ast::GenericArgList) -> ast::PathSegment {
44 self._with_generic_args(type_args, false)
45 }
46
47 #[must_use]
48 pub fn with_turbo_fish(&self, type_args: ast::GenericArgList) -> ast::PathSegment {
49 self._with_generic_args(type_args, true)
50 }
51
52 fn _with_generic_args(&self, type_args: ast::GenericArgList, turbo: bool) -> ast::PathSegment {
53 if let Some(old) = self.generic_arg_list() {
54 return self.replace_children(
55 single_node(old.syntax().clone()),
56 iter::once(type_args.syntax().clone().into()),
57 );
58 }
59 let mut to_insert: ArrayVec<SyntaxElement, 2> = ArrayVec::new();
60 if turbo {
61 to_insert.push(make::token(T![::]).into());
62 }
63 to_insert.push(type_args.syntax().clone().into());
64 self.insert_children(InsertPosition::Last, to_insert)
65 }
66}
67
68impl ast::UseTree { 26impl ast::UseTree {
69 /// Splits off the given prefix, making it the path component of the use tree, appending the rest of the path to all UseTreeList items. 27 /// Splits off the given prefix, making it the path component of the use tree, appending the rest of the path to all UseTreeList items.
70 #[must_use] 28 #[must_use]
@@ -234,16 +192,6 @@ fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> {
234 192
235pub trait AstNodeEdit: AstNode + Clone + Sized { 193pub trait AstNodeEdit: AstNode + Clone + Sized {
236 #[must_use] 194 #[must_use]
237 fn insert_children(
238 &self,
239 position: InsertPosition<SyntaxElement>,
240 to_insert: impl IntoIterator<Item = SyntaxElement>,
241 ) -> Self {
242 let new_syntax = algo::insert_children(self.syntax(), position, to_insert);
243 Self::cast(new_syntax).unwrap()
244 }
245
246 #[must_use]
247 fn replace_children( 195 fn replace_children(
248 &self, 196 &self,
249 to_replace: RangeInclusive<SyntaxElement>, 197 to_replace: RangeInclusive<SyntaxElement>,
diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs
index 2676ed8c9..ca8103668 100644
--- a/crates/syntax/src/ast/edit_in_place.rs
+++ b/crates/syntax/src/ast/edit_in_place.rs
@@ -239,6 +239,16 @@ impl ast::TypeBoundList {
239 } 239 }
240} 240}
241 241
242impl ast::PathSegment {
243 pub fn get_or_create_generic_arg_list(&self) -> ast::GenericArgList {
244 if self.generic_arg_list().is_none() {
245 let arg_list = make::generic_arg_list().clone_for_update();
246 ted::append_child(self.syntax(), arg_list.syntax())
247 }
248 self.generic_arg_list().unwrap()
249 }
250}
251
242impl ast::UseTree { 252impl ast::UseTree {
243 pub fn remove(&self) { 253 pub fn remove(&self) {
244 for &dir in [Direction::Next, Direction::Prev].iter() { 254 for &dir in [Direction::Next, Direction::Prev].iter() {
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index d13926ded..0cf170626 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -106,6 +106,10 @@ pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl {
106 ast_from_text(&format!("impl {} for {} {{}}", trait_, ty)) 106 ast_from_text(&format!("impl {} for {} {{}}", trait_, ty))
107} 107}
108 108
109pub(crate) fn generic_arg_list() -> ast::GenericArgList {
110 ast_from_text("const S: T<> = ();")
111}
112
109pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment { 113pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment {
110 ast_from_text(&format!("use {};", name_ref)) 114 ast_from_text(&format!("use {};", name_ref))
111} 115}
diff --git a/crates/syntax/src/ted.rs b/crates/syntax/src/ted.rs
index a50c0dbca..ae970f44f 100644
--- a/crates/syntax/src/ted.rs
+++ b/crates/syntax/src/ted.rs
@@ -184,6 +184,9 @@ fn ws_between(left: &SyntaxElement, right: &SyntaxElement) -> Option<SyntaxToken
184 if left.kind() == T![&] && right.kind() == SyntaxKind::LIFETIME { 184 if left.kind() == T![&] && right.kind() == SyntaxKind::LIFETIME {
185 return None; 185 return None;
186 } 186 }
187 if right.kind() == SyntaxKind::GENERIC_ARG_LIST {
188 return None;
189 }
187 190
188 if right.kind() == SyntaxKind::USE { 191 if right.kind() == SyntaxKind::USE {
189 let mut indent = IndentLevel::from_element(left); 192 let mut indent = IndentLevel::from_element(left);