aboutsummaryrefslogtreecommitdiff
path: root/crates
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
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')
-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
-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
8 files changed, 192 insertions, 282 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
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);