Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #22204 from Shourya742/2026-04-21-migrate-convert-closure-to-fn
migrate convert closure to fn to SyntaxEditor
Chayim Refael Friedman 6 days ago
parent e32c1f4 · parent 4907d41 · commit 4e97512
-rw-r--r--crates/ide-assists/src/handlers/convert_closure_to_fn.rs107
1 files changed, 58 insertions, 49 deletions
diff --git a/crates/ide-assists/src/handlers/convert_closure_to_fn.rs b/crates/ide-assists/src/handlers/convert_closure_to_fn.rs
index 026b139b54..e6d31b9660 100644
--- a/crates/ide-assists/src/handlers/convert_closure_to_fn.rs
+++ b/crates/ide-assists/src/handlers/convert_closure_to_fn.rs
@@ -11,10 +11,10 @@ use syntax::{
ast::{
self, HasArgList, HasGenericParams, HasName,
edit::{AstNodeEdit, IndentLevel},
- make,
+ syntax_factory::SyntaxFactory,
},
hacks::parse_expr_from_str,
- ted,
+ syntax_editor::SyntaxEditor,
};
use crate::assist_context::{AssistContext, Assists};
@@ -64,6 +64,10 @@ pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_, '
_ => None,
}
});
+
+ let (editor, source_root) = SyntaxEditor::new(ctx.source_file().syntax().clone());
+ let make = editor.make();
+
let module = ctx.sema.scope(closure.syntax())?.module();
let closure_ty = ctx.sema.type_of_expr(&closure.clone().into())?;
let callable = closure_ty.original.as_callable(ctx.db())?;
@@ -85,14 +89,15 @@ pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_, '
let ty = param_ty
.display_source_code(ctx.db(), module.into(), true)
.unwrap_or_else(|_| "_".to_owned());
- Some(make::param(node.pat()?, make::ty(&ty)))
+ Some(make.param(node.pat()?, make.ty(&ty)))
}
}
})
.collect::<Option<Vec<_>>>()?;
let capture_params_start = params.len();
- let mut body = closure.body()?.clone_for_update();
+ let closure_param_list = syntax::AstPtr::new(&closure.param_list()?);
+ let body = closure.body()?;
let mut is_gen = false;
let mut is_async = closure.async_token().is_some();
if is_async {
@@ -107,37 +112,30 @@ pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_, '
{
is_async = true;
ret_ty = ret_ty.future_output(ctx.db())?;
- let token_idx = async_token.index();
- let whitespace_tokens_after_count = async_token
+ let end = async_token
.siblings_with_tokens(Direction::Next)
.skip(1)
- .take_while(|token| token.kind() == SyntaxKind::WHITESPACE)
- .count();
- body.syntax().splice_children(
- token_idx..token_idx + whitespace_tokens_after_count + 1,
- Vec::new(),
- );
+ .take_while(|it| it.kind() == SyntaxKind::WHITESPACE)
+ .last()
+ .unwrap_or_else(|| async_token.clone().into());
+ editor.delete_all(async_token.into()..=end);
}
if let Some(gen_token) = block.gen_token() {
is_gen = true;
ret_ty = ret_ty.iterator_item(ctx.db())?;
- let token_idx = gen_token.index();
- let whitespace_tokens_after_count = gen_token
+ let end = gen_token
.siblings_with_tokens(Direction::Next)
.skip(1)
- .take_while(|token| token.kind() == SyntaxKind::WHITESPACE)
- .count();
- body.syntax().splice_children(
- token_idx..token_idx + whitespace_tokens_after_count + 1,
- Vec::new(),
- );
+ .take_while(|it| it.kind() == SyntaxKind::WHITESPACE)
+ .last()
+ .unwrap_or_else(|| gen_token.clone().into());
+ editor.delete_all(gen_token.into()..=end);
}
if block.try_block_modifier().is_none()
&& block.unsafe_token().is_none()
&& block.label().is_none()
&& block.const_token().is_none()
- && block.async_token().is_none()
{
wrap_body_in_block = false;
}
@@ -148,17 +146,17 @@ pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_, '
"Convert closure to fn",
closure.param_list()?.syntax().text_range(),
|builder| {
+ let make = editor.make();
let closure_name_or_default = closure_name
.as_ref()
.map(|(_, _, it)| it.clone())
- .unwrap_or_else(|| make::name("fun_name"));
+ .unwrap_or_else(|| make.name("fun_name"));
let captures = closure_ty.captured_items(ctx.db());
let capture_tys =
captures.iter().map(|capture| capture.captured_ty(ctx.db())).collect::<Vec<_>>();
let mut captures_as_args = Vec::with_capacity(captures.len());
- let body_root = body.syntax().ancestors().last().unwrap();
// We need to defer this work because otherwise the text range of elements is being messed up, and
// replacements for the next captures won't work.
let mut capture_usages_replacement_map = Vec::with_capacity(captures.len());
@@ -167,9 +165,9 @@ pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_, '
// FIXME: Allow configuring the replacement of `self`.
let is_self = capture.local().is_self(ctx.db()) && !capture.has_field_projections();
let capture_name = if is_self {
- make::name("this")
+ make.name("this")
} else {
- make::name(&capture.place_to_name(ctx.db(), ctx.edition()))
+ make.name(&capture.place_to_name(ctx.db(), ctx.edition()))
};
closure_mentioned_generic_params.extend(capture_ty.generic_params(ctx.db()));
@@ -177,9 +175,9 @@ pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_, '
let capture_ty = capture_ty
.display_source_code(ctx.db(), module.into(), true)
.unwrap_or_else(|_| "_".to_owned());
- let param = make::param(
- ast::Pat::IdentPat(make::ident_pat(false, false, capture_name.clone_subtree())),
- make::ty(&capture_ty),
+ let param = make.param(
+ ast::Pat::IdentPat(make.ident_pat(false, false, capture_name.clone())),
+ make.ty(&capture_ty),
);
if is_self {
// Always put `this` first.
@@ -195,7 +193,7 @@ pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_, '
}
let capture_usage_source = capture_usage.source();
- let capture_usage_source = capture_usage_source.to_node(&body_root);
+ let capture_usage_source = capture_usage_source.to_node(&source_root);
let mut expr = match capture_usage_source {
Either::Left(expr) => expr,
Either::Right(pat) => {
@@ -207,16 +205,16 @@ pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_, '
expr = peel_ref(expr);
}
let replacement = wrap_capture_in_deref_if_needed(
+ make,
&expr,
&capture_name,
capture.kind(),
matches!(expr, ast::Expr::RefExpr(_)) || capture_usage.is_ref(),
- )
- .clone_for_update();
+ );
capture_usages_replacement_map.push((expr, replacement));
}
- let capture_as_arg = capture_as_arg(ctx, capture);
+ let capture_as_arg = capture_as_arg(make, ctx, capture);
if is_self {
captures_as_args.insert(0, capture_as_arg);
} else {
@@ -225,32 +223,38 @@ pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_, '
}
let (closure_type_params, closure_where_clause) =
- compute_closure_type_params(ctx, closure_mentioned_generic_params, &closure);
+ compute_closure_type_params(make, ctx, closure_mentioned_generic_params, &closure);
for (old, new) in capture_usages_replacement_map {
- if old == body {
- body = new;
- } else {
- ted::replace(old.syntax(), new.syntax());
- }
+ editor.replace(old.syntax(), new.syntax());
}
+ let body = closure_param_list
+ .to_node(editor.finish().new_root())
+ .syntax()
+ .parent()
+ .and_then(ast::ClosureExpr::cast)
+ .and_then(|closure| closure.body())
+ .unwrap();
+
+ let make = SyntaxFactory::without_mappings();
+
let body = if wrap_body_in_block {
- make::block_expr([], Some(body.reset_indent().indent(1.into())))
+ make.block_expr([], Some(body.reset_indent().indent(1.into())))
} else {
ast::BlockExpr::cast(body.syntax().clone()).unwrap()
};
- let params = make::param_list(None, params);
+ let params = make.param_list(None, params);
let ret_ty = if ret_ty.is_unit() {
None
} else {
let ret_ty = ret_ty
.display_source_code(ctx.db(), module.into(), true)
.unwrap_or_else(|_| "_".to_owned());
- Some(make::ret_type(make::ty(&ret_ty)))
+ Some(make.ret_type(make.ty(&ret_ty)))
};
- let mut fn_ = make::fn_(
+ let mut fn_ = make.fn_(
None,
None,
closure_name_or_default.clone(),
@@ -266,7 +270,6 @@ pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_, '
);
fn_ = fn_.dedent(IndentLevel::from_token(&fn_.syntax().last_token().unwrap()));
- builder.edit_file(ctx.vfs_file_id());
match &closure_name {
Some((closure_decl, _, _)) => {
fn_ = fn_.indent(closure_decl.indent_level());
@@ -346,6 +349,7 @@ pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_, '
}
fn compute_closure_type_params(
+ make: &SyntaxFactory,
ctx: &AssistContext<'_, '_>,
mentioned_generic_params: FxHashSet<hir::GenericParam>,
closure: &ast::ClosureExpr,
@@ -472,11 +476,11 @@ fn compute_closure_type_params(
}))
.collect::<Vec<_>>();
let where_clause =
- (!include_where_bounds.is_empty()).then(|| make::where_clause(include_where_bounds));
+ (!include_where_bounds.is_empty()).then(|| make.where_clause(include_where_bounds));
// FIXME: Consider generic parameters that do not appear in params/return type/captures but
// written explicitly inside the closure.
- (Some(make::generic_param_list(include_params)), where_clause)
+ (Some(make.generic_param_list(include_params)), where_clause)
}
fn peel_parens(mut expr: ast::Expr) -> ast::Expr {
@@ -497,12 +501,13 @@ fn peel_ref(mut expr: ast::Expr) -> ast::Expr {
}
fn wrap_capture_in_deref_if_needed(
+ make: &SyntaxFactory,
expr: &ast::Expr,
capture_name: &ast::Name,
capture_kind: CaptureKind,
is_ref: bool,
) -> ast::Expr {
- let capture_name = make::expr_path(make::path_from_text(&capture_name.text()));
+ let capture_name = make.expr_path(make.path_from_text(&capture_name.text()));
if capture_kind == CaptureKind::Move || is_ref {
return capture_name;
}
@@ -524,10 +529,14 @@ fn wrap_capture_in_deref_if_needed(
if does_autoderef {
return capture_name;
}
- make::expr_prefix(T![*], capture_name).into()
+ make.expr_prefix(T![*], capture_name).into()
}
-fn capture_as_arg(ctx: &AssistContext<'_, '_>, capture: &ClosureCapture<'_>) -> ast::Expr {
+fn capture_as_arg(
+ make: &SyntaxFactory,
+ ctx: &AssistContext<'_, '_>,
+ capture: &ClosureCapture<'_>,
+) -> ast::Expr {
let place = parse_expr_from_str(
&capture.display_place_source_code(ctx.db(), ctx.edition()),
ctx.edition(),
@@ -543,7 +552,7 @@ fn capture_as_arg(ctx: &AssistContext<'_, '_>, capture: &ClosureCapture<'_>) ->
{
return expr.expr().expect("`display_place_source_code()` produced an invalid expr");
}
- make::expr_ref(place, needs_mut)
+ make.expr_ref(place, needs_mut)
}
fn handle_calls(