-use hir::{Adt, ModuleDef};
+use either::Either;
use ide_db::defs::{Definition, NameRefClass};
use syntax::{
- ast::{self, AstNode, GenericParamsOwner, VisibilityOwner},
- match_ast,
+ ast::{self, AstNode, HasGenericParams, HasVisibility},
+ match_ast, SyntaxNode,
};
use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
// 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);
acc: &mut Assists,
ctx: &AssistContext,
) -> Option<()> {
- let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
- let tuple_fields = match strukt.field_list()? {
+ let strukt = ctx
+ .find_node_at_offset::<ast::Struct>()
+ .map(Either::Left)
+ .or_else(|| ctx.find_node_at_offset::<ast::Variant>().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 = 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",
|edit| {
let names = generate_names(tuple_fields.fields());
edit_field_references(ctx, edit, tuple_fields.fields(), &names);
- edit_struct_references(ctx, edit, &strukt, &names);
+ edit_struct_references(ctx, edit, strukt_def, &names);
edit_struct_def(ctx, edit, &strukt, tuple_fields, names);
},
)
fn edit_struct_def(
ctx: &AssistContext,
edit: &mut AssistBuilder,
- strukt: &ast::Struct,
+ strukt: &Either<ast::Struct, ast::Variant>,
tuple_fields: ast::TupleFieldList,
names: Vec<ast::Name>,
) {
let record_fields = tuple_fields
.fields()
.zip(names)
- .map(|(f, name)| ast::make::record_field(f.visibility(), name, f.ty().unwrap()));
+ .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);
-
- 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());
+ edit.edit_file(ctx.file_id());
+
+ 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());
+ }
+ if let Some(t) = strukt.semicolon_token() {
+ 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: &ast::Struct,
+ strukt: Either<hir::Struct, hir::Variant>,
names: &[ast::Name],
) {
- let strukt = ctx.sema.to_def(strukt).unwrap();
- let strukt_def = Definition::ModuleDef(ModuleDef::Adt(Adt::Struct(strukt)));
- let usages = strukt_def.usages(&ctx.sema).include_self_kw_refs(true).all();
+ let strukt_def = match strukt {
+ Either::Left(s) => Definition::Adt(hir::Adt::Struct(s)),
+ Either::Right(v) => Definition::Variant(v),
+ };
+ let usages = strukt_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 == strukt_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() {
- 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().unwrap(),
- 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).unwrap().path().unwrap();
-
- // this also includes method calls like Foo::new(42), we should skip them
- if let Some(Some(name_ref)) = path.segment().map(|s| s.name_ref()) {
- match NameRefClass::classify(&ctx.sema, &name_ref) {
- Some(NameRefClass::Definition(Definition::SelfType(_))) => {},
- Some(NameRefClass::Definition(def)) if def == strukt_def => {},
- _ => continue,
- };
- }
-
- let arg_list =
- call_expr.syntax().descendants().find_map(ast::ArgList::cast).unwrap();
-
- 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(),
- );
- },
- _ => ()
- }
+ if edit_node(edit, node).is_some() {
+ break;
}
}
}
);
}
+ #[test]
+ fn convert_struct_with_wrapped_references() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+struct Inner$0(u32);
+struct Outer(Inner);
+
+impl Outer {
+ fn new() -> Self {
+ Self(Inner(42))
+ }
+
+ fn into_inner(self) -> u32 {
+ (self.0).0
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer(Inner(x)) = self;
+ x
+ }
+}"#,
+ r#"
+struct Inner { field1: u32 }
+struct Outer(Inner);
+
+impl Outer {
+ fn new() -> Self {
+ Self(Inner { field1: 42 })
+ }
+
+ fn into_inner(self) -> u32 {
+ (self.0).field1
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer(Inner { field1: x }) = self;
+ x
+ }
+}"#,
+ );
+
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+struct Inner(u32);
+struct Outer$0(Inner);
+
+impl Outer {
+ fn new() -> Self {
+ Self(Inner(42))
+ }
+
+ fn into_inner(self) -> u32 {
+ (self.0).0
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer(Inner(x)) = self;
+ x
+ }
+}"#,
+ r#"
+struct Inner(u32);
+struct Outer { field1: Inner }
+
+impl Outer {
+ fn new() -> Self {
+ Self { field1: Inner(42) }
+ }
+
+ fn into_inner(self) -> u32 {
+ (self.field1).0
+ }
+
+ fn into_inner_destructed(self) -> u32 {
+ let Outer { field1: Inner(x) } = self;
+ x
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn convert_struct_with_multi_file_references() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+//- /main.rs
+struct Inner;
+struct A$0(Inner);
+
+mod foo;
+
+//- /foo.rs
+use crate::{A, Inner};
+fn f() {
+ let a = A(Inner);
+}
+"#,
+ r#"
+//- /main.rs
+struct Inner;
+struct A { field1: Inner }
+
+mod foo;
+
+//- /foo.rs
+use crate::{A, Inner};
+fn f() {
+ let a = A { field1: Inner };
+}
+"#,
+ );
+ }
+
#[test]
fn convert_struct_with_where_clause() {
check_assist(
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 };
+}
"#,
);
}