From be9e3fdf3690cb943aa35290049bbd77ddb0cde2 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 23 Sep 2025 15:45:31 +0800 Subject: [PATCH] Add ide-assist: unwrap_destructure_assign_unpaired Unwrap the tuple destructuration assignment to different variables. Example --- ```rust fn foo() { let (a, b); (a, b) $0= (2, 3); } ``` -> ```rust fn foo() { let (a, b); a = 2; b = 3; } ``` --- .../src/handlers/unwrap_destructure_assign.rs | 109 ++++++++++++++++++ crates/ide-assists/src/lib.rs | 2 + crates/ide-assists/src/tests/generated.rs | 20 ++++ 3 files changed, 131 insertions(+) create mode 100644 crates/ide-assists/src/handlers/unwrap_destructure_assign.rs diff --git a/crates/ide-assists/src/handlers/unwrap_destructure_assign.rs b/crates/ide-assists/src/handlers/unwrap_destructure_assign.rs new file mode 100644 index 000000000000..6d1ecf3def6f --- /dev/null +++ b/crates/ide-assists/src/handlers/unwrap_destructure_assign.rs @@ -0,0 +1,109 @@ +use itertools::Itertools; +use syntax::{ + AstNode, T, + ast::{self, Expr, edit::AstNodeEdit, syntax_factory::SyntaxFactory}, + syntax_editor::Element, +}; + +use crate::{AssistContext, AssistId, Assists}; + +pub(crate) fn unwrap_destructure_assign(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let eq_token = ctx.find_token_syntax_at_offset(T![=])?; + let assign = ast::BinExpr::cast(eq_token.parent()?)?; + let expr_stmt = ast::ExprStmt::cast(assign.syntax().parent()?)?; + let (lhs, rhs) = (assign.lhs()?, assign.rhs()?); + + if !ctx.has_empty_selection() { + return None; + } + + match (lhs, rhs) { + (Expr::TupleExpr(lhs), Expr::TupleExpr(rhs)) => { + unwrap_tuple_destructure_assign(acc, ctx, expr_stmt, lhs, rhs) + } + _ => None, + } +} + +// Assist: unwrap_tuple_destructure_assign +// +// Unwrap the tuple destructuration assignment to different variables. +// +// ``` +// fn foo() { +// let (a, b); +// (a, b) $0= (2, 3); +// } +// ``` +// -> +// ``` +// fn foo() { +// let (a, b); +// a = 2; +// b = 3; +// } +// ``` +fn unwrap_tuple_destructure_assign( + acc: &mut Assists, + ctx: &AssistContext<'_>, + expr_stmt: ast::ExprStmt, + lhs: ast::TupleExpr, + rhs: ast::TupleExpr, +) -> Option<()> { + if lhs.fields().count() != rhs.fields().count() { + cov_mark::hit!(unwrap_destructure_assign_unpaired); + return None; + } + + acc.add( + AssistId::refactor_rewrite("unwrap_tuple_destructure_assign"), + "Unwrap tuple destructure assign", + expr_stmt.syntax().text_range(), + |builder| { + let mut edit = builder.make_editor(expr_stmt.syntax()); + let make = SyntaxFactory::with_mappings(); + + let indent = expr_stmt.indent_level(); + let whitespace = make.whitespace(&format!("\n{indent}")).syntax_element(); + let pairs = lhs.fields().zip(rhs.fields()).map(|(lhs, rhs)| { + let assignment = make.expr_assignment(lhs, rhs); + make.expr_stmt(assignment.into()).syntax().syntax_element() + }); + let pairs = Itertools::intersperse(pairs, whitespace); + + edit.replace_with_many(expr_stmt.syntax(), pairs.collect()); + + edit.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.vfs_file_id(), edit); + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_assist_not_applicable; + + use super::*; + + #[test] + fn tuple_not_applicable_not_on_eq_token() { + check_assist_not_applicable( + unwrap_destructure_assign, + "fn foo() { let (a, b); (a, b) = $0(1, 2); }", + ); + check_assist_not_applicable( + unwrap_destructure_assign, + "fn foo() { let (a, b); ($0a, b) = (1, 2); }", + ); + } + + #[test] + fn not_applicable_with_unpaired() { + cov_mark::check!(unwrap_destructure_assign_unpaired); + + check_assist_not_applicable( + unwrap_destructure_assign, + "fn foo() { let (a, b); (a, b, c) $0= (1, 2); }", + ); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 4682c0473238..92b6126e19ac 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -228,6 +228,7 @@ mod handlers { mod unnecessary_async; mod unqualify_method_call; mod unwrap_block; + mod unwrap_destructure_assign; mod unwrap_return_type; mod unwrap_tuple; mod unwrap_type_to_generic_arg; @@ -371,6 +372,7 @@ mod handlers { unnecessary_async::unnecessary_async, unqualify_method_call::unqualify_method_call, unwrap_block::unwrap_block, + unwrap_destructure_assign::unwrap_destructure_assign, unwrap_return_type::unwrap_return_type, unwrap_tuple::unwrap_tuple, unwrap_type_to_generic_arg::unwrap_type_to_generic_arg, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 91348be97eb7..fb814d0bcf12 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -3552,6 +3552,26 @@ fn main() { ) } +#[test] +fn doctest_unwrap_tuple_destructure_assign() { + check_doc_test( + "unwrap_tuple_destructure_assign", + r#####" +fn foo() { + let (a, b); + (a, b) $0= (2, 3); +} +"#####, + r#####" +fn foo() { + let (a, b); + a = 2; + b = 3; +} +"#####, + ) +} + #[test] fn doctest_unwrap_type_to_generic_arg() { check_doc_test(