Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/ide-assists/src/handlers/inline_call.rs309
-rw-r--r--crates/ide-assists/src/handlers/inline_type_alias.rs15
-rw-r--r--crates/ide-db/src/imports/insert_use.rs25
-rw-r--r--crates/syntax/src/ast/edit_in_place.rs19
-rw-r--r--crates/syntax/src/ast/syntax_factory/constructors.rs52
5 files changed, 279 insertions, 141 deletions
diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs
index 21f2249a19..c6a0b22d47 100644
--- a/crates/ide-assists/src/handlers/inline_call.rs
+++ b/crates/ide-assists/src/handlers/inline_call.rs
@@ -1,6 +1,5 @@
use std::collections::BTreeSet;
-use ast::make;
use either::Either;
use hir::{
FileRange, PathResolution, Semantics, TypeInfo,
@@ -8,22 +7,23 @@ use hir::{
sym,
};
use ide_db::{
- EditionedFileId, RootDatabase,
+ EditionedFileId, FileId, FxHashMap, RootDatabase,
base_db::Crate,
defs::Definition,
- imports::insert_use::remove_path_if_in_use_stmt,
+ imports::insert_use::remove_use_tree_if_simple,
path_transform::PathTransform,
search::{FileReference, FileReferenceNode, SearchScope},
- source_change::SourceChangeBuilder,
syntax_helpers::{node_ext::expr_as_name_ref, prettify_macro_expansion},
};
use itertools::{Itertools, izip};
use syntax::{
- AstNode, NodeOrToken, SyntaxKind,
+ AstNode, NodeOrToken, SyntaxKind, TextRange,
ast::{
- self, HasArgList, HasGenericArgs, Pat, PathExpr, edit::IndentLevel, edit_in_place::Indent,
+ self, HasArgList, HasGenericArgs, Pat, PathExpr,
+ edit::{AstNodeEdit, IndentLevel},
+ make,
},
- ted,
+ syntax_editor::SyntaxEditor,
};
use crate::{
@@ -106,37 +106,59 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) ->
let mut usages = usages.all();
let current_file_usage = usages.references.remove(&def_file);
+ let mut file_editors: FxHashMap<FileId, SyntaxEditor> = FxHashMap::default();
let mut remove_def = true;
let mut inline_refs_for_file = |file_id: EditionedFileId, refs: Vec<FileReference>| {
let file_id = file_id.file_id(ctx.db());
builder.edit_file(file_id);
let call_krate = ctx.sema.file_to_module_def(file_id).map(|it| it.krate(ctx.db()));
let count = refs.len();
- // The collects are required as we are otherwise iterating while mutating 🙅‍♀️🙅‍♂️
- let (name_refs, name_refs_use) = split_refs_and_uses(builder, refs, Some);
+ let (name_refs, use_trees) = split_refs_and_uses(refs, Some);
let call_infos: Vec<_> = name_refs
.into_iter()
.filter_map(|it| CallInfo::from_name_ref(it, call_krate?.into()))
// FIXME: do not handle callsites in macros' parameters, because
// directly inlining into macros may cause errors.
.filter(|call_info| !ctx.sema.hir_file_for(call_info.node.syntax()).is_macro())
- .map(|call_info| {
- let mut_node = builder.make_syntax_mut(call_info.node.syntax().clone());
- (call_info, mut_node)
- })
.collect();
- let replaced = call_infos
- .into_iter()
- .map(|(call_info, mut_node)| {
- let replacement =
- inline(&ctx.sema, def_file, function, &func_body, &params, &call_info);
- ted::replace(mut_node, replacement.syntax());
- })
- .count();
- if replaced + name_refs_use.len() == count {
- // we replaced all usages in this file, so we can remove the imports
- name_refs_use.iter().for_each(remove_path_if_in_use_stmt);
- } else {
+
+ // Skip calls nested inside other calls being inlined to avoid overlapping
+ // edits. Nested calls are implicitly replaced when the outer call is inlined.
+ let all_ranges: Vec<TextRange> =
+ call_infos.iter().map(|ci| ci.node.syntax().text_range()).collect();
+ let (call_infos, nested_infos): (Vec<_>, Vec<_>) =
+ call_infos.into_iter().partition(|ci| {
+ let r = ci.node.syntax().text_range();
+ !all_ranges.iter().any(|&other| other != r && other.contains_range(r))
+ });
+ let nested_count = nested_infos.len();
+
+ let anchor = call_infos
+ .first()
+ .map(|ci| ci.node.syntax().clone())
+ .or_else(|| use_trees.first().map(|ut| ut.syntax().clone()));
+ if let Some(anchor) = anchor {
+ let editor =
+ file_editors.entry(file_id).or_insert_with(|| builder.make_editor(&anchor));
+ let replaced = call_infos
+ .into_iter()
+ .map(|call_info| {
+ let replacement = inline(
+ &ctx.sema, def_file, function, &func_body, &params, &call_info,
+ editor,
+ );
+ editor.replace(call_info.node.syntax(), replacement.syntax());
+ })
+ .count();
+ if replaced + nested_count + use_trees.len() == count {
+ // we replaced all usages in this file, so we can remove the imports
+ for use_tree in &use_trees {
+ remove_use_tree_if_simple(use_tree, editor);
+ }
+ } else {
+ remove_def = false;
+ }
+ } else if use_trees.len() != count {
remove_def = false;
}
};
@@ -148,24 +170,29 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) ->
None => builder.edit_file(vfs_def_file),
}
if remove_def {
- builder.delete(ast_func.syntax().text_range());
+ let editor = file_editors
+ .entry(vfs_def_file)
+ .or_insert_with(|| builder.make_editor(ast_func.syntax()));
+ editor.delete(ast_func.syntax());
+ }
+ for (file_id, editor) in file_editors {
+ builder.add_file_edits(file_id, editor);
}
},
)
}
pub(super) fn split_refs_and_uses<T: ast::AstNode>(
- builder: &mut SourceChangeBuilder,
iter: impl IntoIterator<Item = FileReference>,
mut map_ref: impl FnMut(ast::NameRef) -> Option<T>,
-) -> (Vec<T>, Vec<ast::Path>) {
+) -> (Vec<T>, Vec<ast::UseTree>) {
iter.into_iter()
.filter_map(|file_ref| match file_ref.name {
FileReferenceNode::NameRef(name_ref) => Some(name_ref),
_ => None,
})
.filter_map(|name_ref| match name_ref.syntax().ancestors().find_map(ast::UseTree::cast) {
- Some(use_tree) => builder.make_mut(use_tree).path().map(Either::Right),
+ Some(use_tree) => Some(Either::Right(use_tree)),
None => map_ref(name_ref).map(Either::Left),
})
.partition_map(|either| either)
@@ -235,14 +262,11 @@ pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
let syntax = call_info.node.syntax().clone();
acc.add(AssistId::refactor_inline("inline_call"), label, syntax.text_range(), |builder| {
- let replacement = inline(&ctx.sema, file_id, function, &fn_body, &params, &call_info);
- builder.replace_ast(
- match call_info.node {
- ast::CallableExpr::Call(it) => ast::Expr::CallExpr(it),
- ast::CallableExpr::MethodCall(it) => ast::Expr::MethodCallExpr(it),
- },
- replacement,
- );
+ let editor = builder.make_editor(call_info.node.syntax());
+ let replacement =
+ inline(&ctx.sema, file_id, function, &fn_body, &params, &call_info, &editor);
+ editor.replace(call_info.node.syntax(), replacement.syntax());
+ builder.add_file_edits(ctx.vfs_file_id(), editor);
})
}
@@ -318,21 +342,25 @@ fn inline(
fn_body: &ast::BlockExpr,
params: &[(ast::Pat, Option<ast::Type>, hir::Param<'_>)],
CallInfo { node, arguments, generic_arg_list, krate }: &CallInfo,
+ file_editor: &SyntaxEditor,
) -> ast::Expr {
+ let make = file_editor.make();
let file_id = sema.hir_file_for(fn_body.syntax());
- let mut body = if let Some(macro_file) = file_id.macro_file() {
+ let body_to_clone = if let Some(macro_file) = file_id.macro_file() {
cov_mark::hit!(inline_call_defined_in_macro);
let span_map = sema.db.expansion_span_map(macro_file);
let body_prettified =
prettify_macro_expansion(sema.db, fn_body.syntax().clone(), &span_map, *krate);
- if let Some(body) = ast::BlockExpr::cast(body_prettified) {
- body
- } else {
- fn_body.clone_for_update()
- }
+ if let Some(body) = ast::BlockExpr::cast(body_prettified) { body } else { fn_body.clone() }
} else {
- fn_body.clone_for_update()
+ fn_body.clone()
};
+
+ // Capture before `with_ast_node` re-roots and loses the source-relative position.
+ let mut original_body_indent = IndentLevel::from_node(body_to_clone.syntax());
+ let body_offset = body_to_clone.syntax().text_range().start();
+ let (editor, body) = SyntaxEditor::with_ast_node(&body_to_clone);
+
let usages_for_locals = |local| {
Definition::Local(local)
.usages(sema)
@@ -355,7 +383,7 @@ fn inline(
.map(|FileReference { name, range, .. }| match name {
FileReferenceNode::NameRef(_) => body
.syntax()
- .covering_element(range)
+ .covering_element(range - body_offset)
.ancestors()
.nth(3)
.and_then(ast::PathExpr::cast),
@@ -368,48 +396,60 @@ fn inline(
})
.collect();
- if function.self_param(sema.db).is_some() {
- let this = || {
- make::name_ref("this")
- .syntax()
- .clone_for_update()
- .first_token()
- .expect("NameRef should have had a token.")
- };
- if let Some(self_local) = params[0].2.as_local(sema.db) {
- usages_for_locals(self_local)
- .filter_map(|FileReference { name, range, .. }| match name {
- FileReferenceNode::NameRef(_) => Some(body.syntax().covering_element(range)),
- _ => None,
- })
- .for_each(|usage| {
- ted::replace(usage, this());
- });
- }
- }
+ let has_self_param = function.self_param(sema.db).is_some();
+
+ // Collect all self token usages upfront (needed when a let binding is emitted).
+ // When self can be directly inlined, the parameter loop handles those PathExprs;
+ // when a let stmt is needed, we rename all self tokens to `this` here.
+ let self_token_usages: Vec<_> = if has_self_param {
+ params[0]
+ .2
+ .as_local(sema.db)
+ .map(|self_local| {
+ usages_for_locals(self_local)
+ .filter_map(|FileReference { name, range, .. }| match name {
+ FileReferenceNode::NameRef(_) => {
+ Some(body.syntax().covering_element(range - body_offset))
+ }
+ _ => None,
+ })
+ .collect()
+ })
+ .unwrap_or_default()
+ } else {
+ Vec::new()
+ };
- // We should place the following code after last usage of `usages_for_locals`
- // because `ted::replace` will change the offset in syntax tree, which makes
- // `FileReference` incorrect
+ // Replace `Self` type keywords with the actual impl type
if let Some(imp) =
sema.ancestors_with_macros(fn_body.syntax().clone()).find_map(ast::Impl::cast)
&& !node.syntax().ancestors().any(|anc| &anc == imp.syntax())
&& let Some(t) = imp.self_ty()
{
- while let Some(self_tok) = body
+ let self_tokens: Vec<_> = body
.syntax()
.descendants_with_tokens()
.filter_map(NodeOrToken::into_token)
- .find(|tok| tok.kind() == SyntaxKind::SELF_TYPE_KW)
- {
- let replace_with = t.clone_subtree().syntax().clone_for_update();
- if !is_in_type_path(&self_tok)
- && let Some(ty) = ast::Type::cast(replace_with.clone())
- && let Some(generic_arg_list) = ty.generic_arg_list()
+ .filter(|tok| tok.kind() == SyntaxKind::SELF_TYPE_KW)
+ .collect();
+ for self_tok in self_tokens {
+ let replace_with = if !is_in_type_path(&self_tok)
+ && let Some(generic_arg_list) = t.generic_arg_list()
{
- ted::remove(generic_arg_list.syntax());
- }
- ted::replace(self_tok, replace_with);
+ // Strip the outer generic arg list and reparse, since turbofish-less
+ // generics aren't valid in expression position. The outermost
+ // `GenericArgList` text is unique within `t`'s text (any inner generics
+ // are nested inside it), so `replacen(.., 1)` is safe.
+ let stripped = t.syntax().text().to_string().replacen(
+ &generic_arg_list.syntax().text().to_string(),
+ "",
+ 1,
+ );
+ editor.make().ty(&stripped).syntax().clone()
+ } else {
+ t.syntax().clone()
+ };
+ editor.replace(self_tok, replace_with);
}
}
@@ -428,7 +468,19 @@ fn inline(
}
}
- let mut let_stmts = Vec::new();
+ let mut let_stmts: Vec<ast::Stmt> = Vec::new();
+
+ let this_token = editor
+ .make()
+ .name_ref("this")
+ .syntax()
+ .first_token()
+ .expect("NameRef should have had a token.");
+ let rewrite_self_to_this = |editor: &SyntaxEditor| {
+ for usage in &self_token_usages {
+ editor.replace(usage.clone(), this_token.clone());
+ }
+ };
// Inline parameter expressions or generate `let` statements depending on whether inlining works or not.
for ((pat, param_ty, param), usages, expr) in izip!(params, param_use_nodes, arguments) {
@@ -458,7 +510,7 @@ fn inline(
let is_self = param.name(sema.db).is_some_and(|name| name == sym::self_);
if is_self {
- let mut this_pat = make::ident_pat(false, false, make::name("this"));
+ let mut this_pat = make.ident_pat(false, false, make.name("this"));
let mut expr = expr.clone();
if let Pat::IdentPat(pat) = pat {
match (pat.ref_token(), pat.mut_token()) {
@@ -466,11 +518,11 @@ fn inline(
(None, None) => {}
// mut self => let mut this = obj
(None, Some(_)) => {
- this_pat = make::ident_pat(false, true, make::name("this"));
+ this_pat = make.ident_pat(false, true, make.name("this"));
}
// &self => let this = &obj
(Some(_), None) => {
- expr = make::expr_ref(expr, false);
+ expr = make.expr_ref(expr, false);
}
// let foo = &mut X; &mut self => let this = &mut obj
// let mut foo = X; &mut self => let this = &mut *obj (reborrow)
@@ -479,35 +531,38 @@ fn inline(
.type_of_expr(&expr)
.map(|ty| ty.original.is_mutable_reference());
expr = if let Some(true) = should_reborrow {
- make::expr_reborrow(expr)
+ make.expr_reborrow(expr)
} else {
- make::expr_ref(expr, true)
+ make.expr_ref(expr, true)
};
}
}
};
- let_stmts
- .push(make::let_stmt(this_pat.into(), ty, Some(expr)).clone_for_update().into())
+ let_stmts.push(make.let_stmt(this_pat.into(), ty, Some(expr)).into())
} else {
- let_stmts.push(
- make::let_stmt(pat.clone(), ty, Some(expr.clone())).clone_for_update().into(),
- );
+ let_stmts.push(make.let_stmt(pat.clone(), ty, Some(expr.clone())).into());
}
};
+ let is_self_param =
+ has_self_param && param.name(sema.db).is_some_and(|name| name == sym::self_);
+
// check if there is a local var in the function that conflicts with parameter
// if it does then emit a let statement and continue
if func_let_vars.contains(&expr.syntax().text().to_string()) {
+ if is_self_param {
+ rewrite_self_to_this(&editor);
+ }
insert_let_stmt();
continue;
}
- let inline_direct = |usage, replacement: &ast::Expr| {
+ let inline_direct = |editor: &SyntaxEditor, usage: &PathExpr, replacement: &ast::Expr| {
if let Some(field) = path_expr_as_record_field(usage) {
cov_mark::hit!(inline_call_inline_direct_field);
- field.replace_expr(replacement.clone_for_update());
+ field.replace_expr_with_editor(editor, replacement.clone());
} else {
- ted::replace(usage.syntax(), replacement.syntax().clone_for_update());
+ editor.replace(usage.syntax(), replacement.syntax());
}
};
@@ -518,66 +573,74 @@ fn inline(
&& usage.syntax().parent().and_then(ast::Expr::cast).is_some() =>
{
cov_mark::hit!(inline_call_inline_closure);
- let expr = make::expr_paren(expr.clone()).into();
- inline_direct(usage, &expr);
+ let expr = editor.make().expr_paren(expr.clone()).into();
+ inline_direct(&editor, usage, &expr);
}
// inline single use literals
[usage] if matches!(expr, ast::Expr::Literal(_)) => {
cov_mark::hit!(inline_call_inline_literal);
- inline_direct(usage, expr);
+ inline_direct(&editor, usage, expr);
}
// inline direct local arguments
[_, ..] if expr_as_name_ref(expr).is_some() => {
cov_mark::hit!(inline_call_inline_locals);
- usages.iter().for_each(|usage| inline_direct(usage, expr));
+ usages.iter().for_each(|usage| inline_direct(&editor, usage, expr));
}
// can't inline, emit a let statement
_ => {
+ if is_self_param {
+ // Rename all `self` tokens to `this` so the let binding matches.
+ rewrite_self_to_this(&editor);
+ }
insert_let_stmt();
}
}
}
+ // Apply all edits to get the transformed body
+ let edit = editor.finish();
+ let mut body = ast::BlockExpr::cast(edit.new_root().clone())
+ .expect("editor root should still be a BlockExpr");
+
+ // Apply generic substitution (needs immutable tree)
if let Some(generic_arg_list) = generic_arg_list.clone()
&& let Some((target, source)) = &sema.scope(node.syntax()).zip(sema.scope(fn_body.syntax()))
- {
- body.reindent_to(IndentLevel(0));
- if let Some(new_body) = ast::BlockExpr::cast(
+ && let Some(new_body) = ast::BlockExpr::cast(
PathTransform::function_call(target, source, function, generic_arg_list)
.apply(body.syntax()),
- ) {
- body = new_body;
- }
+ )
+ {
+ body = new_body;
}
let is_async_fn = function.is_async(sema.db);
if is_async_fn {
cov_mark::hit!(inline_call_async_fn);
- body = make::async_move_block_expr(body.statements(), body.tail_expr()).clone_for_update();
+ body = make.async_move_block_expr(body.statements(), body.tail_expr());
// Arguments should be evaluated outside the async block, and then moved into it.
if !let_stmts.is_empty() {
cov_mark::hit!(inline_call_async_fn_with_let_stmts);
- body.indent(IndentLevel(1));
- body = make::block_expr(let_stmts, Some(body.into())).clone_for_update();
+ body = body.indent(IndentLevel(1));
+ body = make.block_expr(let_stmts, Some(body.into()));
}
- } else if let Some(stmt_list) = body.stmt_list() {
- let position = stmt_list.l_curly_token().expect("L_CURLY for StatementList is missing.");
- let_stmts.into_iter().rev().for_each(|let_stmt| {
- ted::insert(ted::Position::after(position.clone()), let_stmt.syntax().clone());
- });
+ } else if !let_stmts.is_empty() {
+ // Prepend let statements to the body's existing statements
+ let stmts: Vec<ast::Stmt> = let_stmts.into_iter().chain(body.statements()).collect();
+ body = make.block_expr(stmts, body.tail_expr());
+ original_body_indent = IndentLevel(0);
}
let original_indentation = match node {
ast::CallableExpr::Call(it) => it.indent_level(),
ast::CallableExpr::MethodCall(it) => it.indent_level(),
};
- body.reindent_to(original_indentation);
+ body = body.dedent(original_body_indent).indent(original_indentation);
let no_stmts = body.statements().next().is_none();
match body.tail_expr() {
Some(expr) if matches!(expr, ast::Expr::ClosureExpr(_)) && no_stmts => {
- make::expr_paren(expr).clone_for_update().into()
+ make.expr_paren(expr).into()
}
Some(expr) if !is_async_fn && no_stmts => expr,
_ => match node
@@ -587,7 +650,7 @@ fn inline(
.and_then(|bin_expr| bin_expr.lhs())
{
Some(lhs) if lhs.syntax() == node.syntax() => {
- make::expr_paren(ast::Expr::BlockExpr(body)).clone_for_update().into()
+ make.expr_paren(ast::Expr::BlockExpr(body)).into()
}
_ => ast::Expr::BlockExpr(body),
},
@@ -1866,6 +1929,28 @@ fn _hash2(self_: &u64, state: &mut u64) {
}
#[test]
+ fn inline_callers_nested_calls() {
+ check_assist(
+ inline_into_callers,
+ r#"
+fn id$0(x: u32) -> u32 { x }
+fn main() {
+ let x = id(id(1));
+}
+"#,
+ r#"
+
+fn main() {
+ let x = {
+ let x = id(1);
+ x
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
fn inline_into_callers_in_macros_not_applicable() {
check_assist_not_applicable(
inline_into_callers,
diff --git a/crates/ide-assists/src/handlers/inline_type_alias.rs b/crates/ide-assists/src/handlers/inline_type_alias.rs
index 6d8750afdc..9c7d266057 100644
--- a/crates/ide-assists/src/handlers/inline_type_alias.rs
+++ b/crates/ide-assists/src/handlers/inline_type_alias.rs
@@ -5,8 +5,7 @@
use hir::{HasSource, PathResolution};
use ide_db::FxHashMap;
use ide_db::{
- defs::Definition, imports::insert_use::ast_to_remove_for_path_in_use_stmt,
- search::FileReference,
+ defs::Definition, imports::insert_use::remove_use_tree_if_simple, search::FileReference,
};
use itertools::Itertools;
use syntax::ast::syntax_factory::SyntaxFactory;
@@ -72,14 +71,12 @@ pub(crate) fn inline_type_alias_uses(acc: &mut Assists, ctx: &AssistContext<'_>)
let source = ctx.sema.parse(file_id);
let editor = builder.make_editor(source.syntax());
- let (path_types, path_type_uses) =
- split_refs_and_uses(builder, refs, |path_type| {
- path_type.syntax().ancestors().nth(3).and_then(ast::PathType::cast)
- });
+ let (path_types, path_type_uses) = split_refs_and_uses(refs, |path_type| {
+ path_type.syntax().ancestors().nth(3).and_then(ast::PathType::cast)
+ });
path_type_uses
.iter()
- .flat_map(ast_to_remove_for_path_in_use_stmt)
- .for_each(|x| editor.delete(x.syntax()));
+ .for_each(|use_tree| remove_use_tree_if_simple(use_tree, &editor));
for (target, replacement) in path_types.into_iter().filter_map(|path_type| {
let replacement =
@@ -1094,7 +1091,6 @@ fn f() -> Vec<&str> {
}
//- /foo.rs
-
fn foo() {
let _: Vec<i8> = Vec::new();
}
@@ -1123,7 +1119,6 @@ mod foo;
//- /foo.rs
-
fn foo() {
let _: i32 = 0;
}
diff --git a/crates/ide-db/src/imports/insert_use.rs b/crates/ide-db/src/imports/insert_use.rs
index fe30a4dc5c..7402ec8f5a 100644
--- a/crates/ide-db/src/imports/insert_use.rs
+++ b/crates/ide-db/src/imports/insert_use.rs
@@ -7,10 +7,7 @@ use std::cmp::Ordering;
use hir::Semantics;
use syntax::{
Direction, NodeOrToken, SyntaxKind, SyntaxNode, algo,
- ast::{
- self, AstNode, HasAttrs, HasModuleItem, HasVisibility, PathSegmentKind,
- edit_in_place::Removable, make,
- },
+ ast::{self, AstNode, HasAttrs, HasModuleItem, HasVisibility, PathSegmentKind, make},
syntax_editor::{Position, SyntaxEditor},
ted,
};
@@ -326,24 +323,14 @@ fn insert_use_with_alias_option_with_editor(
insert_use_with_editor_(scope, use_item, cfg.group, syntax_editor);
}
-pub fn ast_to_remove_for_path_in_use_stmt(path: &ast::Path) -> Option<Box<dyn Removable>> {
- // FIXME: improve this
- if path.parent_path().is_some() {
- return None;
- }
- let use_tree = path.syntax().parent().and_then(ast::UseTree::cast)?;
+pub fn remove_use_tree_if_simple(use_tree: &ast::UseTree, editor: &SyntaxEditor) {
if use_tree.use_tree_list().is_some() || use_tree.star_token().is_some() {
- return None;
+ return;
}
if let Some(use_) = use_tree.syntax().parent().and_then(ast::Use::cast) {
- return Some(Box::new(use_));
- }
- Some(Box::new(use_tree))
-}
-
-pub fn remove_path_if_in_use_stmt(path: &ast::Path) {
- if let Some(node) = ast_to_remove_for_path_in_use_stmt(path) {
- node.remove();
+ syntax::syntax_editor::Removable::remove(&use_, editor);
+ } else {
+ syntax::syntax_editor::Removable::remove(use_tree, editor);
}
}
diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs
index 46ea4daba8..5396826394 100644
--- a/crates/syntax/src/ast/edit_in_place.rs
+++ b/crates/syntax/src/ast/edit_in_place.rs
@@ -10,6 +10,7 @@ use crate::{
SyntaxNode, SyntaxToken,
algo::{self, neighbor},
ast::{self, edit::IndentLevel, make},
+ syntax_editor::SyntaxEditor,
ted,
};
@@ -451,6 +452,24 @@ impl ast::RecordExprField {
ted::insert_all_raw(ted::Position::last_child_of(self.syntax()), children);
}
}
+
+ /// [`SyntaxEditor`]-based equivalent of [`replace_expr`](Self::replace_expr).
+ pub fn replace_expr_with_editor(&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());
+ }
+ }
}
impl ast::RecordPatFieldList {
diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs
index b9106408b3..5a01580c56 100644
--- a/crates/syntax/src/ast/syntax_factory/constructors.rs
+++ b/crates/syntax/src/ast/syntax_factory/constructors.rs
@@ -985,6 +985,37 @@ impl SyntaxFactory {
ast
}
+ pub fn async_move_block_expr(
+ &self,
+ statements: impl IntoIterator<Item = ast::Stmt>,
+ tail_expr: Option<ast::Expr>,
+ ) -> ast::BlockExpr {
+ let (statements, mut input) = iterator_input(statements);
+
+ let ast = make::async_move_block_expr(statements, tail_expr.clone()).clone_for_update();
+
+ if let Some(mut mapping) = self.mappings() {
+ let stmt_list = ast.stmt_list().unwrap();
+ let mut builder = SyntaxMappingBuilder::new(stmt_list.syntax().clone());
+
+ if let Some(input) = tail_expr {
+ builder.map_node(
+ input.syntax().clone(),
+ stmt_list.tail_expr().unwrap().syntax().clone(),
+ );
+ } else if let Some(ast_tail) = stmt_list.tail_expr() {
+ let last_stmt = input.pop().unwrap();
+ builder.map_node(last_stmt, ast_tail.syntax().clone());
+ }
+
+ builder.map_children(input, stmt_list.statements().map(|it| it.syntax().clone()));
+
+ builder.finish(&mut mapping);
+ }
+
+ ast
+ }
+
pub fn expr_empty_block(&self) -> ast::BlockExpr {
make::expr_empty_block().clone_for_update()
}
@@ -1135,6 +1166,27 @@ impl SyntaxFactory {
ast.into()
}
+ pub fn expr_reborrow(&self, expr: ast::Expr) -> ast::Expr {
+ let ast::Expr::RefExpr(ast) = make::expr_reborrow(expr.clone()).clone_for_update() else {
+ unreachable!()
+ };
+
+ if let Some(mut mapping) = self.mappings() {
+ // Layout: RefExpr(&mut, PrefixExpr(*, expr)). Map `expr` to the
+ // inner expr inside the synthesized PrefixExpr.
+ let prefix = match ast.expr() {
+ Some(ast::Expr::PrefixExpr(p)) => p,
+ _ => unreachable!("expr_reborrow always produces `&mut *expr`"),
+ };
+ let inner = prefix.expr().unwrap();
+ let mut builder = SyntaxMappingBuilder::new(prefix.syntax().clone());
+ builder.map_node(expr.syntax().clone(), inner.syntax().clone());
+ builder.finish(&mut mapping);
+ }
+
+ ast.into()
+ }
+
pub fn expr_raw_ref(&self, expr: ast::Expr, exclusive: bool) -> ast::Expr {
let ast::Expr::RefExpr(ast) =
make::expr_raw_ref(expr.clone(), exclusive).clone_for_update()