Skip to content

Commit cea589b

Browse files
committed
internal: rewrite assoc item manipulaion to use mutable trees
1 parent 73123a7 commit cea589b

File tree

7 files changed

+89
-168
lines changed

7 files changed

+89
-168
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ incremental = false
99

1010
# Disabling debug info speeds up builds a bunch,
1111
# and we don't rely on it for debugging that much.
12-
debug = 0
12+
debug = 1
1313

1414
[profile.dev.package]
1515
# These speed up local tests.

crates/ide_assists/src/handlers/add_missing_impl_members.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -
6464
// impl Trait for () {
6565
// type X = ();
6666
// fn foo(&self) {}$0
67-
//
6867
// }
6968
// ```
7069
// ->
@@ -195,6 +194,7 @@ impl Foo for S {
195194
fn baz(&self) {
196195
todo!()
197196
}
197+
198198
}"#,
199199
);
200200
}
@@ -231,6 +231,7 @@ impl Foo for S {
231231
fn foo(&self) {
232232
${0:todo!()}
233233
}
234+
234235
}"#,
235236
);
236237
}

crates/ide_assists/src/tests/generated.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ trait Trait {
5050
impl Trait for () {
5151
type X = ();
5252
fn foo(&self) {}$0
53-
5453
}
5554
"#####,
5655
r#####"

crates/ide_assists/src/utils.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,12 @@ pub fn add_trait_assoc_items_to_impl(
128128
sema: &hir::Semantics<ide_db::RootDatabase>,
129129
items: Vec<ast::AssocItem>,
130130
trait_: hir::Trait,
131-
impl_def: ast::Impl,
131+
impl_: ast::Impl,
132132
target_scope: hir::SemanticsScope,
133133
) -> (ast::Impl, ast::AssocItem) {
134-
let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list);
135-
136-
let n_existing_items = impl_item_list.assoc_items().count();
137134
let source_scope = sema.scope_for_def(trait_);
138135
let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
139-
.or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone()));
136+
.or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_.clone()));
140137

141138
let items = items
142139
.into_iter()
@@ -147,13 +144,18 @@ pub fn add_trait_assoc_items_to_impl(
147144
ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
148145
_ => it,
149146
})
150-
.map(|it| edit::remove_attrs_and_docs(&it));
151-
152-
let new_impl_item_list = impl_item_list.append_items(items);
153-
let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list);
154-
let first_new_item =
155-
new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap();
156-
return (new_impl_def, first_new_item);
147+
.map(|it| edit::remove_attrs_and_docs(&it).clone_subtree().clone_for_update());
148+
149+
let res = impl_.clone_for_update();
150+
let assoc_item_list = res.get_or_create_assoc_item_list();
151+
let mut first_item = None;
152+
for item in items {
153+
if first_item.is_none() {
154+
first_item = Some(item.clone())
155+
}
156+
assoc_item_list.add_item(item)
157+
}
158+
return (res, first_item.unwrap());
157159

158160
fn add_body(fn_def: ast::Fn) -> ast::Fn {
159161
match fn_def.body() {

crates/syntax/src/ast/edit.rs

Lines changed: 6 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -80,81 +80,6 @@ where
8080
}
8181
}
8282

83-
impl ast::Impl {
84-
#[must_use]
85-
pub fn with_assoc_item_list(&self, items: ast::AssocItemList) -> ast::Impl {
86-
let mut to_insert: ArrayVec<SyntaxElement, 2> = ArrayVec::new();
87-
if let Some(old_items) = self.assoc_item_list() {
88-
let to_replace: SyntaxElement = old_items.syntax().clone().into();
89-
to_insert.push(items.syntax().clone().into());
90-
self.replace_children(single_node(to_replace), to_insert)
91-
} else {
92-
to_insert.push(make::tokens::single_space().into());
93-
to_insert.push(items.syntax().clone().into());
94-
self.insert_children(InsertPosition::Last, to_insert)
95-
}
96-
}
97-
}
98-
99-
impl ast::AssocItemList {
100-
#[must_use]
101-
pub fn append_items(
102-
&self,
103-
items: impl IntoIterator<Item = ast::AssocItem>,
104-
) -> ast::AssocItemList {
105-
let mut res = self.clone();
106-
if !self.syntax().text().contains_char('\n') {
107-
res = make_multiline(res);
108-
}
109-
items.into_iter().for_each(|it| res = res.append_item(it));
110-
res.fixup_trailing_whitespace().unwrap_or(res)
111-
}
112-
113-
#[must_use]
114-
pub fn append_item(&self, item: ast::AssocItem) -> ast::AssocItemList {
115-
let (indent, position, whitespace) = match self.assoc_items().last() {
116-
Some(it) => (
117-
leading_indent(it.syntax()).unwrap_or_default().to_string(),
118-
InsertPosition::After(it.syntax().clone().into()),
119-
"\n\n",
120-
),
121-
None => match self.l_curly_token() {
122-
Some(it) => (
123-
" ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(),
124-
InsertPosition::After(it.into()),
125-
"\n",
126-
),
127-
None => return self.clone(),
128-
},
129-
};
130-
let ws = tokens::WsBuilder::new(&format!("{}{}", whitespace, indent));
131-
let to_insert: ArrayVec<SyntaxElement, 2> =
132-
[ws.ws().into(), item.syntax().clone().into()].into();
133-
self.insert_children(position, to_insert)
134-
}
135-
136-
/// Remove extra whitespace between last item and closing curly brace.
137-
fn fixup_trailing_whitespace(&self) -> Option<ast::AssocItemList> {
138-
let first_token_after_items =
139-
self.assoc_items().last()?.syntax().next_sibling_or_token()?;
140-
let last_token_before_curly = self.r_curly_token()?.prev_sibling_or_token()?;
141-
if last_token_before_curly != first_token_after_items {
142-
// there is something more between last item and
143-
// right curly than just whitespace - bail out
144-
return None;
145-
}
146-
let whitespace =
147-
last_token_before_curly.clone().into_token().and_then(ast::Whitespace::cast)?;
148-
let text = whitespace.syntax().text();
149-
let newline = text.rfind('\n')?;
150-
let keep = tokens::WsBuilder::new(&text[newline..]);
151-
Some(self.replace_children(
152-
first_token_after_items..=last_token_before_curly,
153-
std::iter::once(keep.ws().into()),
154-
))
155-
}
156-
}
157-
15883
impl ast::RecordExprFieldList {
15984
#[must_use]
16085
pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList {
@@ -246,21 +171,6 @@ impl ast::TypeAlias {
246171
}
247172
}
248173

249-
impl ast::TypeParam {
250-
#[must_use]
251-
pub fn remove_bounds(&self) -> ast::TypeParam {
252-
let colon = match self.colon_token() {
253-
Some(it) => it,
254-
None => return self.clone(),
255-
};
256-
let end = match self.type_bound_list() {
257-
Some(it) => it.syntax().clone().into(),
258-
None => colon.clone().into(),
259-
};
260-
self.replace_children(colon.into()..=end, iter::empty())
261-
}
262-
}
263-
264174
impl ast::Path {
265175
#[must_use]
266176
pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path {
@@ -411,61 +321,6 @@ impl ast::MatchArmList {
411321
}
412322
}
413323

414-
impl ast::GenericParamList {
415-
#[must_use]
416-
pub fn append_params(
417-
&self,
418-
params: impl IntoIterator<Item = ast::GenericParam>,
419-
) -> ast::GenericParamList {
420-
let mut res = self.clone();
421-
params.into_iter().for_each(|it| res = res.append_param(it));
422-
res
423-
}
424-
425-
#[must_use]
426-
pub fn append_param(&self, item: ast::GenericParam) -> ast::GenericParamList {
427-
let space = tokens::single_space();
428-
429-
let mut to_insert: ArrayVec<SyntaxElement, 4> = ArrayVec::new();
430-
if self.generic_params().next().is_some() {
431-
to_insert.push(space.into());
432-
}
433-
to_insert.push(item.syntax().clone().into());
434-
435-
macro_rules! after_l_angle {
436-
() => {{
437-
let anchor = match self.l_angle_token() {
438-
Some(it) => it.into(),
439-
None => return self.clone(),
440-
};
441-
InsertPosition::After(anchor)
442-
}};
443-
}
444-
445-
macro_rules! after_field {
446-
($anchor:expr) => {
447-
if let Some(comma) = $anchor
448-
.syntax()
449-
.siblings_with_tokens(Direction::Next)
450-
.find(|it| it.kind() == T![,])
451-
{
452-
InsertPosition::After(comma)
453-
} else {
454-
to_insert.insert(0, make::token(T![,]).into());
455-
InsertPosition::After($anchor.syntax().clone().into())
456-
}
457-
};
458-
}
459-
460-
let position = match self.generic_params().last() {
461-
Some(it) => after_field!(it),
462-
None => after_l_angle!(),
463-
};
464-
465-
self.insert_children(position, to_insert)
466-
}
467-
}
468-
469324
#[must_use]
470325
pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
471326
N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
@@ -516,6 +371,12 @@ impl ops::Add<u8> for IndentLevel {
516371
}
517372

518373
impl IndentLevel {
374+
pub fn single() -> IndentLevel {
375+
IndentLevel(0)
376+
}
377+
pub fn is_zero(&self) -> bool {
378+
self.0 == 0
379+
}
519380
pub fn from_element(element: &SyntaxElement) -> IndentLevel {
520381
match element {
521382
rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it),

crates/syntax/src/ast/edit_in_place.rs

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22
33
use std::iter::empty;
44

5-
use parser::T;
5+
use parser::{SyntaxKind, T};
6+
use rowan::SyntaxElement;
67

78
use crate::{
89
algo::neighbor,
9-
ast::{self, edit::AstNodeEdit, make, GenericParamsOwner, WhereClause},
10+
ast::{
11+
self,
12+
edit::{AstNodeEdit, IndentLevel},
13+
make, GenericParamsOwner,
14+
},
1015
ted::{self, Position},
1116
AstNode, AstToken, Direction,
1217
};
@@ -37,7 +42,7 @@ impl GenericParamsOwnerEdit for ast::Fn {
3742
}
3843
}
3944

40-
fn get_or_create_where_clause(&self) -> WhereClause {
45+
fn get_or_create_where_clause(&self) -> ast::WhereClause {
4146
if self.where_clause().is_none() {
4247
let position = if let Some(ty) = self.ret_type() {
4348
Position::after(ty.syntax())
@@ -67,7 +72,7 @@ impl GenericParamsOwnerEdit for ast::Impl {
6772
}
6873
}
6974

70-
fn get_or_create_where_clause(&self) -> WhereClause {
75+
fn get_or_create_where_clause(&self) -> ast::WhereClause {
7176
if self.where_clause().is_none() {
7277
let position = if let Some(items) = self.assoc_item_list() {
7378
Position::before(items.syntax())
@@ -97,7 +102,7 @@ impl GenericParamsOwnerEdit for ast::Trait {
97102
}
98103
}
99104

100-
fn get_or_create_where_clause(&self) -> WhereClause {
105+
fn get_or_create_where_clause(&self) -> ast::WhereClause {
101106
if self.where_clause().is_none() {
102107
let position = if let Some(items) = self.assoc_item_list() {
103108
Position::before(items.syntax())
@@ -127,7 +132,7 @@ impl GenericParamsOwnerEdit for ast::Struct {
127132
}
128133
}
129134

130-
fn get_or_create_where_clause(&self) -> WhereClause {
135+
fn get_or_create_where_clause(&self) -> ast::WhereClause {
131136
if self.where_clause().is_none() {
132137
let tfl = self.field_list().and_then(|fl| match fl {
133138
ast::FieldList::RecordFieldList(_) => None,
@@ -165,7 +170,7 @@ impl GenericParamsOwnerEdit for ast::Enum {
165170
}
166171
}
167172

168-
fn get_or_create_where_clause(&self) -> WhereClause {
173+
fn get_or_create_where_clause(&self) -> ast::WhereClause {
169174
if self.where_clause().is_none() {
170175
let position = if let Some(gpl) = self.generic_param_list() {
171176
Position::after(gpl.syntax())
@@ -272,6 +277,59 @@ impl ast::Use {
272277
}
273278
}
274279

280+
impl ast::Impl {
281+
pub fn get_or_create_assoc_item_list(&self) -> ast::AssocItemList {
282+
if self.assoc_item_list().is_none() {
283+
let assoc_item_list = make::assoc_item_list().clone_for_update();
284+
ted::append_child(self.syntax(), assoc_item_list.syntax());
285+
}
286+
self.assoc_item_list().unwrap()
287+
}
288+
}
289+
290+
impl ast::AssocItemList {
291+
pub fn add_item(&self, item: ast::AssocItem) {
292+
let (indent, position, whitespace) = match self.assoc_items().last() {
293+
Some(last_item) => (
294+
IndentLevel::from_node(last_item.syntax()),
295+
Position::after(last_item.syntax()),
296+
"\n\n",
297+
),
298+
None => match self.l_curly_token() {
299+
Some(l_curly) => {
300+
self.normalize_ws_between_braces();
301+
(IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly), "\n")
302+
}
303+
None => (IndentLevel::single(), Position::last_child_of(self.syntax()), "\n"),
304+
},
305+
};
306+
let elements: Vec<SyntaxElement<_>> = vec![
307+
make::tokens::whitespace(&format!("{}{}", whitespace, indent)).into(),
308+
item.syntax().clone().into(),
309+
];
310+
ted::insert_all(position, elements);
311+
}
312+
313+
fn normalize_ws_between_braces(&self) -> Option<()> {
314+
let l = self.l_curly_token()?;
315+
let r = self.r_curly_token()?;
316+
let indent = IndentLevel::from_node(self.syntax());
317+
318+
match l.next_sibling_or_token() {
319+
Some(ws) if ws.kind() == SyntaxKind::WHITESPACE => {
320+
if ws.next_sibling_or_token()?.into_token()? == r {
321+
ted::replace(ws, make::tokens::whitespace(&format!("\n{}", indent)));
322+
}
323+
}
324+
Some(ws) if ws.kind() == T!['}'] => {
325+
ted::insert(Position::after(l), make::tokens::whitespace(&format!("\n{}", indent)));
326+
}
327+
_ => (),
328+
}
329+
Some(())
330+
}
331+
}
332+
275333
#[cfg(test)]
276334
mod tests {
277335
use std::fmt;

crates/syntax/src/ast/make.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ fn ty_from_text(text: &str) -> ast::Type {
9999
}
100100

101101
pub fn assoc_item_list() -> ast::AssocItemList {
102-
ast_from_text("impl C for D {};")
102+
ast_from_text("impl C for D {}")
103103
}
104104

105105
pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl {

0 commit comments

Comments
 (0)