From ed0cf1c5faa5818dfc4dd4d0b61f50209fd5f280 Mon Sep 17 00:00:00 2001 From: harudagondi Date: Sat, 17 Sep 2022 15:57:45 +0800 Subject: [PATCH 1/2] Add functionality to unwrap tuple declarations --- .../ide-assists/src/handlers/unwrap_tuple.rs | 160 ++++++++++++++++++ crates/ide-assists/src/lib.rs | 2 + crates/ide-assists/src/tests/generated.rs | 19 +++ 3 files changed, 181 insertions(+) create mode 100644 crates/ide-assists/src/handlers/unwrap_tuple.rs diff --git a/crates/ide-assists/src/handlers/unwrap_tuple.rs b/crates/ide-assists/src/handlers/unwrap_tuple.rs new file mode 100644 index 00000000000..171e9214a4e --- /dev/null +++ b/crates/ide-assists/src/handlers/unwrap_tuple.rs @@ -0,0 +1,160 @@ +use syntax::{ + ast::{self, edit::AstNodeEdit}, + AstNode, T, +}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: unwrap_tuple +// +// Unwrap the tuple to different variables. +// +// ``` +// # //- minicore: result +// fn main() { +// $0let (foo, bar) = ("Foo", "Bar"); +// } +// ``` +// -> +// ``` +// fn main() { +// let foo = "Foo"; +// let bar = "Bar"; +// } +// ``` +pub(crate) fn unwrap_tuple(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let let_kw = ctx.find_token_syntax_at_offset(T![let])?; + let let_stmt = let_kw.parent().and_then(ast::LetStmt::cast)?; + let indent_level = let_stmt.indent_level().0 as usize; + let pat = let_stmt.pat()?; + let ty = let_stmt.ty(); + let init = let_stmt.initializer()?; + + // This only applies for tuple patterns, types, and initializers. + let tuple_pat = match pat { + ast::Pat::TuplePat(pat) => pat, + _ => return None, + }; + let tuple_ty = ty.and_then(|it| match it { + ast::Type::TupleType(ty) => Some(ty), + _ => None, + }); + let tuple_init = match init { + ast::Expr::TupleExpr(expr) => expr, + _ => return None, + }; + + stdx::always!( + tuple_pat.fields().count() == tuple_init.fields().count(), + "Length of tuples in pattern and initializer do not match" + ); + + let parent = let_kw.parent()?; + + acc.add( + AssistId("unwrap_tuple", AssistKind::RefactorRewrite), + "Unwrap tuple", + let_kw.text_range(), + |edit| { + let indents = " ".repeat(indent_level); + + // If there is an ascribed type, insert that type for each declaration, + // otherwise, omit that type. + if let Some(tys) = tuple_ty { + stdx::always!( + tuple_pat.fields().count() == tys.fields().count(), + "Length of tuples in patterns and type do not match" + ); + + let mut zipped_decls = String::new(); + for (pat, ty, expr) in + itertools::izip!(tuple_pat.fields(), tys.fields(), tuple_init.fields()) + { + zipped_decls.push_str(&format!("{}let {pat}: {ty} = {expr};\n", indents)) + } + edit.replace(parent.text_range(), zipped_decls.trim()); + } else { + let mut zipped_decls = String::new(); + for (pat, expr) in itertools::izip!(tuple_pat.fields(), tuple_init.fields()) { + zipped_decls.push_str(&format!("{}let {pat} = {expr};\n", indents)); + } + edit.replace(parent.text_range(), zipped_decls.trim()); + } + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_assist; + + use super::*; + + #[test] + fn unwrap_tuples() { + check_assist( + unwrap_tuple, + r#" +fn main() { + $0let (foo, bar) = ("Foo", "Bar"); +} +"#, + r#" +fn main() { + let foo = "Foo"; + let bar = "Bar"; +} +"#, + ); + + check_assist( + unwrap_tuple, + r#" +fn main() { + $0let (foo, bar, baz) = ("Foo", "Bar", "Baz"); +} +"#, + r#" +fn main() { + let foo = "Foo"; + let bar = "Bar"; + let baz = "Baz"; +} +"#, + ); + } + + #[test] + fn unwrap_tuple_with_types() { + check_assist( + unwrap_tuple, + r#" +fn main() { + $0let (foo, bar): (u8, i32) = (5, 10); +} +"#, + r#" +fn main() { + let foo: u8 = 5; + let bar: i32 = 10; +} +"#, + ); + + check_assist( + unwrap_tuple, + r#" +fn main() { + $0let (foo, bar, baz): (u8, i32, f64) = (5, 10, 17.5); +} +"#, + r#" +fn main() { + let foo: u8 = 5; + let bar: i32 = 10; + let baz: f64 = 17.5; +} +"#, + ); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 812d22efbd7..82bcc3dfa5d 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -189,6 +189,7 @@ mod handlers { mod replace_turbofish_with_explicit_type; mod split_import; mod unmerge_match_arm; + mod unwrap_tuple; mod sort_items; mod toggle_ignore; mod unmerge_use; @@ -291,6 +292,7 @@ mod handlers { unnecessary_async::unnecessary_async, unwrap_block::unwrap_block, unwrap_result_return_type::unwrap_result_return_type, + unwrap_tuple::unwrap_tuple, wrap_return_type_in_result::wrap_return_type_in_result, // These are manually sorted for better priorities. By default, // priority is determined by the size of the target range (smaller diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 3a696635afd..d403f86c6d8 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -2386,6 +2386,25 @@ fn foo() -> i32 { 42i32 } ) } +#[test] +fn doctest_unwrap_tuple() { + check_doc_test( + "unwrap_tuple", + r#####" +//- minicore: result +fn main() { + $0let (foo, bar) = ("Foo", "Bar"); +} +"#####, + r#####" +fn main() { + let foo = "Foo"; + let bar = "Bar"; +} +"#####, + ) +} + #[test] fn doctest_wrap_return_type_in_result() { check_doc_test( From c2dc32c48e8a7027390738b63e88fe220e4e56d9 Mon Sep 17 00:00:00 2001 From: harudagondi Date: Wed, 21 Sep 2022 09:11:02 +0800 Subject: [PATCH 2/2] return None instead of assert --- crates/ide-assists/src/handlers/unwrap_tuple.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/ide-assists/src/handlers/unwrap_tuple.rs b/crates/ide-assists/src/handlers/unwrap_tuple.rs index 171e9214a4e..25c58d086e9 100644 --- a/crates/ide-assists/src/handlers/unwrap_tuple.rs +++ b/crates/ide-assists/src/handlers/unwrap_tuple.rs @@ -44,10 +44,14 @@ pub(crate) fn unwrap_tuple(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option _ => return None, }; - stdx::always!( - tuple_pat.fields().count() == tuple_init.fields().count(), - "Length of tuples in pattern and initializer do not match" - ); + if tuple_pat.fields().count() != tuple_init.fields().count() { + return None; + } + if let Some(tys) = &tuple_ty { + if tuple_pat.fields().count() != tys.fields().count() { + return None; + } + } let parent = let_kw.parent()?; @@ -61,11 +65,6 @@ pub(crate) fn unwrap_tuple(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option // If there is an ascribed type, insert that type for each declaration, // otherwise, omit that type. if let Some(tys) = tuple_ty { - stdx::always!( - tuple_pat.fields().count() == tys.fields().count(), - "Length of tuples in patterns and type do not match" - ); - let mut zipped_decls = String::new(); for (pat, ty, expr) in itertools::izip!(tuple_pat.fields(), tys.fields(), tuple_init.fields())