Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/ide-assists/src/handlers/merge_imports.rs139
-rw-r--r--crates/ide-assists/src/handlers/normalize_import.rs8
-rw-r--r--crates/ide-db/src/imports/insert_use.rs8
-rw-r--r--crates/ide-db/src/imports/insert_use/tests.rs10
-rw-r--r--crates/ide-db/src/imports/merge_imports.rs664
-rw-r--r--crates/parser/src/output.rs2
-rw-r--r--crates/syntax/src/ast.rs1
-rw-r--r--crates/syntax/src/ast/edit.rs131
-rw-r--r--crates/syntax/src/ast/edit_in_place.rs308
-rw-r--r--crates/syntax/src/syntax_editor/edits.rs24
10 files changed, 626 insertions, 669 deletions
diff --git a/crates/ide-assists/src/handlers/merge_imports.rs b/crates/ide-assists/src/handlers/merge_imports.rs
index dc40a6a640..5d81f49b18 100644
--- a/crates/ide-assists/src/handlers/merge_imports.rs
+++ b/crates/ide-assists/src/handlers/merge_imports.rs
@@ -1,10 +1,12 @@
-use either::Either;
use ide_db::imports::{
insert_use::{ImportGranularity, InsertUseConfig},
merge_imports::{MergeBehavior, try_merge_imports, try_merge_trees},
};
use syntax::{
- AstNode, SyntaxElement, SyntaxNode, algo::neighbor, ast, match_ast, syntax_editor::Removable,
+ AstNode, SyntaxElement,
+ algo::neighbor,
+ ast, match_ast,
+ syntax_editor::{Removable, SyntaxEditor},
};
use crate::{
@@ -13,8 +15,6 @@ use crate::{
utils::next_prev,
};
-use Edit::*;
-
// Assist: merge_imports
//
// Merges neighbor imports with a common prefix.
@@ -28,16 +28,17 @@ use Edit::*;
// use std::{fmt::Formatter, io};
// ```
pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
- let (target, edits) = if ctx.has_empty_selection() {
+ let (target, editor) = if ctx.has_empty_selection() {
// Merge a neighbor
cov_mark::hit!(merge_with_use_item_neighbors);
let tree = ctx.find_node_at_offset::<ast::UseTree>()?.top_use_tree();
let target = tree.syntax().text_range();
let use_item = tree.syntax().parent().and_then(ast::Use::cast)?;
- let mut neighbor = next_prev().find_map(|dir| neighbor(&use_item, dir)).into_iter();
- let edits = use_item.try_merge_from(&mut neighbor, &ctx.config.insert_use);
- (target, edits?)
+ let neighbor = next_prev().find_map(|dir| neighbor(&use_item, dir))?;
+ let (editor, _) = SyntaxEditor::new(use_item.syntax().parent()?.ancestors().last()?);
+ merge_uses(use_item, vec![neighbor], &ctx.config.insert_use, &editor)?;
+ (target, editor)
} else {
// Merge selected
let selection_range = ctx.selection_trimmed();
@@ -50,104 +51,80 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> O
});
let first_selected = selected_nodes.next()?;
- let edits = match_ast! {
+ let (editor, _) = SyntaxEditor::new(parent_node.ancestors().last().unwrap());
+ match_ast! {
match first_selected {
ast::Use(use_item) => {
cov_mark::hit!(merge_with_selected_use_item_neighbors);
- use_item.try_merge_from(&mut selected_nodes.filter_map(ast::Use::cast), &ctx.config.insert_use)
+ merge_uses(
+ use_item,
+ selected_nodes.filter_map(ast::Use::cast).collect(),
+ &ctx.config.insert_use,
+ &editor,
+ )?;
},
ast::UseTree(use_tree) => {
cov_mark::hit!(merge_with_selected_use_tree_neighbors);
- use_tree.try_merge_from(&mut selected_nodes.filter_map(ast::UseTree::cast), &ctx.config.insert_use)
+ merge_use_trees(
+ use_tree,
+ selected_nodes.filter_map(ast::UseTree::cast).collect(),
+ &editor,
+ )?;
},
_ => return None,
}
- };
- (selection_range, edits?)
- };
-
- let parent_node = match ctx.covering_element() {
- SyntaxElement::Node(n) => n,
- SyntaxElement::Token(t) => t.parent()?,
+ }
+ (selection_range, editor)
};
acc.add(AssistId::refactor_rewrite("merge_imports"), "Merge imports", target, |builder| {
- let editor = builder.make_editor(&parent_node);
-
- for edit in edits {
- match edit {
- Remove(it) => {
- let node = it.as_ref();
- if let Some(left) = node.left() {
- left.remove(&editor);
- } else if let Some(right) = node.right() {
- right.remove(&editor);
- }
- }
- Replace(old, new) => {
- editor.replace(old, &new);
- }
- }
- }
builder.add_file_edits(ctx.vfs_file_id(), editor);
})
}
-trait Merge: AstNode + Clone {
- fn try_merge_from(
- self,
- items: &mut dyn Iterator<Item = Self>,
- cfg: &InsertUseConfig,
- ) -> Option<Vec<Edit>> {
- let mut edits = Vec::new();
- let mut merged = self.clone();
- for item in items {
- merged = merged.try_merge(&item, cfg)?;
- edits.push(Edit::Remove(item.into_either()));
- }
- if !edits.is_empty() {
- edits.push(Edit::replace(self, merged));
- Some(edits)
- } else {
- None
- }
+fn merge_uses(
+ first: ast::Use,
+ rest: Vec<ast::Use>,
+ cfg: &InsertUseConfig,
+ editor: &SyntaxEditor,
+) -> Option<()> {
+ if rest.is_empty() {
+ return None;
}
- fn try_merge(&self, other: &Self, cfg: &InsertUseConfig) -> Option<Self>;
- fn into_either(self) -> Either<ast::Use, ast::UseTree>;
-}
-impl Merge for ast::Use {
- fn try_merge(&self, other: &Self, cfg: &InsertUseConfig) -> Option<Self> {
- let mb = match cfg.granularity {
- ImportGranularity::One => MergeBehavior::One,
- _ => MergeBehavior::Crate,
- };
- try_merge_imports(self, other, mb)
+ let mb = match cfg.granularity {
+ ImportGranularity::One => MergeBehavior::One,
+ _ => MergeBehavior::Crate,
+ };
+ let mut merged = first.clone();
+ for item in &rest {
+ merged = try_merge_imports(editor.make(), &merged, item, mb)?;
}
- fn into_either(self) -> Either<ast::Use, ast::UseTree> {
- Either::Left(self)
+ for item in rest {
+ item.remove(editor);
}
+ editor.replace(first.syntax(), merged.syntax());
+ Some(())
}
-impl Merge for ast::UseTree {
- fn try_merge(&self, other: &Self, _: &InsertUseConfig) -> Option<Self> {
- try_merge_trees(self, other, MergeBehavior::Crate)
- }
- fn into_either(self) -> Either<ast::Use, ast::UseTree> {
- Either::Right(self)
+fn merge_use_trees(
+ first: ast::UseTree,
+ rest: Vec<ast::UseTree>,
+ editor: &SyntaxEditor,
+) -> Option<()> {
+ if rest.is_empty() {
+ return None;
}
-}
-
-#[derive(Debug)]
-enum Edit {
- Remove(Either<ast::Use, ast::UseTree>),
- Replace(SyntaxNode, SyntaxNode),
-}
-impl Edit {
- fn replace(old: impl AstNode, new: impl AstNode) -> Self {
- Edit::Replace(old.syntax().clone(), new.syntax().clone())
+ let mut merged = first.clone();
+ for item in &rest {
+ merged = try_merge_trees(editor.make(), &merged, item, MergeBehavior::Crate)?;
+ }
+ for item in rest {
+ item.remove(editor);
}
+ editor.replace(first.syntax(), merged.syntax());
+ Some(())
}
#[cfg(test)]
diff --git a/crates/ide-assists/src/handlers/normalize_import.rs b/crates/ide-assists/src/handlers/normalize_import.rs
index f97a3e583f..906f38cba4 100644
--- a/crates/ide-assists/src/handlers/normalize_import.rs
+++ b/crates/ide-assists/src/handlers/normalize_import.rs
@@ -1,5 +1,5 @@
use ide_db::imports::merge_imports::try_normalize_import;
-use syntax::{AstNode, ast};
+use syntax::{AstNode, ast, syntax_editor::SyntaxEditor};
use crate::{
AssistId,
@@ -25,11 +25,13 @@ pub(crate) fn normalize_import(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -
};
let target = use_item.syntax().text_range();
+ let (editor, _) = SyntaxEditor::new(use_item.syntax().ancestors().last().unwrap());
let normalized_use_item =
- try_normalize_import(&use_item, ctx.config.insert_use.granularity.into())?;
+ try_normalize_import(editor.make(), &use_item, ctx.config.insert_use.granularity.into())?;
+ editor.replace(use_item.syntax(), normalized_use_item.syntax());
acc.add(AssistId::refactor_rewrite("normalize_import"), "Normalize import", target, |builder| {
- builder.replace_ast(use_item, normalized_use_item);
+ builder.add_file_edits(ctx.vfs_file_id(), editor);
})
}
diff --git a/crates/ide-db/src/imports/insert_use.rs b/crates/ide-db/src/imports/insert_use.rs
index c3949f8713..27e3ed6bdb 100644
--- a/crates/ide-db/src/imports/insert_use.rs
+++ b/crates/ide-db/src/imports/insert_use.rs
@@ -17,7 +17,7 @@ use crate::{
RootDatabase,
imports::merge_imports::{
MergeBehavior, NormalizationStyle, common_prefix, eq_attrs, eq_visibility,
- try_merge_imports, use_tree_cmp,
+ try_merge_imports, use_tree_cmp, wrap_in_tree_list,
},
};
@@ -251,7 +251,7 @@ fn insert_use_with_alias_option_with_editor(
let mut use_tree = make.use_tree(path, None, alias, false);
if mb == Some(MergeBehavior::One)
&& use_tree.path().is_some()
- && let Some(wrapped) = use_tree.wrap_in_tree_list_with_editor()
+ && let Some(wrapped) = wrap_in_tree_list(&use_tree, make)
{
use_tree = wrapped;
}
@@ -263,7 +263,9 @@ fn insert_use_with_alias_option_with_editor(
for existing_use in
scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
{
- if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
+ if let Some(merged) =
+ try_merge_imports(syntax_editor.make(), &existing_use, &use_item, mb)
+ {
syntax_editor.replace(existing_use.syntax(), merged.syntax());
return;
}
diff --git a/crates/ide-db/src/imports/insert_use/tests.rs b/crates/ide-db/src/imports/insert_use/tests.rs
index 4fa05c4603..34ff7c8d5b 100644
--- a/crates/ide-db/src/imports/insert_use/tests.rs
+++ b/crates/ide-db/src/imports/insert_use/tests.rs
@@ -1,4 +1,5 @@
use stdx::trim_indent;
+use syntax::ast::syntax_factory::SyntaxFactory;
use test_fixture::WithFixture;
use test_utils::{CURSOR_MARKER, assert_eq_text};
@@ -1430,7 +1431,8 @@ fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior
.find_map(ast::Use::cast)
.unwrap();
- let result = try_merge_imports(&use0, &use1, mb);
+ let make = SyntaxFactory::without_mappings();
+ let result = try_merge_imports(&make, &use0, &use1, mb);
assert_eq!(result.map(|u| u.to_string()), None);
}
@@ -1495,7 +1497,8 @@ fn check_merge(ra_fixture0: &str, ra_fixture1: &str, last: &str, mb: MergeBehavi
.find_map(ast::Use::cast)
.unwrap();
- let result = try_merge_imports(&use0, &use1, mb);
+ let make = SyntaxFactory::without_mappings();
+ let result = try_merge_imports(&make, &use0, &use1, mb);
assert_eq!(result.map(|u| u.to_string().trim().to_owned()), Some(last.trim().to_owned()));
}
@@ -1525,7 +1528,8 @@ fn merge_gated_imports_with_different_values() {
.find_map(ast::Use::cast)
.unwrap();
- let result = try_merge_imports(&use0, &use1, MergeBehavior::Crate);
+ let make = SyntaxFactory::without_mappings();
+ let result = try_merge_imports(&make, &use0, &use1, MergeBehavior::Crate);
assert_eq!(result, None);
}
diff --git a/crates/ide-db/src/imports/merge_imports.rs b/crates/ide-db/src/imports/merge_imports.rs
index bbd351a4cc..9b68c27ae3 100644
--- a/crates/ide-db/src/imports/merge_imports.rs
+++ b/crates/ide-db/src/imports/merge_imports.rs
@@ -4,12 +4,12 @@ use std::cmp::Ordering;
use itertools::{EitherOrBoth, Itertools};
use parser::T;
use syntax::{
- Direction, SyntaxElement, ToSmolStr, algo,
+ ToSmolStr,
ast::{
- self, AstNode, HasAttrs, HasName, HasVisibility, PathSegmentKind, edit_in_place::Removable,
- make,
+ self, AstNode, HasAttrs, HasName, HasVisibility, PathSegmentKind,
+ syntax_factory::SyntaxFactory,
},
- ted::{self, Position},
+ syntax_editor::{Position, SyntaxEditor},
};
use crate::syntax_helpers::node_ext::vis_eq;
@@ -39,8 +39,8 @@ impl MergeBehavior {
}
/// Merge `rhs` into `lhs` keeping both intact.
-/// Returned AST is mutable.
pub fn try_merge_imports(
+ make: &SyntaxFactory,
lhs: &ast::Use,
rhs: &ast::Use,
merge_behavior: MergeBehavior,
@@ -53,39 +53,39 @@ pub fn try_merge_imports(
return None;
}
- let lhs = lhs.clone_subtree().clone_for_update();
- let rhs = rhs.clone_subtree().clone_for_update();
let lhs_tree = lhs.use_tree()?;
let rhs_tree = rhs.use_tree()?;
- try_merge_trees_mut(&lhs_tree, &rhs_tree, merge_behavior)?;
+ let merged_tree = try_merge_trees_with_factory(lhs_tree, rhs_tree, merge_behavior, make)?;
// Ignore `None` result because normalization should not affect the merge result.
- try_normalize_use_tree_mut(&lhs_tree, merge_behavior.into());
+ let use_tree = try_normalize_use_tree(merged_tree.clone(), merge_behavior.into(), make)
+ .unwrap_or(merged_tree);
- Some(lhs)
+ make_use_with_tree(lhs, use_tree)
}
/// Merge `rhs` into `lhs` keeping both intact.
-/// Returned AST is mutable.
pub fn try_merge_trees(
+ make: &SyntaxFactory,
lhs: &ast::UseTree,
rhs: &ast::UseTree,
merge: MergeBehavior,
) -> Option<ast::UseTree> {
- let lhs = lhs.clone_subtree().clone_for_update();
- let rhs = rhs.clone_subtree().clone_for_update();
- try_merge_trees_mut(&lhs, &rhs, merge)?;
+ let merged = try_merge_trees_with_factory(lhs.clone(), rhs.clone(), merge, make)?;
// Ignore `None` result because normalization should not affect the merge result.
- try_normalize_use_tree_mut(&lhs, merge.into());
-
- Some(lhs)
+ Some(try_normalize_use_tree(merged.clone(), merge.into(), make).unwrap_or(merged))
}
-fn try_merge_trees_mut(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior) -> Option<()> {
+fn try_merge_trees_with_factory(
+ mut lhs: ast::UseTree,
+ mut rhs: ast::UseTree,
+ merge: MergeBehavior,
+ make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
if merge == MergeBehavior::One {
- lhs.wrap_in_tree_list();
- rhs.wrap_in_tree_list();
+ lhs = wrap_in_tree_list(&lhs, make).unwrap_or(lhs);
+ rhs = wrap_in_tree_list(&rhs, make).unwrap_or(rhs);
} else {
let lhs_path = lhs.path()?;
let rhs_path = rhs.path()?;
@@ -100,46 +100,53 @@ fn try_merge_trees_mut(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehav
// and we can safely return here
let lhs_name = lhs.rename().and_then(|lhs_name| lhs_name.name());
let rhs_name = rhs.rename().and_then(|rhs_name| rhs_name.name());
- if lhs_name != rhs_name {
+ if lhs_name.as_ref().map(|name| name.text())
+ != rhs_name.as_ref().map(|name| name.text())
+ {
return None;
}
- ted::replace(lhs.syntax(), rhs.syntax());
- // we can safely return here, in this case `recursive_merge` doesn't do anything
- return Some(());
+ return Some(rhs);
} else {
- lhs.split_prefix(&lhs_prefix);
- rhs.split_prefix(&rhs_prefix);
+ lhs = split_prefix(&lhs, &lhs_prefix, make)?;
+ rhs = split_prefix(&rhs, &rhs_prefix, make)?;
}
}
- recursive_merge(lhs, rhs, merge)
+ recursive_merge(lhs, rhs, merge, make)
}
/// Recursively merges rhs to lhs
#[must_use]
-fn recursive_merge(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior) -> Option<()> {
+fn recursive_merge(
+ lhs: ast::UseTree,
+ rhs: ast::UseTree,
+ merge: MergeBehavior,
+ make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
let mut use_trees: Vec<ast::UseTree> = lhs
- .use_tree_list()
- .into_iter()
- .flat_map(|list| list.use_trees())
- // We use Option here to early return from this function(this is not the
- // same as a `filter` op).
+ .use_tree_list()?
+ .use_trees()
+ // We use Option here to early return from this function. This is not the
+ // same as a `filter` op.
.map(|tree| merge.is_tree_allowed(&tree).then_some(tree))
.collect::<Option<_>>()?;
+
// Sorts the use trees similar to rustfmt's algorithm for ordering imports
// (see `use_tree_cmp` doc).
use_trees.sort_unstable_by(use_tree_cmp);
- for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
+
+ for rhs_t in rhs.use_tree_list()?.use_trees() {
if !merge.is_tree_allowed(&rhs_t) {
return None;
}
match use_trees.binary_search_by(|lhs_t| use_tree_cmp_bin_search(lhs_t, &rhs_t)) {
Ok(idx) => {
- let lhs_t = &mut use_trees[idx];
+ let mut lhs_t = use_trees[idx].clone();
let lhs_path = lhs_t.path()?;
let rhs_path = rhs_t.path()?;
let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
+
if lhs_prefix == lhs_path && rhs_prefix == rhs_path {
let tree_is_self = |tree: &ast::UseTree| {
tree.path().as_ref().map(path_is_self).unwrap_or(false)
@@ -157,20 +164,20 @@ fn recursive_merge(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior)
};
if lhs_t.rename().and_then(|x| x.underscore_token()).is_some() {
- ted::replace(lhs_t.syntax(), rhs_t.syntax());
- *lhs_t = rhs_t;
+ use_trees[idx] = rhs_t;
continue;
}
- match (tree_contains_self(lhs_t), tree_contains_self(&rhs_t)) {
+ match (tree_contains_self(&lhs_t), tree_contains_self(&rhs_t)) {
(Some(true), None) => {
- remove_subtree_if_only_self(lhs_t);
+ lhs_t = remove_subtree_if_only_self(lhs_t, make)?;
+ use_trees[idx] = lhs_t;
continue;
}
(None, Some(true)) => {
- ted::replace(lhs_t.syntax(), rhs_t.syntax());
- *lhs_t = rhs_t;
- remove_subtree_if_only_self(lhs_t);
+ lhs_t = rhs_t;
+ lhs_t = remove_subtree_if_only_self(lhs_t, make)?;
+ use_trees[idx] = lhs_t;
continue;
}
_ => (),
@@ -180,9 +187,11 @@ fn recursive_merge(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior)
continue;
}
}
- lhs_t.split_prefix(&lhs_prefix);
- rhs_t.split_prefix(&rhs_prefix);
- recursive_merge(lhs_t, &rhs_t, merge)?;
+
+ lhs_t = split_prefix(&lhs_t, &lhs_prefix, make)?;
+ let rhs_t = split_prefix(&rhs_t, &rhs_prefix, make)?;
+ lhs_t = recursive_merge(lhs_t, rhs_t, merge, make)?;
+ use_trees[idx] = lhs_t;
}
Err(_)
if merge == MergeBehavior::Module
@@ -192,15 +201,12 @@ fn recursive_merge(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior)
return None;
}
Err(insert_idx) => {
- use_trees.insert(insert_idx, rhs_t.clone());
- // We simply add the use tree to the end of tree list. Ordering of use trees
- // and imports is done by the `try_normalize_*` functions. The sorted `use_trees`
- // vec is only used for binary search.
- lhs.get_or_create_use_tree_list().add_use_tree(rhs_t);
+ use_trees.insert(insert_idx, rhs_t);
}
}
}
- Some(())
+
+ with_use_tree_list(&lhs, use_trees, make)
}
/// Style to follow when normalizing a use tree.
@@ -250,241 +256,216 @@ impl From<MergeBehavior> for NormalizationStyle {
/// - `foo::{bar::Qux, bar::{self}}` -> `{foo::bar::{self, Qux}}`
/// - `foo::bar::{self}` -> `{foo::bar}`
/// - `foo::bar` -> `{foo::bar}`
-pub fn try_normalize_import(use_item: &ast::Use, style: NormalizationStyle) -> Option<ast::Use> {
- let use_item = use_item.clone_subtree().clone_for_update();
- try_normalize_use_tree_mut(&use_item.use_tree()?, style)?;
- Some(use_item)
+pub fn try_normalize_import(
+ make: &SyntaxFactory,
+ use_item: &ast::Use,
+ style: NormalizationStyle,
+) -> Option<ast::Use> {
+ let use_tree = try_normalize_use_tree(use_item.use_tree()?, style, make)?;
+
+ make_use_with_tree(use_item, use_tree)
}
-fn try_normalize_use_tree_mut(use_tree: &ast::UseTree, style: NormalizationStyle) -> Option<()> {
+fn try_normalize_use_tree(
+ use_tree: ast::UseTree,
+ style: NormalizationStyle,
+ make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
if style == NormalizationStyle::One {
+ let mut use_tree = use_tree;
let mut modified = false;
- modified |= use_tree.wrap_in_tree_list().is_some();
- modified |= recursive_normalize(use_tree, style).is_some();
- if !modified {
- // Either the use tree was already normalized or its semantically empty.
- return None;
+ if let Some(wrapped) = wrap_in_tree_list(&use_tree, make) {
+ use_tree = wrapped;
+ modified = true;
}
- } else {
- recursive_normalize(use_tree, NormalizationStyle::Default)?;
+ if let Some(normalized) = recursive_normalize(use_tree.clone(), style, make) {
+ use_tree = normalized;
+ modified = true;
+ }
+ return modified.then_some(use_tree);
}
- Some(())
+
+ recursive_normalize(use_tree, NormalizationStyle::Default, make)
}
/// Recursively normalizes a use tree and its subtrees (if any).
-fn recursive_normalize(use_tree: &ast::UseTree, style: NormalizationStyle) -> Option<()> {
+fn recursive_normalize(
+ use_tree: ast::UseTree,
+ style: NormalizationStyle,
+ make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
let use_tree_list = use_tree.use_tree_list()?;
- let merge_subtree_into_parent_tree = |single_subtree: &ast::UseTree| {
- let subtree_is_only_self = single_subtree.path().as_ref().is_some_and(path_is_self);
-
- let merged_path = match (use_tree.path(), single_subtree.path()) {
- // If the subtree is `{self}` then we cannot merge: `use
- // foo::bar::{self}` is not equivalent to `use foo::bar`. See
- // https://github.com/rust-lang/rust-analyzer/pull/17140#issuecomment-2079189725.
- _ if subtree_is_only_self => None,
-
- (None, None) => None,
- (Some(outer), None) => Some(outer),
- (None, Some(inner)) => Some(inner),
- (Some(outer), Some(inner)) => Some(make::path_concat(outer, inner).clone_for_update()),
- };
-
- if merged_path.is_some()
- || single_subtree.use_tree_list().is_some()
- || single_subtree.star_token().is_some()
+ let mut subtrees = use_tree_list.use_trees().collect::<Vec<_>>();
+ if subtrees.len() == 1 {
+ if style == NormalizationStyle::One {
+ let subtree = subtrees.pop()?;
+ let normalized = recursive_normalize(subtree, NormalizationStyle::Default, make)?;
+ return with_use_tree_list(&use_tree, vec![normalized], make);
+ }
+
+ let merged = merge_single_subtree_into_parent_tree(use_tree, make)?;
+ return Some(recursive_normalize(merged.clone(), style, make).unwrap_or(merged));
+ }
+
+ let mut modified = false;
+ let mut new_use_tree_list = Vec::new();
+ for subtree in subtrees {
+ if one_style_tree_list(&subtree).is_some() {
+ let mut elements = Vec::new();
+ flatten_one_style_tree(subtree, &mut elements, &mut modified, make);
+ new_use_tree_list.extend(elements);
+ modified = true;
+ } else if let Some(normalized) =
+ recursive_normalize(subtree.clone(), NormalizationStyle::Default, make)
{
- ted::remove_all_iter(use_tree.syntax().children_with_tokens());
- if let Some(path) = merged_path {
- ted::insert_raw(Position::first_child_of(use_tree.syntax()), path.syntax());
- if single_subtree.use_tree_list().is_some() || single_subtree.star_token().is_some()
+ new_use_tree_list.push(normalized);
+ modified = true;
+ } else {
+ new_use_tree_list.push(subtree);
+ }
+ }
+
+ let mut use_tree =
+ if modified { with_use_tree_list(&use_tree, new_use_tree_list, make)? } else { use_tree };
+
+ let mut use_tree_list = use_tree.use_tree_list()?.use_trees().collect::<Vec<_>>();
+ let mut anchor_idx = 0;
+ let mut merged_any = false;
+ while anchor_idx < use_tree_list.len() {
+ let mut candidate_idx = anchor_idx + 1;
+ while candidate_idx < use_tree_list.len() {
+ if let Some(mut merged) = try_merge_trees_with_factory(
+ use_tree_list[anchor_idx].clone(),
+ use_tree_list[candidate_idx].clone(),
+ MergeBehavior::Crate,
+ make,
+ ) {
+ if let Some(normalized) =
+ recursive_normalize(merged.clone(), NormalizationStyle::Default, make)
{
- ted::insert_raw(
- Position::last_child_of(use_tree.syntax()),
- make::token(T![::]),
- );
+ merged = normalized;
}
+
+ use_tree_list[anchor_idx] = merged;
+ use_tree_list.remove(candidate_idx);
+ merged_any = true;
+ } else {
+ candidate_idx += 1;
}
- if let Some(inner_use_tree_list) = single_subtree.use_tree_list() {
- ted::insert_raw(
- Position::last_child_of(use_tree.syntax()),
- inner_use_tree_list.syntax(),
- );
- } else if single_subtree.star_token().is_some() {
- ted::insert_raw(Position::last_child_of(use_tree.syntax()), make::token(T![*]));
- } else if let Some(rename) = single_subtree.rename() {
- ted::insert_raw(
- Position::last_child_of(use_tree.syntax()),
- make::tokens::single_space(),
- );
- ted::insert_raw(Position::last_child_of(use_tree.syntax()), rename.syntax());
- }
- Some(())
- } else {
- // Bail on semantically empty use trees.
- None
}
- };
- let one_style_tree_list = |subtree: &ast::UseTree| match (
- subtree.path().is_none() && subtree.star_token().is_none() && subtree.rename().is_none(),
- subtree.use_tree_list(),
- ) {
- (true, tree_list) => tree_list,
- _ => None,
- };
- let add_element_to_list = |elem: SyntaxElement, elements: &mut Vec<SyntaxElement>| {
- if !elements.is_empty() {
- elements.push(make::token(T![,]).into());
- elements.push(make::tokens::single_space().into());
+
+ anchor_idx += 1;
+ }
+ if merged_any {
+ use_tree = with_use_tree_list(&use_tree, use_tree_list, make)?;
+ modified = true;
+ }
+
+ if style != NormalizationStyle::One {
+ let subtrees = use_tree.use_tree_list()?.use_trees().collect::<Vec<_>>();
+ if subtrees.len() == 1
+ && let Some(merged) = merge_single_subtree_into_parent_tree(use_tree.clone(), make)
+ {
+ use_tree = merged;
+ modified = true;
}
- elements.push(elem);
- };
- if let Some((single_subtree,)) = use_tree_list.use_trees().collect_tuple() {
- if style == NormalizationStyle::One {
- // Only normalize descendant subtrees if the normalization style is "one".
- recursive_normalize(&single_subtree, NormalizationStyle::Default)?;
- } else {
- // Otherwise, merge the single subtree into it's parent (if possible)
- // and then normalize the result.
- merge_subtree_into_parent_tree(&single_subtree)?;
- recursive_normalize(use_tree, style);
+ }
+
+ if let Some(list) = use_tree.use_tree_list() {
+ let mut use_tree_list = list.use_trees().collect::<Vec<_>>();
+ if use_tree_list
+ .windows(2)
+ .any(|trees| use_tree_cmp_bin_search(&trees[0], &trees[1]).is_gt())
+ {
+ use_tree_list.sort_unstable_by(use_tree_cmp_bin_search);
+ use_tree = with_use_tree_list(&use_tree, use_tree_list, make)?;
+ modified = true;
}
- } else {
- // Tracks whether any changes have been made to the use tree.
- let mut modified = false;
+ }
- // Recursively un-nests (if necessary) and then normalizes each subtree in the tree list.
- for subtree in use_tree_list.use_trees() {
- if let Some(one_tree_list) = one_style_tree_list(&subtree) {
- let mut elements = Vec::new();
- let mut one_tree_list_iter = one_tree_list.use_trees();
- let mut prev_skipped = Vec::new();
- loop {
- let mut prev_skipped_iter = prev_skipped.into_iter();
- let mut curr_skipped = Vec::new();
-
- while let Some(sub_sub_tree) =
- one_tree_list_iter.next().or(prev_skipped_iter.next())
- {
- if let Some(sub_one_tree_list) = one_style_tree_list(&sub_sub_tree) {
- curr_skipped.extend(sub_one_tree_list.use_trees());
- } else {
- modified |=
- recursive_normalize(&sub_sub_tree, NormalizationStyle::Default)
- .is_some();
- add_element_to_list(
- sub_sub_tree.syntax().clone().into(),
- &mut elements,
- );
- }
- }
+ modified.then_some(use_tree)
+}
- if curr_skipped.is_empty() {
- // Un-nesting is complete.
- break;
- }
- prev_skipped = curr_skipped;
- }
+fn flatten_one_style_tree(
+ subtree: ast::UseTree,
+ elements: &mut Vec<ast::UseTree>,
+ modified: &mut bool,
+ make: &SyntaxFactory,
+) {
+ let Some(one_tree_list) = one_style_tree_list(&subtree) else { return };
+ let mut one_tree_list_iter = one_tree_list.use_trees();
+ let mut prev_skipped = Vec::new();
+ loop {
+ let mut prev_skipped_iter = prev_skipped.into_iter();
+ let mut curr_skipped = Vec::new();
- // Either removes the subtree (if its semantically empty) or replaces it with
- // the un-nested elements.
- if elements.is_empty() {
- subtree.remove();
- } else {
- ted::replace_with_many(subtree.syntax(), elements);
- }
- // Silence unused assignment warning on `modified`.
- let _ = modified;
- modified = true;
+ while let Some(sub_sub_tree) =
+ one_tree_list_iter.next().or_else(|| prev_skipped_iter.next())
+ {
+ if let Some(sub_one_tree_list) = one_style_tree_list(&sub_sub_tree) {
+ curr_skipped.extend(sub_one_tree_list.use_trees());
+ } else if let Some(normalized) =
+ recursive_normalize(sub_sub_tree.clone(), NormalizationStyle::Default, make)
+ {
+ *modified = true;
+ elements.push(normalized);
} else {
- modified |= recursive_normalize(&subtree, NormalizationStyle::Default).is_some();
+ elements.push(sub_sub_tree);
}
}
- // Merge all merge-able subtrees.
- let mut tree_list_iter = use_tree_list.use_trees();
- let mut anchor = tree_list_iter.next()?;
- let mut prev_skipped = Vec::new();
- loop {
- let mut has_merged = false;
- let mut prev_skipped_iter = prev_skipped.into_iter();
- let mut next_anchor = None;
- let mut curr_skipped = Vec::new();
-
- while let Some(candidate) = tree_list_iter.next().or(prev_skipped_iter.next()) {
- let result = try_merge_trees_mut(&anchor, &candidate, MergeBehavior::Crate);
- if result.is_some() {
- // Remove merged subtree.
- candidate.remove();
- has_merged = true;
- } else if next_anchor.is_none() {
- next_anchor = Some(candidate);
- } else {
- curr_skipped.push(candidate);
- }
- }
-
- if has_merged {
- // Normalize the merge result.
- recursive_normalize(&anchor, NormalizationStyle::Default);
- modified = true;
- }
+ if curr_skipped.is_empty() {
+ break;
+ }
+ prev_skipped = curr_skipped;
+ }
+}
- let (Some(next_anchor), true) = (next_anchor, !curr_skipped.is_empty()) else {
- // Merging is complete.
- break;
- };
+fn merge_single_subtree_into_parent_tree(
+ use_tree: ast::UseTree,
+ make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
+ let single_subtree = get_single_subtree(&use_tree)?;
+ let subtree_is_only_self = single_subtree.path().as_ref().is_some_and(path_is_self);
+
+ let merged_path = match (use_tree.path(), single_subtree.path()) {
+ _ if subtree_is_only_self => None,
+ (None, None) => None,
+ (Some(outer), None) => Some(outer),
+ (None, Some(inner)) => Some(inner),
+ (Some(outer), Some(inner)) => Some(make.path_concat(outer, inner)),
+ };
- // Try to merge the remaining subtrees in the next iteration.
- anchor = next_anchor;
- prev_skipped = curr_skipped;
- }
+ let list = single_subtree.use_tree_list();
+ let list_is_none = list.is_none();
+ let star = single_subtree.star_token().is_some();
+ if merged_path.is_some() || list.is_some() || star {
+ let rename = (!star && list_is_none).then(|| single_subtree.rename()).flatten();
+ make_use_tree_from_parts(make, merged_path, list, rename, star)
+ } else {
+ None
+ }
+}
- let mut subtrees: Vec<_> = use_tree_list.use_trees().collect();
- // Merge the remaining subtree into its parent, if its only one and
- // the normalization style is not "one".
- if subtrees.len() == 1 && style != NormalizationStyle::One {
- modified |= merge_subtree_into_parent_tree(&subtrees[0]).is_some();
- }
- // Order the remaining subtrees (if necessary).
- if subtrees.len() > 1 {
- let mut did_sort = false;
- subtrees.sort_unstable_by(|a, b| {
- let order = use_tree_cmp_bin_search(a, b);
- if !did_sort && order == Ordering::Less {
- did_sort = true;
- }
- order
- });
- if did_sort {
- let start = use_tree_list
- .l_curly_token()
- .and_then(|l_curly| algo::non_trivia_sibling(l_curly.into(), Direction::Next))
- .filter(|it| it.kind() != T!['}']);
- let end = use_tree_list
- .r_curly_token()
- .and_then(|r_curly| algo::non_trivia_sibling(r_curly.into(), Direction::Prev))
- .filter(|it| it.kind() != T!['{']);
- if let Some((start, end)) = start.zip(end) {
- // Attempt to insert elements while preserving preceding and trailing trivia.
- let mut elements = Vec::new();
- for subtree in subtrees {
- add_element_to_list(subtree.syntax().clone().into(), &mut elements);
- }
- ted::replace_all(start..=end, elements);
- } else {
- let new_use_tree_list = make::use_tree_list(subtrees).clone_for_update();
- ted::replace(use_tree_list.syntax(), new_use_tree_list.syntax());
- }
- modified = true;
- }
- }
+fn one_style_tree_list(subtree: &ast::UseTree) -> Option<ast::UseTreeList> {
+ (subtree.path().is_none() && subtree.star_token().is_none() && subtree.rename().is_none())
+ .then(|| subtree.use_tree_list())
+ .flatten()
+}
- if !modified {
- // Either the use tree was already normalized or its semantically empty.
- return None;
+fn remove_subtree_if_only_self(
+ use_tree: ast::UseTree,
+ make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
+ let Some(single_subtree) = get_single_subtree(&use_tree) else {
+ return Some(use_tree);
+ };
+ match (use_tree.path(), single_subtree.path()) {
+ (Some(path), Some(inner)) if path_is_self(&inner) => {
+ Some(make.use_tree(path, None, use_tree.rename(), false))
}
+ _ => Some(use_tree),
}
- Some(())
}
/// Traverses both paths until they differ, returning the common prefix of both.
@@ -513,10 +494,9 @@ pub fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast
fn use_tree_cmp_bin_search(lhs: &ast::UseTree, rhs: &ast::UseTree) -> Ordering {
let lhs_is_simple_path = lhs.is_simple_path() && lhs.rename().is_none();
let rhs_is_simple_path = rhs.is_simple_path() && rhs.rename().is_none();
- match (
- lhs.path().as_ref().and_then(ast::Path::first_segment),
- rhs.path().as_ref().and_then(ast::Path::first_segment),
- ) {
+ let lhs_segment = lhs.path().and_then(|path| path.first_segment());
+ let rhs_segment = rhs.path().and_then(|path| path.first_segment());
+ match (lhs_segment, rhs_segment) {
(None, None) => match (lhs_is_simple_path, rhs_is_simple_path) {
(true, true) => Ordering::Equal,
(true, false) => Ordering::Less,
@@ -701,14 +681,164 @@ fn get_single_subtree(use_tree: &ast::UseTree) -> Option<ast::UseTree> {
.map(|(single_subtree,)| single_subtree)
}
-fn remove_subtree_if_only_self(use_tree: &ast::UseTree) {
- let Some(single_subtree) = get_single_subtree(use_tree) else { return };
- match (use_tree.path(), single_subtree.path()) {
- (Some(_), Some(inner)) if path_is_self(&inner) => {
- ted::remove_all_iter(single_subtree.syntax().children_with_tokens());
- }
- _ => (),
+fn make_use_with_tree(original: &ast::Use, use_tree: ast::UseTree) -> Option<ast::Use> {
+ let (editor, use_item) = SyntaxEditor::with_ast_node(original);
+ let original_tree = use_item.use_tree()?;
+ editor.replace(original_tree.syntax(), use_tree.syntax());
+ let edit = editor.finish();
+ ast::Use::cast(edit.new_root().clone())
+}
+
+fn make_use_tree_list(
+ make: &SyntaxFactory,
+ use_trees: Vec<ast::UseTree>,
+ style_source: Option<&ast::UseTreeList>,
+) -> Option<ast::UseTreeList> {
+ let use_tree_list = make.use_tree_list(use_trees);
+ let Some(style_source) = style_source else {
+ return Some(use_tree_list);
+ };
+
+ let source_l_curly = style_source.l_curly_token()?;
+ let source_r_curly = style_source.r_curly_token()?;
+
+ let leading_ws = source_l_curly.next_token().filter(|token| token.kind().is_trivia());
+
+ let trailing_ws = source_r_curly.prev_token().filter(|token| token.kind().is_trivia());
+
+ let source_trailing_token = trailing_ws
+ .as_ref()
+ .and_then(|token| token.prev_token())
+ .or_else(|| source_r_curly.prev_token());
+
+ let source_has_trailing_comma =
+ source_trailing_token.is_some_and(|token| token.kind() == T![,]);
+
+ let (editor, use_tree_list) = SyntaxEditor::with_ast_node(&use_tree_list);
+ let make = editor.make();
+
+ if let Some(leading_ws) = leading_ws {
+ editor.insert(
+ Position::after(use_tree_list.l_curly_token()?),
+ make.whitespace(leading_ws.text()),
+ );
+ }
+
+ let r_curly = use_tree_list.r_curly_token()?;
+
+ let generated_has_trailing_comma = r_curly
+ .prev_token()
+ .and_then(|token| if token.kind().is_trivia() { token.prev_token() } else { Some(token) })
+ .is_some_and(|token| token.kind() == T![,]);
+
+ let mut trailing = Vec::new();
+
+ if source_has_trailing_comma
+ && !generated_has_trailing_comma
+ && use_tree_list.use_trees().next().is_some()
+ {
+ trailing.push(make.token(T![,]).into());
+ }
+
+ if let Some(trailing_ws) = trailing_ws {
+ trailing.push(make.whitespace(trailing_ws.text()).into());
+ }
+
+ if !trailing.is_empty() {
+ editor.insert_all(Position::before(r_curly), trailing);
+ }
+
+ let edit = editor.finish();
+ ast::UseTreeList::cast(edit.new_root().clone())
+}
+
+fn make_use_tree_from_list(make: &SyntaxFactory, list: ast::UseTreeList) -> Option<ast::UseTree> {
+ let placeholder = make.use_tree_glob();
+ let (editor, use_tree) = SyntaxEditor::with_ast_node(&placeholder);
+ let first_child = use_tree.syntax().first_child_or_token()?;
+ let last_child = use_tree.syntax().last_child_or_token()?;
+ editor.replace_all(first_child..=last_child, vec![list.syntax().clone().into()]);
+ let edit = editor.finish();
+ ast::UseTree::cast(edit.new_root().clone())
+}
+
+fn make_use_tree_from_parts(
+ make: &SyntaxFactory,
+ path: Option<ast::Path>,
+ list: Option<ast::UseTreeList>,
+ rename: Option<ast::Rename>,
+ star: bool,
+) -> Option<ast::UseTree> {
+ match (path, list, star) {
+ (Some(path), list, star) => Some(make.use_tree(path, list, rename, star)),
+ (None, Some(list), false) if rename.is_none() => make_use_tree_from_list(make, list),
+ (None, None, true) if rename.is_none() => Some(make.use_tree_glob()),
+ (None, None, false) if rename.is_none() => None,
+ _ => None,
+ }
+}
+
+fn with_use_tree_list(
+ use_tree: &ast::UseTree,
+ use_trees: Vec<ast::UseTree>,
+ make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
+ let list = make_use_tree_list(make, use_trees, use_tree.use_tree_list().as_ref())?;
+ make_use_tree_from_parts(
+ make,
+ use_tree.path(),
+ Some(list),
+ use_tree.rename(),
+ use_tree.star_token().is_some(),
+ )
+}
+
+pub(crate) fn wrap_in_tree_list(
+ use_tree: &ast::UseTree,
+ make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
+ if use_tree.path().is_none()
+ && use_tree.use_tree_list().is_some()
+ && use_tree.rename().is_none()
+ && use_tree.star_token().is_none()
+ {
+ return None;
}
+
+ let list = make_use_tree_list(make, vec![use_tree.clone()], None)?;
+ make_use_tree_from_list(make, list)
+}
+
+fn split_prefix(
+ use_tree: &ast::UseTree,
+ prefix: &ast::Path,
+ make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
+ let path = use_tree.path()?;
+ if path == *prefix && use_tree.use_tree_list().is_some() {
+ return Some(use_tree.clone());
+ }
+
+ let suffix = if path == *prefix {
+ if use_tree.star_token().is_some() {
+ make.use_tree_glob()
+ } else {
+ let self_path = make.path_unqualified(make.path_segment_self());
+ make.use_tree(self_path, None, use_tree.rename(), false)
+ }
+ } else {
+ let suffix_segments = path.segments().skip(prefix.segments().count());
+ let suffix_path = make.path_from_segments(suffix_segments, false);
+ make.use_tree(
+ suffix_path,
+ use_tree.use_tree_list(),
+ use_tree.rename(),
+ use_tree.star_token().is_some(),
+ )
+ };
+
+ let list = make_use_tree_list(make, vec![suffix], None)?;
+ Some(make.use_tree(prefix.clone(), Some(list), None, false))
}
// Taken from rustfmt
diff --git a/crates/parser/src/output.rs b/crates/parser/src/output.rs
index 2f09b11218..ce64db8ada 100644
--- a/crates/parser/src/output.rs
+++ b/crates/parser/src/output.rs
@@ -18,7 +18,7 @@ pub struct Output {
///
/// ```text
/// |16 bit kind|8 bit n_input_tokens|4 bit tag|4 bit leftover|
- /// ``````
+ /// ```
event: Vec<u32>,
error: Vec<String>,
}
diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs
index dc592a4372..d8c7e15830 100644
--- a/crates/syntax/src/ast.rs
+++ b/crates/syntax/src/ast.rs
@@ -1,7 +1,6 @@
//! Abstract Syntax Tree, layered on top of untyped `SyntaxNode`s
pub mod edit;
-pub mod edit_in_place;
mod expr_ext;
mod generated;
pub mod make;
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 2e3a4016ee..eaa36903bf 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -13,7 +13,7 @@ use crate::{
SyntaxKind::{ATTR, COMMENT, WHITESPACE},
SyntaxNode, SyntaxToken,
ast::{self, AstNode, HasName, make},
- syntax_editor::{Position, SyntaxEditor, SyntaxMappingBuilder},
+ syntax_editor::{Position, Removable, SyntaxEditor, SyntaxMappingBuilder},
};
use super::syntax_factory::SyntaxFactory;
@@ -263,6 +263,109 @@ pub fn indent(node: &SyntaxNode, level: IndentLevel) -> SyntaxNode {
level.clone_increase_indent(node)
}
+impl ast::GenericParamList {
+ /// Constructs a matching [`ast::GenericArgList`]
+ pub fn to_generic_args(&self, make: &SyntaxFactory) -> ast::GenericArgList {
+ let args = self.generic_params().filter_map(|param| match param {
+ ast::GenericParam::LifetimeParam(it) => {
+ Some(ast::GenericArg::LifetimeArg(make.lifetime_arg(it.lifetime()?)))
+ }
+ ast::GenericParam::TypeParam(it) => {
+ Some(ast::GenericArg::TypeArg(make.type_arg(make.ty_name(it.name()?))))
+ }
+ ast::GenericParam::ConstParam(it) => {
+ // Name-only const params get parsed as `TypeArg`s
+ Some(ast::GenericArg::TypeArg(make.type_arg(make.ty_name(it.name()?))))
+ }
+ });
+
+ make::generic_arg_list(args)
+ }
+}
+
+impl ast::UseTree {
+ /// Deletes the usetree node represented by the input. Recursively removes parents, including use nodes that become empty.
+ pub fn remove_recursive(self, editor: &SyntaxEditor) {
+ let parent = self.syntax().parent();
+
+ if let Some(u) = parent.clone().and_then(ast::Use::cast) {
+ u.remove(editor);
+ } else if let Some(u) = parent.and_then(ast::UseTreeList::cast) {
+ if u.use_trees().nth(1).is_none()
+ || u.use_trees().all(|use_tree| {
+ use_tree.syntax() == self.syntax() || editor.deleted(use_tree.syntax())
+ })
+ {
+ u.parent_use_tree().remove_recursive(editor);
+ return;
+ }
+ self.remove(editor);
+ u.remove_unnecessary_braces(editor);
+ }
+ }
+
+ /// Splits off the given prefix, making it the path component of the use tree,
+ /// appending the rest of the path to all UseTreeList items.
+ ///
+ /// # Examples
+ ///
+ /// `prefix$0::suffix` -> `prefix::{suffix}`
+ ///
+ /// `prefix$0` -> `prefix::{self}`
+ ///
+ /// `prefix$0::*` -> `prefix::{*}`
+ pub fn split_prefix_with_editor(&self, editor: &SyntaxEditor, prefix: &ast::Path) {
+ debug_assert_eq!(self.path(), Some(prefix.top_path()));
+
+ let make = editor.make();
+ let path = self.path().unwrap();
+ let suffix = if path == *prefix {
+ if self.use_tree_list().is_some() {
+ return;
+ } else if self.star_token().is_some() {
+ make.use_tree_glob()
+ } else {
+ let self_path = make.path_unqualified(make.path_segment_self());
+ make.use_tree(self_path, None, self.rename(), false)
+ }
+ } else {
+ let suffix_segments = path.segments().skip(prefix.segments().count());
+ let suffix_path = make.path_from_segments(suffix_segments, false);
+ make.use_tree(
+ suffix_path,
+ self.use_tree_list(),
+ self.rename(),
+ self.star_token().is_some(),
+ )
+ };
+ let use_tree_list = make.use_tree_list(once(suffix));
+ let new_use_tree = make.use_tree(prefix.clone(), Some(use_tree_list), None, false);
+
+ editor.replace(self.syntax(), new_use_tree.syntax());
+ }
+}
+
+impl ast::RecordExprField {
+ /// This will either replace the initializer, or in the case that this is a shorthand convert
+ /// the initializer into the name ref and insert the expr as the new initializer.
+ pub fn replace_expr(&self, editor: &SyntaxEditor, expr: ast::Expr) {
+ if self.name_ref().is_some() {
+ if let Some(prev) = self.expr() {
+ editor.replace(prev.syntax(), expr.syntax());
+ }
+ } else if let Some(ast::Expr::PathExpr(path_expr)) = self.expr()
+ && let Some(path) = path_expr.path()
+ && let Some(name_ref) = path.as_single_name_ref()
+ {
+ // shorthand `{ x }` → expand to `{ x: expr }`
+ let new_field = editor
+ .make()
+ .record_expr_field(editor.make().name_ref(&name_ref.text()), Some(expr));
+ editor.replace(self.syntax(), new_field.syntax());
+ }
+ }
+}
+
#[test]
fn test_increase_indent() {
let arm_list = {
@@ -285,3 +388,29 @@ fn test_increase_indent() {
}"
);
}
+
+#[test]
+fn split_prefix_inserts_self() {
+ check_split_prefix("use foo;", "foo::{self}");
+}
+
+#[test]
+fn split_prefix_preserves_rename() {
+ check_split_prefix("use foo as bar;", "foo::{self as bar}");
+}
+
+#[test]
+fn split_prefix_wraps_glob() {
+ check_split_prefix("use foo::*;", "foo::{*}");
+}
+
+#[cfg(test)]
+fn check_split_prefix(before: &str, expected: &str) {
+ let source = crate::SourceFile::parse(before, parser::Edition::CURRENT).tree();
+ let use_tree = source.syntax().descendants().find_map(ast::UseTree::cast).unwrap();
+ let (editor, use_tree) = SyntaxEditor::with_ast_node(&use_tree);
+ let prefix = use_tree.path().unwrap();
+ use_tree.split_prefix_with_editor(&editor, &prefix);
+ let edit = editor.finish();
+ assert_eq!(edit.new_root().to_string(), expected);
+}
diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs
deleted file mode 100644
index d7f1844b13..0000000000
--- a/crates/syntax/src/ast/edit_in_place.rs
+++ /dev/null
@@ -1,308 +0,0 @@
-//! Structural editing for ast.
-
-use std::iter::{empty, once, successors};
-
-use parser::T;
-
-use crate::{
- AstNode, AstToken, Direction,
- algo::{self, neighbor},
- ast::{self, make, syntax_factory::SyntaxFactory},
- syntax_editor::SyntaxEditor,
- ted,
-};
-
-use super::HasName;
-
-impl ast::GenericParamList {
- /// Constructs a matching [`ast::GenericArgList`]
- pub fn to_generic_args(&self, make: &SyntaxFactory) -> ast::GenericArgList {
- let args = self.generic_params().filter_map(|param| match param {
- ast::GenericParam::LifetimeParam(it) => {
- Some(ast::GenericArg::LifetimeArg(make.lifetime_arg(it.lifetime()?)))
- }
- ast::GenericParam::TypeParam(it) => {
- Some(ast::GenericArg::TypeArg(make.type_arg(make.ty_name(it.name()?))))
- }
- ast::GenericParam::ConstParam(it) => {
- // Name-only const params get parsed as `TypeArg`s
- Some(ast::GenericArg::TypeArg(make.type_arg(make.ty_name(it.name()?))))
- }
- });
-
- make::generic_arg_list(args)
- }
-}
-
-pub trait Removable: AstNode {
- fn remove(&self);
-}
-
-impl Removable for ast::UseTree {
- fn remove(&self) {
- for dir in [Direction::Next, Direction::Prev] {
- if let Some(next_use_tree) = neighbor(self, dir) {
- let separators = self
- .syntax()
- .siblings_with_tokens(dir)
- .skip(1)
- .take_while(|it| it.as_node() != Some(next_use_tree.syntax()));
- ted::remove_all_iter(separators);
- break;
- }
- }
- ted::remove(self.syntax());
- }
-}
-
-impl ast::UseTree {
- /// Editor variant of UseTree remove
- fn remove_with_editor(&self, editor: &SyntaxEditor) {
- for dir in [Direction::Next, Direction::Prev] {
- if let Some(next_use_tree) = neighbor(self, dir) {
- let separators = self
- .syntax()
- .siblings_with_tokens(dir)
- .skip(1)
- .take_while(|it| it.as_node() != Some(next_use_tree.syntax()));
- for separator in separators {
- editor.delete(separator);
- }
- break;
- }
- }
- editor.delete(self.syntax());
- }
-
- /// Deletes the usetree node represented by the input. Recursively removes parents, including use nodes that become empty.
- pub fn remove_recursive(self, editor: &SyntaxEditor) {
- let parent = self.syntax().parent();
-
- if let Some(u) = parent.clone().and_then(ast::Use::cast) {
- u.remove(editor);
- } else if let Some(u) = parent.and_then(ast::UseTreeList::cast) {
- if u.use_trees().nth(1).is_none()
- || u.use_trees().all(|use_tree| {
- use_tree.syntax() == self.syntax() || editor.deleted(use_tree.syntax())
- })
- {
- u.parent_use_tree().remove_recursive(editor);
- return;
- }
- self.remove_with_editor(editor);
- u.remove_unnecessary_braces(editor);
- }
- }
-
- pub fn get_or_create_use_tree_list(&self) -> ast::UseTreeList {
- match self.use_tree_list() {
- Some(it) => it,
- None => {
- let position = ted::Position::last_child_of(self.syntax());
- let use_tree_list = make::use_tree_list(empty()).clone_for_update();
- let mut elements = Vec::with_capacity(2);
- if self.coloncolon_token().is_none() {
- elements.push(make::token(T![::]).into());
- }
- elements.push(use_tree_list.syntax().clone().into());
- ted::insert_all_raw(position, elements);
- use_tree_list
- }
- }
- }
-
- /// Splits off the given prefix, making it the path component of the use tree,
- /// appending the rest of the path to all UseTreeList items.
- ///
- /// # Examples
- ///
- /// `prefix$0::suffix` -> `prefix::{suffix}`
- ///
- /// `prefix$0` -> `prefix::{self}`
- ///
- /// `prefix$0::*` -> `prefix::{*}`
- pub fn split_prefix(&self, prefix: &ast::Path) {
- debug_assert_eq!(self.path(), Some(prefix.top_path()));
- let path = self.path().unwrap();
- if &path == prefix && self.use_tree_list().is_none() {
- if self.star_token().is_some() {
- // path$0::* -> *
- if let Some(a) = self.coloncolon_token() {
- ted::remove(a)
- }
- ted::remove(prefix.syntax());
- } else {
- // path$0 -> self
- let self_suffix =
- make::path_unqualified(make::path_segment_self()).clone_for_update();
- ted::replace(path.syntax(), self_suffix.syntax());
- }
- } else if split_path_prefix(prefix).is_none() {
- return;
- }
- // At this point, prefix path is detached; _self_ use tree has suffix path.
- // Next, transform 'suffix' use tree into 'prefix::{suffix}'
- let subtree = self.clone_subtree().clone_for_update();
- ted::remove_all_iter(self.syntax().children_with_tokens());
- ted::insert(ted::Position::first_child_of(self.syntax()), prefix.syntax());
- self.get_or_create_use_tree_list().add_use_tree(subtree);
-
- fn split_path_prefix(prefix: &ast::Path) -> Option<()> {
- let parent = prefix.parent_path()?;
- let segment = parent.segment()?;
- if algo::has_errors(segment.syntax()) {
- return None;
- }
- for p in successors(parent.parent_path(), |it| it.parent_path()) {
- p.segment()?;
- }
- if let Some(a) = prefix.parent_path().and_then(|p| p.coloncolon_token()) {
- ted::remove(a)
- }
- ted::remove(prefix.syntax());
- Some(())
- }
- }
-
- /// Editor variant of `split_prefix`
- pub fn split_prefix_with_editor(&self, editor: &SyntaxEditor, prefix: &ast::Path) {
- debug_assert_eq!(self.path(), Some(prefix.top_path()));
-
- let make = editor.make();
- let path = self.path().unwrap();
- let suffix = if path == *prefix && self.use_tree_list().is_none() {
- if self.star_token().is_some() {
- make.use_tree_glob()
- } else {
- let self_path = make.path_unqualified(make.path_segment_self());
- make.use_tree(self_path, None, None, false)
- }
- } else {
- let suffix_segments = path.segments().skip(prefix.segments().count());
- let suffix_path = make.path_from_segments(suffix_segments, false);
- make.use_tree(
- suffix_path,
- self.use_tree_list(),
- self.rename(),
- self.star_token().is_some(),
- )
- };
- let use_tree_list = make.use_tree_list(once(suffix));
- let new_use_tree = make.use_tree(prefix.clone(), Some(use_tree_list), None, false);
-
- editor.replace(self.syntax(), new_use_tree.syntax());
- }
-
- /// Wraps the use tree in use tree list with no top level path (if it isn't already).
- ///
- /// # Examples
- ///
- /// `foo::bar` -> `{foo::bar}`
- ///
- /// `{foo::bar}` -> `{foo::bar}`
- pub fn wrap_in_tree_list(&self) -> Option<()> {
- if self.use_tree_list().is_some()
- && self.path().is_none()
- && self.star_token().is_none()
- && self.rename().is_none()
- {
- return None;
- }
- let subtree = self.clone_subtree().clone_for_update();
- ted::remove_all_iter(self.syntax().children_with_tokens());
- ted::append_child(
- self.syntax(),
- make::use_tree_list(once(subtree)).clone_for_update().syntax(),
- );
- Some(())
- }
-}
-
-impl ast::UseTreeList {
- pub fn add_use_tree(&self, use_tree: ast::UseTree) {
- let (position, elements) = match self.use_trees().last() {
- Some(last_tree) => (
- ted::Position::after(last_tree.syntax()),
- vec![
- make::token(T![,]).into(),
- make::tokens::single_space().into(),
- use_tree.syntax.into(),
- ],
- ),
- None => {
- let position = match self.l_curly_token() {
- Some(l_curly) => ted::Position::after(l_curly),
- None => ted::Position::last_child_of(self.syntax()),
- };
- (position, vec![use_tree.syntax.into()])
- }
- };
- ted::insert_all_raw(position, elements);
- }
-}
-
-impl ast::Use {
- fn remove(&self, editor: &SyntaxEditor) {
- let make = editor.make();
- let next_ws = self
- .syntax()
- .next_sibling_or_token()
- .and_then(|it| it.into_token())
- .and_then(ast::Whitespace::cast);
- if let Some(next_ws) = next_ws {
- let ws_text = next_ws.syntax().text();
- if let Some(rest) = ws_text.strip_prefix('\n') {
- let next_use_removed = next_ws
- .syntax()
- .next_sibling_or_token()
- .and_then(|it| it.into_node())
- .and_then(ast::Use::cast)
- .and_then(|use_| use_.use_tree())
- .is_some_and(|use_tree| editor.deleted(use_tree.syntax()));
- if rest.is_empty() || next_use_removed {
- editor.delete(next_ws.syntax());
- } else {
- editor.replace(next_ws.syntax(), make.whitespace(rest));
- }
- }
- }
- let prev_ws = self
- .syntax()
- .prev_sibling_or_token()
- .and_then(|it| it.into_token())
- .and_then(ast::Whitespace::cast);
- if let Some(prev_ws) = prev_ws {
- let ws_text = prev_ws.syntax().text();
- let prev_newline = ws_text.rfind('\n').map(|x| x + 1).unwrap_or(0);
- let rest = &ws_text[0..prev_newline];
- if rest.is_empty() {
- editor.delete(prev_ws.syntax());
- } else {
- editor.replace(prev_ws.syntax(), make.whitespace(rest));
- }
- }
-
- editor.delete(self.syntax());
- }
-}
-
-impl ast::RecordExprField {
- /// This will either replace the initializer, or in the case that this is a shorthand convert
- /// the initializer into the name ref and insert the expr as the new initializer.
- pub fn replace_expr(&self, editor: &SyntaxEditor, expr: ast::Expr) {
- if self.name_ref().is_some() {
- if let Some(prev) = self.expr() {
- editor.replace(prev.syntax(), expr.syntax());
- }
- } else if let Some(ast::Expr::PathExpr(path_expr)) = self.expr()
- && let Some(path) = path_expr.path()
- && let Some(name_ref) = path.as_single_name_ref()
- {
- // shorthand `{ x }` → expand to `{ x: expr }`
- let new_field = editor
- .make()
- .record_expr_field(editor.make().name_ref(&name_ref.text()), Some(expr));
- editor.replace(self.syntax(), new_field.syntax());
- }
- }
-}
diff --git a/crates/syntax/src/syntax_editor/edits.rs b/crates/syntax/src/syntax_editor/edits.rs
index f2b979eb9d..9fab8716b4 100644
--- a/crates/syntax/src/syntax_editor/edits.rs
+++ b/crates/syntax/src/syntax_editor/edits.rs
@@ -459,13 +459,35 @@ impl Removable for ast::Use {
if let Some(next_ws) = next_ws {
let ws_text = next_ws.syntax().text();
if let Some(rest) = ws_text.strip_prefix('\n') {
- if rest.is_empty() {
+ let next_use_removed = next_ws
+ .syntax()
+ .next_sibling_or_token()
+ .and_then(|it| it.into_node())
+ .and_then(ast::Use::cast)
+ .and_then(|use_| use_.use_tree())
+ .is_some_and(|use_tree| editor.deleted(use_tree.syntax()));
+ if rest.is_empty() || next_use_removed {
editor.delete(next_ws.syntax());
} else {
editor.replace(next_ws.syntax(), make.whitespace(rest));
}
}
}
+ let prev_ws = self
+ .syntax()
+ .prev_sibling_or_token()
+ .and_then(|it| it.into_token())
+ .and_then(ast::Whitespace::cast);
+ if let Some(prev_ws) = prev_ws {
+ let ws_text = prev_ws.syntax().text();
+ let prev_newline = ws_text.rfind('\n').map(|x| x + 1).unwrap_or(0);
+ let rest = &ws_text[0..prev_newline];
+ if rest.is_empty() {
+ editor.delete(prev_ws.syntax());
+ } else {
+ editor.replace(prev_ws.syntax(), make.whitespace(rest));
+ }
+ }
editor.delete(self.syntax());
}