From ca9ffba0473cb32b06c01bc5d387e538d379f19e Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Fri, 4 Jun 2021 21:48:32 -0700 Subject: [PATCH 1/3] Add assist for converting a tuple enum variant to a named variant --- .../convert_tuple_variant_to_named_variant.rs | 515 ++++++++++++++++++ crates/ide_assists/src/lib.rs | 2 + 2 files changed, 517 insertions(+) create mode 100644 crates/ide_assists/src/handlers/convert_tuple_variant_to_named_variant.rs diff --git a/crates/ide_assists/src/handlers/convert_tuple_variant_to_named_variant.rs b/crates/ide_assists/src/handlers/convert_tuple_variant_to_named_variant.rs new file mode 100644 index 0000000000..586ad9809f --- /dev/null +++ b/crates/ide_assists/src/handlers/convert_tuple_variant_to_named_variant.rs @@ -0,0 +1,515 @@ +use ide_db::defs::{Definition, NameRefClass}; +use syntax::{ + ast::{self, AstNode, VisibilityOwner}, + match_ast, SyntaxNode, +}; + +use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists}; + +// Assist: convert_tuple_variant_to_named_variant +// +// Converts tuple variant to variant with named fields. +// +// ``` +// enum A { +// Variant(usize), +// } +// +// impl A { +// fn new(value: usize) -> A { +// A::Variant(value) +// } +// +// fn new_with_default() -> A { +// A::new(Default::default()) +// } +// +// fn value(self) -> usize { +// match self { +// A::Variant(value) => value, +// } +// } +// } +// ``` +// -> +// ``` +// enum A { +// Variant { +// field1: usize +// }, +// } +// +// impl A { +// fn new(value: usize) -> A { +// A::Variant { +// field1: value, +// } +// } +// +// fn new_with_default() -> A { +// A::new(Default::default()) +// } +// +// fn value(self) -> usize { +// match self { +// A::Variant { field1: value } => value, +// } +// } +// } +// ``` +pub(crate) fn convert_tuple_variant_to_named_variant( + acc: &mut Assists, + ctx: &AssistContext, +) -> Option<()> { + let variant = ctx.find_node_at_offset::()?; + let tuple_fields = match variant.field_list()? { + ast::FieldList::TupleFieldList(it) => it, + ast::FieldList::RecordFieldList(_) => return None, + }; + let variant_def = ctx.sema.to_def(&variant)?; + + let target = variant.syntax().text_range(); + acc.add( + AssistId("convert_tuple_variant_to_named_variant", AssistKind::RefactorRewrite), + "Convert to named struct", + target, + |edit| { + let names = generate_names(tuple_fields.fields()); + edit_field_references(ctx, edit, tuple_fields.fields(), &names); // TODO: is this needed? + edit_variant_references(ctx, edit, variant_def, &names); + edit_variant_def(ctx, edit, tuple_fields, names); + }, + ) +} + +fn edit_variant_def( + ctx: &AssistContext, + edit: &mut AssistBuilder, + tuple_fields: ast::TupleFieldList, + names: Vec, +) { + let record_fields = tuple_fields + .fields() + .zip(names) + .filter_map(|(f, name)| Some(ast::make::record_field(f.visibility(), name, f.ty()?))); + let record_fields = ast::make::record_field_list(record_fields); + let tuple_fields_text_range = tuple_fields.syntax().text_range(); + + edit.edit_file(ctx.frange.file_id); + + edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text()); + + edit.replace(tuple_fields_text_range, record_fields.to_string()); +} + +fn edit_variant_references( + ctx: &AssistContext, + edit: &mut AssistBuilder, + variant: hir::Variant, + names: &[ast::Name], +) { + let variant_def = Definition::ModuleDef(hir::ModuleDef::Variant(variant)); + let usages = variant_def.usages(&ctx.sema).include_self_refs().all(); + + let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> { + match_ast! { + match node { + ast::TupleStructPat(tuple_struct_pat) => { + edit.replace( + tuple_struct_pat.syntax().text_range(), + ast::make::record_pat_with_fields( + tuple_struct_pat.path()?, + ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map( + |(pat, name)| { + ast::make::record_pat_field( + ast::make::name_ref(&name.to_string()), + pat, + ) + }, + )), + ) + .to_string(), + ); + }, + // for tuple struct creations like Foo(42) + ast::CallExpr(call_expr) => { + let path = call_expr.syntax().descendants().find_map(ast::PathExpr::cast).and_then(|expr| expr.path())?; + + // this also includes method calls like Foo::new(42), we should skip them + if let Some(name_ref) = path.segment().and_then(|s| s.name_ref()) { + match NameRefClass::classify(&ctx.sema, &name_ref) { + Some(NameRefClass::Definition(Definition::SelfType(_))) => {}, + Some(NameRefClass::Definition(def)) if def == variant_def => {}, + _ => return None, + }; + } + + let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?; + + edit.replace( + call_expr.syntax().text_range(), + ast::make::record_expr( + path, + ast::make::record_expr_field_list(arg_list.args().zip(names).map( + |(expr, name)| { + ast::make::record_expr_field( + ast::make::name_ref(&name.to_string()), + Some(expr), + ) + }, + )), + ) + .to_string(), + ); + }, + _ => return None, + } + } + Some(()) + }; + + for (file_id, refs) in usages { + edit.edit_file(file_id); + for r in refs { + for node in r.name.syntax().ancestors() { + if edit_node(edit, node).is_some() { + break; + } + } + } + } +} + +fn edit_field_references( + ctx: &AssistContext, + edit: &mut AssistBuilder, + fields: impl Iterator, + names: &[ast::Name], +) { + for (field, name) in fields.zip(names) { + let field = match ctx.sema.to_def(&field) { + Some(it) => it, + None => continue, + }; + let def = Definition::Field(field); + let usages = def.usages(&ctx.sema).all(); + for (file_id, refs) in usages { + edit.edit_file(file_id); + for r in refs { + if let Some(name_ref) = r.name.as_name_ref() { + edit.replace(name_ref.syntax().text_range(), name.text()); + } + } + } + } +} + +fn generate_names(fields: impl Iterator) -> Vec { + fields.enumerate().map(|(i, _)| ast::make::name(&format!("field{}", i + 1))).collect() +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn not_applicable_other_than_tuple_variant() { + check_assist_not_applicable( + convert_tuple_variant_to_named_variant, + r#"enum Enum { Variant$0 { value: usize } };"#, + ); + check_assist_not_applicable(convert_tuple_variant_to_named_variant, r#"enum Enum { Variant$0 }"#); + } + + #[test] + fn convert_simple_variant() { + check_assist( + convert_tuple_variant_to_named_variant, + r#" +enum A { + $0Variant(usize), +} + +impl A { + fn new(value: usize) -> A { + A::Variant(value) + } + + fn new_with_default() -> A { + A::new(Default::default()) + } + + fn value(self) -> usize { + match self { + A::Variant(value) => value, + } + } +}"#, + r#" +enum A { + Variant { field1: usize }, +} + +impl A { + fn new(value: usize) -> A { + A::Variant { field1: value } + } + + fn new_with_default() -> A { + A::new(Default::default()) + } + + fn value(self) -> usize { + match self { + A::Variant { field1: value } => value, + } + } +}"#, + ); + } + + #[test] + fn convert_variant_referenced_via_self_kw() { + check_assist( + convert_tuple_variant_to_named_variant, + r#" +enum A { + $0Variant(usize), +} + +impl A { + fn new(value: usize) -> A { + Self::Variant(value) + } + + fn new_with_default() -> A { + Self::new(Default::default()) + } + + fn value(self) -> usize { + match self { + Self::Variant(value) => value, + } + } +}"#, + r#" +enum A { + Variant { field1: usize }, +} + +impl A { + fn new(value: usize) -> A { + Self::Variant { field1: value } + } + + fn new_with_default() -> A { + Self::new(Default::default()) + } + + fn value(self) -> usize { + match self { + Self::Variant { field1: value } => value, + } + } +}"#, + ); + } + + #[test] + fn convert_destructured_variant() { + check_assist( + convert_tuple_variant_to_named_variant, + r#" +enum A { + $0Variant(usize), +} + +impl A { + fn into_inner(self) -> usize { + let A::Variant(first) = self; + first + } + + fn into_inner_via_self(self) -> usize { + let Self::Variant(first) = self; + first + } +}"#, + r#" +enum A { + Variant { field1: usize }, +} + +impl A { + fn into_inner(self) -> usize { + let A::Variant { field1: first } = self; + first + } + + fn into_inner_via_self(self) -> usize { + let Self::Variant { field1: first } = self; + first + } +}"#, + ); + } + + #[test] + fn convert_variant_with_wrapped_references() { + check_assist( + convert_tuple_variant_to_named_variant, + r#" +enum Inner { + $0Variant(usize), +} +enum Outer { + Variant(Inner), +} + +impl Outer { + fn new() -> Self { + Self::Variant(Inner::Variant(42)) + } + + fn into_inner_destructed(self) -> u32 { + let Outer::Variant(Inner::Variant(x)) = self; + x + } +}"#, + r#" +enum Inner { + Variant { field1: usize }, +} +enum Outer { + Variant(Inner), +} + +impl Outer { + fn new() -> Self { + Self::Variant(Inner::Variant { field1: 42 }) + } + + fn into_inner_destructed(self) -> u32 { + let Outer::Variant(Inner::Variant { field1: x }) = self; + x + } +}"#, + ); + + check_assist( + convert_tuple_variant_to_named_variant, + r#" +enum Inner { + Variant(usize), +} +enum Outer { + $0Variant(Inner), +} + +impl Outer { + fn new() -> Self { + Self::Variant(Inner::Variant(42)) + } + + fn into_inner_destructed(self) -> u32 { + let Outer::Variant(Inner::Variant(x)) = self; + x + } +}"#, + r#" +enum Inner { + Variant(usize), +} +enum Outer { + Variant { field1: Inner }, +} + +impl Outer { + fn new() -> Self { + Self::Variant { field1: Inner::Variant(42) } + } + + fn into_inner_destructed(self) -> u32 { + let Outer::Variant { field1: Inner::Variant(x) } = self; + x + } +}"#, + ); + } + + #[test] + fn convert_variant_with_multi_file_references() { + check_assist( + convert_tuple_variant_to_named_variant, + r#" +//- /main.rs +struct Inner; +enum A { + $0Variant(Inner), +} + +mod foo; + +//- /foo.rs +use crate::{A, Inner}; +fn f() { + let a = A::Variant(Inner); +} +"#, + r#" +//- /main.rs +struct Inner; +enum A { + Variant { field1: Inner }, +} + +mod foo; + +//- /foo.rs +use crate::{A, Inner}; +fn f() { + let a = A::Variant { field1: Inner }; +} +"#, + ); + } + + #[test] + fn convert_directly_used_variant() { + check_assist( + convert_tuple_variant_to_named_variant, + r#" +//- /main.rs +struct Inner; +enum A { + $0Variant(Inner), +} + +mod foo; + +//- /foo.rs +use crate::{A::Variant, Inner}; +fn f() { + let a = Variant(Inner); +} +"#, + r#" +//- /main.rs +struct Inner; +enum A { + Variant { field1: Inner }, +} + +mod foo; + +//- /foo.rs +use crate::{A::Variant, Inner}; +fn f() { + let a = Variant { field1: Inner }; +} +"#, + ); + } +} diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 16af72927b..d642333155 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs @@ -186,6 +186,7 @@ mod handlers { mod convert_iter_for_each_to_for; mod convert_into_to_from; mod convert_tuple_struct_to_named_struct; + mod convert_tuple_variant_to_named_variant; mod early_return; mod expand_glob_import; mod extract_function; @@ -256,6 +257,7 @@ mod handlers { convert_iter_for_each_to_for::convert_iter_for_each_to_for, convert_into_to_from::convert_into_to_from, convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct, + convert_tuple_variant_to_named_variant::convert_tuple_variant_to_named_variant, early_return::convert_to_guarded_return, expand_glob_import::expand_glob_import, extract_struct_from_enum_variant::extract_struct_from_enum_variant, From 18f796a728806e5138708a7af00d6064ec251382 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Sat, 5 Jun 2021 15:29:29 -0700 Subject: [PATCH 2/3] Refactor to be just one assist --- .../convert_tuple_struct_to_named_struct.rs | 343 +++++++++++- .../convert_tuple_variant_to_named_variant.rs | 515 ------------------ crates/ide_assists/src/lib.rs | 2 - 3 files changed, 328 insertions(+), 532 deletions(-) delete mode 100644 crates/ide_assists/src/handlers/convert_tuple_variant_to_named_variant.rs diff --git a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs index 70949ca358..53709e31fb 100644 --- a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -1,3 +1,4 @@ +use either::Either; use ide_db::defs::{Definition, NameRefClass}; use syntax::{ ast::{self, AstNode, GenericParamsOwner, VisibilityOwner}, @@ -8,7 +9,7 @@ use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, // Assist: convert_tuple_struct_to_named_struct // -// Converts tuple struct to struct with named fields. +// Converts tuple struct to struct with named fields, and analogously for tuple enum variants. // // ``` // struct Point$0(f32, f32); @@ -49,14 +50,21 @@ pub(crate) fn convert_tuple_struct_to_named_struct( acc: &mut Assists, ctx: &AssistContext, ) -> Option<()> { - let strukt = ctx.find_node_at_offset::()?; - let tuple_fields = match strukt.field_list()? { + let strukt = ctx + .find_node_at_offset::() + .map(Either::Left) + .or_else(|| ctx.find_node_at_offset::().map(Either::Right))?; + let field_list = strukt.as_ref().either(|s| s.field_list(), |v| v.field_list())?; + let tuple_fields = match field_list { ast::FieldList::TupleFieldList(it) => it, ast::FieldList::RecordFieldList(_) => return None, }; - let strukt_def = ctx.sema.to_def(&strukt)?; + let strukt_def = match &strukt { + Either::Left(s) => Either::Left(ctx.sema.to_def(s)?), + Either::Right(v) => Either::Right(ctx.sema.to_def(v)?), + }; + let target = strukt.as_ref().either(|s| s.syntax(), |v| v.syntax()).text_range(); - let target = strukt.syntax().text_range(); acc.add( AssistId("convert_tuple_struct_to_named_struct", AssistKind::RefactorRewrite), "Convert to named struct", @@ -73,7 +81,7 @@ pub(crate) fn convert_tuple_struct_to_named_struct( fn edit_struct_def( ctx: &AssistContext, edit: &mut AssistBuilder, - strukt: &ast::Struct, + strukt: &Either, tuple_fields: ast::TupleFieldList, names: Vec, ) { @@ -86,27 +94,34 @@ fn edit_struct_def( edit.edit_file(ctx.frange.file_id); - if let Some(w) = strukt.where_clause() { - edit.delete(w.syntax().text_range()); - edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text()); - edit.insert(tuple_fields_text_range.start(), w.syntax().text()); - edit.insert(tuple_fields_text_range.start(), ","); - edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text()); + if let Either::Left(strukt) = strukt { + if let Some(w) = strukt.where_clause() { + edit.delete(w.syntax().text_range()); + edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text()); + edit.insert(tuple_fields_text_range.start(), w.syntax().text()); + edit.insert(tuple_fields_text_range.start(), ","); + edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text()); + } else { + edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text()); + } + strukt.semicolon_token().map(|t| edit.delete(t.text_range())); } else { edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text()); } edit.replace(tuple_fields_text_range, record_fields.to_string()); - strukt.semicolon_token().map(|t| edit.delete(t.text_range())); } fn edit_struct_references( ctx: &AssistContext, edit: &mut AssistBuilder, - strukt: hir::Struct, + strukt: Either, names: &[ast::Name], ) { - let strukt_def = Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(strukt))); + let strukt_def = match strukt { + Either::Left(s) => Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(s))), + Either::Right(v) => Definition::ModuleDef(hir::ModuleDef::Variant(v)), + }; let usages = strukt_def.usages(&ctx.sema).include_self_refs().all(); let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> { @@ -510,6 +525,304 @@ where T: Display, { field1: T } +"#, + ); + } + #[test] + fn not_applicable_other_than_tuple_variant() { + check_assist_not_applicable( + convert_tuple_struct_to_named_struct, + r#"enum Enum { Variant$0 { value: usize } };"#, + ); + check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"enum Enum { Variant$0 }"#); + } + + #[test] + fn convert_simple_variant() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +enum A { + $0Variant(usize), +} + +impl A { + fn new(value: usize) -> A { + A::Variant(value) + } + + fn new_with_default() -> A { + A::new(Default::default()) + } + + fn value(self) -> usize { + match self { + A::Variant(value) => value, + } + } +}"#, + r#" +enum A { + Variant { field1: usize }, +} + +impl A { + fn new(value: usize) -> A { + A::Variant { field1: value } + } + + fn new_with_default() -> A { + A::new(Default::default()) + } + + fn value(self) -> usize { + match self { + A::Variant { field1: value } => value, + } + } +}"#, + ); + } + + #[test] + fn convert_variant_referenced_via_self_kw() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +enum A { + $0Variant(usize), +} + +impl A { + fn new(value: usize) -> A { + Self::Variant(value) + } + + fn new_with_default() -> A { + Self::new(Default::default()) + } + + fn value(self) -> usize { + match self { + Self::Variant(value) => value, + } + } +}"#, + r#" +enum A { + Variant { field1: usize }, +} + +impl A { + fn new(value: usize) -> A { + Self::Variant { field1: value } + } + + fn new_with_default() -> A { + Self::new(Default::default()) + } + + fn value(self) -> usize { + match self { + Self::Variant { field1: value } => value, + } + } +}"#, + ); + } + + #[test] + fn convert_destructured_variant() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +enum A { + $0Variant(usize), +} + +impl A { + fn into_inner(self) -> usize { + let A::Variant(first) = self; + first + } + + fn into_inner_via_self(self) -> usize { + let Self::Variant(first) = self; + first + } +}"#, + r#" +enum A { + Variant { field1: usize }, +} + +impl A { + fn into_inner(self) -> usize { + let A::Variant { field1: first } = self; + first + } + + fn into_inner_via_self(self) -> usize { + let Self::Variant { field1: first } = self; + first + } +}"#, + ); + } + + #[test] + fn convert_variant_with_wrapped_references() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +enum Inner { + $0Variant(usize), +} +enum Outer { + Variant(Inner), +} + +impl Outer { + fn new() -> Self { + Self::Variant(Inner::Variant(42)) + } + + fn into_inner_destructed(self) -> u32 { + let Outer::Variant(Inner::Variant(x)) = self; + x + } +}"#, + r#" +enum Inner { + Variant { field1: usize }, +} +enum Outer { + Variant(Inner), +} + +impl Outer { + fn new() -> Self { + Self::Variant(Inner::Variant { field1: 42 }) + } + + fn into_inner_destructed(self) -> u32 { + let Outer::Variant(Inner::Variant { field1: x }) = self; + x + } +}"#, + ); + + check_assist( + convert_tuple_struct_to_named_struct, + r#" +enum Inner { + Variant(usize), +} +enum Outer { + $0Variant(Inner), +} + +impl Outer { + fn new() -> Self { + Self::Variant(Inner::Variant(42)) + } + + fn into_inner_destructed(self) -> u32 { + let Outer::Variant(Inner::Variant(x)) = self; + x + } +}"#, + r#" +enum Inner { + Variant(usize), +} +enum Outer { + Variant { field1: Inner }, +} + +impl Outer { + fn new() -> Self { + Self::Variant { field1: Inner::Variant(42) } + } + + fn into_inner_destructed(self) -> u32 { + let Outer::Variant { field1: Inner::Variant(x) } = self; + x + } +}"#, + ); + } + + #[test] + fn convert_variant_with_multi_file_references() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +//- /main.rs +struct Inner; +enum A { + $0Variant(Inner), +} + +mod foo; + +//- /foo.rs +use crate::{A, Inner}; +fn f() { + let a = A::Variant(Inner); +} +"#, + r#" +//- /main.rs +struct Inner; +enum A { + Variant { field1: Inner }, +} + +mod foo; + +//- /foo.rs +use crate::{A, Inner}; +fn f() { + let a = A::Variant { field1: Inner }; +} +"#, + ); + } + + #[test] + fn convert_directly_used_variant() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +//- /main.rs +struct Inner; +enum A { + $0Variant(Inner), +} + +mod foo; + +//- /foo.rs +use crate::{A::Variant, Inner}; +fn f() { + let a = Variant(Inner); +} +"#, + r#" +//- /main.rs +struct Inner; +enum A { + Variant { field1: Inner }, +} + +mod foo; + +//- /foo.rs +use crate::{A::Variant, Inner}; +fn f() { + let a = Variant { field1: Inner }; +} "#, ); } diff --git a/crates/ide_assists/src/handlers/convert_tuple_variant_to_named_variant.rs b/crates/ide_assists/src/handlers/convert_tuple_variant_to_named_variant.rs deleted file mode 100644 index 586ad9809f..0000000000 --- a/crates/ide_assists/src/handlers/convert_tuple_variant_to_named_variant.rs +++ /dev/null @@ -1,515 +0,0 @@ -use ide_db::defs::{Definition, NameRefClass}; -use syntax::{ - ast::{self, AstNode, VisibilityOwner}, - match_ast, SyntaxNode, -}; - -use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists}; - -// Assist: convert_tuple_variant_to_named_variant -// -// Converts tuple variant to variant with named fields. -// -// ``` -// enum A { -// Variant(usize), -// } -// -// impl A { -// fn new(value: usize) -> A { -// A::Variant(value) -// } -// -// fn new_with_default() -> A { -// A::new(Default::default()) -// } -// -// fn value(self) -> usize { -// match self { -// A::Variant(value) => value, -// } -// } -// } -// ``` -// -> -// ``` -// enum A { -// Variant { -// field1: usize -// }, -// } -// -// impl A { -// fn new(value: usize) -> A { -// A::Variant { -// field1: value, -// } -// } -// -// fn new_with_default() -> A { -// A::new(Default::default()) -// } -// -// fn value(self) -> usize { -// match self { -// A::Variant { field1: value } => value, -// } -// } -// } -// ``` -pub(crate) fn convert_tuple_variant_to_named_variant( - acc: &mut Assists, - ctx: &AssistContext, -) -> Option<()> { - let variant = ctx.find_node_at_offset::()?; - let tuple_fields = match variant.field_list()? { - ast::FieldList::TupleFieldList(it) => it, - ast::FieldList::RecordFieldList(_) => return None, - }; - let variant_def = ctx.sema.to_def(&variant)?; - - let target = variant.syntax().text_range(); - acc.add( - AssistId("convert_tuple_variant_to_named_variant", AssistKind::RefactorRewrite), - "Convert to named struct", - target, - |edit| { - let names = generate_names(tuple_fields.fields()); - edit_field_references(ctx, edit, tuple_fields.fields(), &names); // TODO: is this needed? - edit_variant_references(ctx, edit, variant_def, &names); - edit_variant_def(ctx, edit, tuple_fields, names); - }, - ) -} - -fn edit_variant_def( - ctx: &AssistContext, - edit: &mut AssistBuilder, - tuple_fields: ast::TupleFieldList, - names: Vec, -) { - let record_fields = tuple_fields - .fields() - .zip(names) - .filter_map(|(f, name)| Some(ast::make::record_field(f.visibility(), name, f.ty()?))); - let record_fields = ast::make::record_field_list(record_fields); - let tuple_fields_text_range = tuple_fields.syntax().text_range(); - - edit.edit_file(ctx.frange.file_id); - - edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text()); - - edit.replace(tuple_fields_text_range, record_fields.to_string()); -} - -fn edit_variant_references( - ctx: &AssistContext, - edit: &mut AssistBuilder, - variant: hir::Variant, - names: &[ast::Name], -) { - let variant_def = Definition::ModuleDef(hir::ModuleDef::Variant(variant)); - let usages = variant_def.usages(&ctx.sema).include_self_refs().all(); - - let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> { - match_ast! { - match node { - ast::TupleStructPat(tuple_struct_pat) => { - edit.replace( - tuple_struct_pat.syntax().text_range(), - ast::make::record_pat_with_fields( - tuple_struct_pat.path()?, - ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map( - |(pat, name)| { - ast::make::record_pat_field( - ast::make::name_ref(&name.to_string()), - pat, - ) - }, - )), - ) - .to_string(), - ); - }, - // for tuple struct creations like Foo(42) - ast::CallExpr(call_expr) => { - let path = call_expr.syntax().descendants().find_map(ast::PathExpr::cast).and_then(|expr| expr.path())?; - - // this also includes method calls like Foo::new(42), we should skip them - if let Some(name_ref) = path.segment().and_then(|s| s.name_ref()) { - match NameRefClass::classify(&ctx.sema, &name_ref) { - Some(NameRefClass::Definition(Definition::SelfType(_))) => {}, - Some(NameRefClass::Definition(def)) if def == variant_def => {}, - _ => return None, - }; - } - - let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?; - - edit.replace( - call_expr.syntax().text_range(), - ast::make::record_expr( - path, - ast::make::record_expr_field_list(arg_list.args().zip(names).map( - |(expr, name)| { - ast::make::record_expr_field( - ast::make::name_ref(&name.to_string()), - Some(expr), - ) - }, - )), - ) - .to_string(), - ); - }, - _ => return None, - } - } - Some(()) - }; - - for (file_id, refs) in usages { - edit.edit_file(file_id); - for r in refs { - for node in r.name.syntax().ancestors() { - if edit_node(edit, node).is_some() { - break; - } - } - } - } -} - -fn edit_field_references( - ctx: &AssistContext, - edit: &mut AssistBuilder, - fields: impl Iterator, - names: &[ast::Name], -) { - for (field, name) in fields.zip(names) { - let field = match ctx.sema.to_def(&field) { - Some(it) => it, - None => continue, - }; - let def = Definition::Field(field); - let usages = def.usages(&ctx.sema).all(); - for (file_id, refs) in usages { - edit.edit_file(file_id); - for r in refs { - if let Some(name_ref) = r.name.as_name_ref() { - edit.replace(name_ref.syntax().text_range(), name.text()); - } - } - } - } -} - -fn generate_names(fields: impl Iterator) -> Vec { - fields.enumerate().map(|(i, _)| ast::make::name(&format!("field{}", i + 1))).collect() -} - -#[cfg(test)] -mod tests { - use crate::tests::{check_assist, check_assist_not_applicable}; - - use super::*; - - #[test] - fn not_applicable_other_than_tuple_variant() { - check_assist_not_applicable( - convert_tuple_variant_to_named_variant, - r#"enum Enum { Variant$0 { value: usize } };"#, - ); - check_assist_not_applicable(convert_tuple_variant_to_named_variant, r#"enum Enum { Variant$0 }"#); - } - - #[test] - fn convert_simple_variant() { - check_assist( - convert_tuple_variant_to_named_variant, - r#" -enum A { - $0Variant(usize), -} - -impl A { - fn new(value: usize) -> A { - A::Variant(value) - } - - fn new_with_default() -> A { - A::new(Default::default()) - } - - fn value(self) -> usize { - match self { - A::Variant(value) => value, - } - } -}"#, - r#" -enum A { - Variant { field1: usize }, -} - -impl A { - fn new(value: usize) -> A { - A::Variant { field1: value } - } - - fn new_with_default() -> A { - A::new(Default::default()) - } - - fn value(self) -> usize { - match self { - A::Variant { field1: value } => value, - } - } -}"#, - ); - } - - #[test] - fn convert_variant_referenced_via_self_kw() { - check_assist( - convert_tuple_variant_to_named_variant, - r#" -enum A { - $0Variant(usize), -} - -impl A { - fn new(value: usize) -> A { - Self::Variant(value) - } - - fn new_with_default() -> A { - Self::new(Default::default()) - } - - fn value(self) -> usize { - match self { - Self::Variant(value) => value, - } - } -}"#, - r#" -enum A { - Variant { field1: usize }, -} - -impl A { - fn new(value: usize) -> A { - Self::Variant { field1: value } - } - - fn new_with_default() -> A { - Self::new(Default::default()) - } - - fn value(self) -> usize { - match self { - Self::Variant { field1: value } => value, - } - } -}"#, - ); - } - - #[test] - fn convert_destructured_variant() { - check_assist( - convert_tuple_variant_to_named_variant, - r#" -enum A { - $0Variant(usize), -} - -impl A { - fn into_inner(self) -> usize { - let A::Variant(first) = self; - first - } - - fn into_inner_via_self(self) -> usize { - let Self::Variant(first) = self; - first - } -}"#, - r#" -enum A { - Variant { field1: usize }, -} - -impl A { - fn into_inner(self) -> usize { - let A::Variant { field1: first } = self; - first - } - - fn into_inner_via_self(self) -> usize { - let Self::Variant { field1: first } = self; - first - } -}"#, - ); - } - - #[test] - fn convert_variant_with_wrapped_references() { - check_assist( - convert_tuple_variant_to_named_variant, - r#" -enum Inner { - $0Variant(usize), -} -enum Outer { - Variant(Inner), -} - -impl Outer { - fn new() -> Self { - Self::Variant(Inner::Variant(42)) - } - - fn into_inner_destructed(self) -> u32 { - let Outer::Variant(Inner::Variant(x)) = self; - x - } -}"#, - r#" -enum Inner { - Variant { field1: usize }, -} -enum Outer { - Variant(Inner), -} - -impl Outer { - fn new() -> Self { - Self::Variant(Inner::Variant { field1: 42 }) - } - - fn into_inner_destructed(self) -> u32 { - let Outer::Variant(Inner::Variant { field1: x }) = self; - x - } -}"#, - ); - - check_assist( - convert_tuple_variant_to_named_variant, - r#" -enum Inner { - Variant(usize), -} -enum Outer { - $0Variant(Inner), -} - -impl Outer { - fn new() -> Self { - Self::Variant(Inner::Variant(42)) - } - - fn into_inner_destructed(self) -> u32 { - let Outer::Variant(Inner::Variant(x)) = self; - x - } -}"#, - r#" -enum Inner { - Variant(usize), -} -enum Outer { - Variant { field1: Inner }, -} - -impl Outer { - fn new() -> Self { - Self::Variant { field1: Inner::Variant(42) } - } - - fn into_inner_destructed(self) -> u32 { - let Outer::Variant { field1: Inner::Variant(x) } = self; - x - } -}"#, - ); - } - - #[test] - fn convert_variant_with_multi_file_references() { - check_assist( - convert_tuple_variant_to_named_variant, - r#" -//- /main.rs -struct Inner; -enum A { - $0Variant(Inner), -} - -mod foo; - -//- /foo.rs -use crate::{A, Inner}; -fn f() { - let a = A::Variant(Inner); -} -"#, - r#" -//- /main.rs -struct Inner; -enum A { - Variant { field1: Inner }, -} - -mod foo; - -//- /foo.rs -use crate::{A, Inner}; -fn f() { - let a = A::Variant { field1: Inner }; -} -"#, - ); - } - - #[test] - fn convert_directly_used_variant() { - check_assist( - convert_tuple_variant_to_named_variant, - r#" -//- /main.rs -struct Inner; -enum A { - $0Variant(Inner), -} - -mod foo; - -//- /foo.rs -use crate::{A::Variant, Inner}; -fn f() { - let a = Variant(Inner); -} -"#, - r#" -//- /main.rs -struct Inner; -enum A { - Variant { field1: Inner }, -} - -mod foo; - -//- /foo.rs -use crate::{A::Variant, Inner}; -fn f() { - let a = Variant { field1: Inner }; -} -"#, - ); - } -} diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index d642333155..16af72927b 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs @@ -186,7 +186,6 @@ mod handlers { mod convert_iter_for_each_to_for; mod convert_into_to_from; mod convert_tuple_struct_to_named_struct; - mod convert_tuple_variant_to_named_variant; mod early_return; mod expand_glob_import; mod extract_function; @@ -257,7 +256,6 @@ mod handlers { convert_iter_for_each_to_for::convert_iter_for_each_to_for, convert_into_to_from::convert_into_to_from, convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct, - convert_tuple_variant_to_named_variant::convert_tuple_variant_to_named_variant, early_return::convert_to_guarded_return, expand_glob_import::expand_glob_import, extract_struct_from_enum_variant::extract_struct_from_enum_variant, From 9b74dd0c04651eb22e743c6ffc91c11bb3df87d8 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Sat, 5 Jun 2021 17:09:56 -0700 Subject: [PATCH 3/3] fmt --- .../convert_tuple_struct_to_named_struct.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs index 53709e31fb..fc5a17f052 100644 --- a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -97,10 +97,16 @@ fn edit_struct_def( if let Either::Left(strukt) = strukt { if let Some(w) = strukt.where_clause() { edit.delete(w.syntax().text_range()); - edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text()); + edit.insert( + tuple_fields_text_range.start(), + ast::make::tokens::single_newline().text(), + ); edit.insert(tuple_fields_text_range.start(), w.syntax().text()); edit.insert(tuple_fields_text_range.start(), ","); - edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text()); + edit.insert( + tuple_fields_text_range.start(), + ast::make::tokens::single_newline().text(), + ); } else { edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text()); } @@ -534,7 +540,10 @@ where convert_tuple_struct_to_named_struct, r#"enum Enum { Variant$0 { value: usize } };"#, ); - check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"enum Enum { Variant$0 }"#); + check_assist_not_applicable( + convert_tuple_struct_to_named_struct, + r#"enum Enum { Variant$0 }"#, + ); } #[test]