internal: rewrite assoc item manipulaion to use mutable trees

This commit is contained in:
Aleksey Kladov 2021-05-14 18:47:08 +03:00
parent 73123a7550
commit cea589b3b5
7 changed files with 88 additions and 167 deletions

View File

@ -9,7 +9,7 @@ incremental = false
# Disabling debug info speeds up builds a bunch, # Disabling debug info speeds up builds a bunch,
# and we don't rely on it for debugging that much. # and we don't rely on it for debugging that much.
debug = 0 debug = 1
[profile.dev.package] [profile.dev.package]
# These speed up local tests. # These speed up local tests.

View File

@ -64,7 +64,6 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -
// impl Trait for () { // impl Trait for () {
// type X = (); // type X = ();
// fn foo(&self) {}$0 // fn foo(&self) {}$0
//
// } // }
// ``` // ```
// -> // ->
@ -195,6 +194,7 @@ impl Foo for S {
fn baz(&self) { fn baz(&self) {
todo!() todo!()
} }
}"#, }"#,
); );
} }
@ -231,6 +231,7 @@ impl Foo for S {
fn foo(&self) { fn foo(&self) {
${0:todo!()} ${0:todo!()}
} }
}"#, }"#,
); );
} }

View File

@ -50,7 +50,6 @@ trait Trait {
impl Trait for () { impl Trait for () {
type X = (); type X = ();
fn foo(&self) {}$0 fn foo(&self) {}$0
} }
"#####, "#####,
r#####" r#####"

View File

@ -128,15 +128,12 @@ pub fn add_trait_assoc_items_to_impl(
sema: &hir::Semantics<ide_db::RootDatabase>, sema: &hir::Semantics<ide_db::RootDatabase>,
items: Vec<ast::AssocItem>, items: Vec<ast::AssocItem>,
trait_: hir::Trait, trait_: hir::Trait,
impl_def: ast::Impl, impl_: ast::Impl,
target_scope: hir::SemanticsScope, target_scope: hir::SemanticsScope,
) -> (ast::Impl, ast::AssocItem) { ) -> (ast::Impl, ast::AssocItem) {
let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list);
let n_existing_items = impl_item_list.assoc_items().count();
let source_scope = sema.scope_for_def(trait_); let source_scope = sema.scope_for_def(trait_);
let ast_transform = QualifyPaths::new(&target_scope, &source_scope) let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
.or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone())); .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_.clone()));
let items = items let items = items
.into_iter() .into_iter()
@ -147,13 +144,18 @@ pub fn add_trait_assoc_items_to_impl(
ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()), ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
_ => it, _ => it,
}) })
.map(|it| edit::remove_attrs_and_docs(&it)); .map(|it| edit::remove_attrs_and_docs(&it).clone_subtree().clone_for_update());
let new_impl_item_list = impl_item_list.append_items(items); let res = impl_.clone_for_update();
let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list); let assoc_item_list = res.get_or_create_assoc_item_list();
let first_new_item = let mut first_item = None;
new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap(); for item in items {
return (new_impl_def, first_new_item); if first_item.is_none() {
first_item = Some(item.clone())
}
assoc_item_list.add_item(item)
}
return (res, first_item.unwrap());
fn add_body(fn_def: ast::Fn) -> ast::Fn { fn add_body(fn_def: ast::Fn) -> ast::Fn {
match fn_def.body() { match fn_def.body() {

View File

@ -80,81 +80,6 @@ where
} }
} }
impl ast::Impl {
#[must_use]
pub fn with_assoc_item_list(&self, items: ast::AssocItemList) -> ast::Impl {
let mut to_insert: ArrayVec<SyntaxElement, 2> = ArrayVec::new();
if let Some(old_items) = self.assoc_item_list() {
let to_replace: SyntaxElement = old_items.syntax().clone().into();
to_insert.push(items.syntax().clone().into());
self.replace_children(single_node(to_replace), to_insert)
} else {
to_insert.push(make::tokens::single_space().into());
to_insert.push(items.syntax().clone().into());
self.insert_children(InsertPosition::Last, to_insert)
}
}
}
impl ast::AssocItemList {
#[must_use]
pub fn append_items(
&self,
items: impl IntoIterator<Item = ast::AssocItem>,
) -> ast::AssocItemList {
let mut res = self.clone();
if !self.syntax().text().contains_char('\n') {
res = make_multiline(res);
}
items.into_iter().for_each(|it| res = res.append_item(it));
res.fixup_trailing_whitespace().unwrap_or(res)
}
#[must_use]
pub fn append_item(&self, item: ast::AssocItem) -> ast::AssocItemList {
let (indent, position, whitespace) = match self.assoc_items().last() {
Some(it) => (
leading_indent(it.syntax()).unwrap_or_default().to_string(),
InsertPosition::After(it.syntax().clone().into()),
"\n\n",
),
None => match self.l_curly_token() {
Some(it) => (
" ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(),
InsertPosition::After(it.into()),
"\n",
),
None => return self.clone(),
},
};
let ws = tokens::WsBuilder::new(&format!("{}{}", whitespace, indent));
let to_insert: ArrayVec<SyntaxElement, 2> =
[ws.ws().into(), item.syntax().clone().into()].into();
self.insert_children(position, to_insert)
}
/// Remove extra whitespace between last item and closing curly brace.
fn fixup_trailing_whitespace(&self) -> Option<ast::AssocItemList> {
let first_token_after_items =
self.assoc_items().last()?.syntax().next_sibling_or_token()?;
let last_token_before_curly = self.r_curly_token()?.prev_sibling_or_token()?;
if last_token_before_curly != first_token_after_items {
// there is something more between last item and
// right curly than just whitespace - bail out
return None;
}
let whitespace =
last_token_before_curly.clone().into_token().and_then(ast::Whitespace::cast)?;
let text = whitespace.syntax().text();
let newline = text.rfind('\n')?;
let keep = tokens::WsBuilder::new(&text[newline..]);
Some(self.replace_children(
first_token_after_items..=last_token_before_curly,
std::iter::once(keep.ws().into()),
))
}
}
impl ast::RecordExprFieldList { impl ast::RecordExprFieldList {
#[must_use] #[must_use]
pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList { pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList {
@ -246,21 +171,6 @@ impl ast::TypeAlias {
} }
} }
impl ast::TypeParam {
#[must_use]
pub fn remove_bounds(&self) -> ast::TypeParam {
let colon = match self.colon_token() {
Some(it) => it,
None => return self.clone(),
};
let end = match self.type_bound_list() {
Some(it) => it.syntax().clone().into(),
None => colon.clone().into(),
};
self.replace_children(colon.into()..=end, iter::empty())
}
}
impl ast::Path { impl ast::Path {
#[must_use] #[must_use]
pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path { pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path {
@ -411,61 +321,6 @@ impl ast::MatchArmList {
} }
} }
impl ast::GenericParamList {
#[must_use]
pub fn append_params(
&self,
params: impl IntoIterator<Item = ast::GenericParam>,
) -> ast::GenericParamList {
let mut res = self.clone();
params.into_iter().for_each(|it| res = res.append_param(it));
res
}
#[must_use]
pub fn append_param(&self, item: ast::GenericParam) -> ast::GenericParamList {
let space = tokens::single_space();
let mut to_insert: ArrayVec<SyntaxElement, 4> = ArrayVec::new();
if self.generic_params().next().is_some() {
to_insert.push(space.into());
}
to_insert.push(item.syntax().clone().into());
macro_rules! after_l_angle {
() => {{
let anchor = match self.l_angle_token() {
Some(it) => it.into(),
None => return self.clone(),
};
InsertPosition::After(anchor)
}};
}
macro_rules! after_field {
($anchor:expr) => {
if let Some(comma) = $anchor
.syntax()
.siblings_with_tokens(Direction::Next)
.find(|it| it.kind() == T![,])
{
InsertPosition::After(comma)
} else {
to_insert.insert(0, make::token(T![,]).into());
InsertPosition::After($anchor.syntax().clone().into())
}
};
}
let position = match self.generic_params().last() {
Some(it) => after_field!(it),
None => after_l_angle!(),
};
self.insert_children(position, to_insert)
}
}
#[must_use] #[must_use]
pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
@ -516,6 +371,12 @@ impl ops::Add<u8> for IndentLevel {
} }
impl IndentLevel { impl IndentLevel {
pub fn single() -> IndentLevel {
IndentLevel(0)
}
pub fn is_zero(&self) -> bool {
self.0 == 0
}
pub fn from_element(element: &SyntaxElement) -> IndentLevel { pub fn from_element(element: &SyntaxElement) -> IndentLevel {
match element { match element {
rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it), rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it),

View File

@ -2,11 +2,16 @@
use std::iter::empty; use std::iter::empty;
use parser::T; use parser::{SyntaxKind, T};
use rowan::SyntaxElement;
use crate::{ use crate::{
algo::neighbor, algo::neighbor,
ast::{self, edit::AstNodeEdit, make, GenericParamsOwner, WhereClause}, ast::{
self,
edit::{AstNodeEdit, IndentLevel},
make, GenericParamsOwner,
},
ted::{self, Position}, ted::{self, Position},
AstNode, AstToken, Direction, AstNode, AstToken, Direction,
}; };
@ -37,7 +42,7 @@ impl GenericParamsOwnerEdit for ast::Fn {
} }
} }
fn get_or_create_where_clause(&self) -> WhereClause { fn get_or_create_where_clause(&self) -> ast::WhereClause {
if self.where_clause().is_none() { if self.where_clause().is_none() {
let position = if let Some(ty) = self.ret_type() { let position = if let Some(ty) = self.ret_type() {
Position::after(ty.syntax()) Position::after(ty.syntax())
@ -67,7 +72,7 @@ impl GenericParamsOwnerEdit for ast::Impl {
} }
} }
fn get_or_create_where_clause(&self) -> WhereClause { fn get_or_create_where_clause(&self) -> ast::WhereClause {
if self.where_clause().is_none() { if self.where_clause().is_none() {
let position = if let Some(items) = self.assoc_item_list() { let position = if let Some(items) = self.assoc_item_list() {
Position::before(items.syntax()) Position::before(items.syntax())
@ -97,7 +102,7 @@ impl GenericParamsOwnerEdit for ast::Trait {
} }
} }
fn get_or_create_where_clause(&self) -> WhereClause { fn get_or_create_where_clause(&self) -> ast::WhereClause {
if self.where_clause().is_none() { if self.where_clause().is_none() {
let position = if let Some(items) = self.assoc_item_list() { let position = if let Some(items) = self.assoc_item_list() {
Position::before(items.syntax()) Position::before(items.syntax())
@ -127,7 +132,7 @@ impl GenericParamsOwnerEdit for ast::Struct {
} }
} }
fn get_or_create_where_clause(&self) -> WhereClause { fn get_or_create_where_clause(&self) -> ast::WhereClause {
if self.where_clause().is_none() { if self.where_clause().is_none() {
let tfl = self.field_list().and_then(|fl| match fl { let tfl = self.field_list().and_then(|fl| match fl {
ast::FieldList::RecordFieldList(_) => None, ast::FieldList::RecordFieldList(_) => None,
@ -165,7 +170,7 @@ impl GenericParamsOwnerEdit for ast::Enum {
} }
} }
fn get_or_create_where_clause(&self) -> WhereClause { fn get_or_create_where_clause(&self) -> ast::WhereClause {
if self.where_clause().is_none() { if self.where_clause().is_none() {
let position = if let Some(gpl) = self.generic_param_list() { let position = if let Some(gpl) = self.generic_param_list() {
Position::after(gpl.syntax()) Position::after(gpl.syntax())
@ -272,6 +277,59 @@ impl ast::Use {
} }
} }
impl ast::Impl {
pub fn get_or_create_assoc_item_list(&self) -> ast::AssocItemList {
if self.assoc_item_list().is_none() {
let assoc_item_list = make::assoc_item_list().clone_for_update();
ted::append_child(self.syntax(), assoc_item_list.syntax());
}
self.assoc_item_list().unwrap()
}
}
impl ast::AssocItemList {
pub fn add_item(&self, item: ast::AssocItem) {
let (indent, position, whitespace) = match self.assoc_items().last() {
Some(last_item) => (
IndentLevel::from_node(last_item.syntax()),
Position::after(last_item.syntax()),
"\n\n",
),
None => match self.l_curly_token() {
Some(l_curly) => {
self.normalize_ws_between_braces();
(IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly), "\n")
}
None => (IndentLevel::single(), Position::last_child_of(self.syntax()), "\n"),
},
};
let elements: Vec<SyntaxElement<_>> = vec![
make::tokens::whitespace(&format!("{}{}", whitespace, indent)).into(),
item.syntax().clone().into(),
];
ted::insert_all(position, elements);
}
fn normalize_ws_between_braces(&self) -> Option<()> {
let l = self.l_curly_token()?;
let r = self.r_curly_token()?;
let indent = IndentLevel::from_node(self.syntax());
match l.next_sibling_or_token() {
Some(ws) if ws.kind() == SyntaxKind::WHITESPACE => {
if ws.next_sibling_or_token()?.into_token()? == r {
ted::replace(ws, make::tokens::whitespace(&format!("\n{}", indent)));
}
}
Some(ws) if ws.kind() == T!['}'] => {
ted::insert(Position::after(l), make::tokens::whitespace(&format!("\n{}", indent)));
}
_ => (),
}
Some(())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::fmt; use std::fmt;

View File

@ -99,7 +99,7 @@ fn ty_from_text(text: &str) -> ast::Type {
} }
pub fn assoc_item_list() -> ast::AssocItemList { pub fn assoc_item_list() -> ast::AssocItemList {
ast_from_text("impl C for D {};") ast_from_text("impl C for D {}")
} }
pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl { pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl {