1 use ide_db::defs::{Definition, NameRefClass};
3 ast::{self, AstNode, GenericParamsOwner, VisibilityOwner},
7 use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
9 // Assist: convert_tuple_struct_to_named_struct
11 // Converts tuple struct to struct with named fields.
14 // struct Point$0(f32, f32);
17 // pub fn new(x: f32, y: f32) -> Self {
21 // pub fn x(&self) -> f32 {
25 // pub fn y(&self) -> f32 {
32 // struct Point { field1: f32, field2: f32 }
35 // pub fn new(x: f32, y: f32) -> Self {
36 // Point { field1: x, field2: y }
39 // pub fn x(&self) -> f32 {
43 // pub fn y(&self) -> f32 {
48 pub(crate) fn convert_tuple_struct_to_named_struct(
52 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
53 let tuple_fields = match strukt.field_list()? {
54 ast::FieldList::TupleFieldList(it) => it,
55 ast::FieldList::RecordFieldList(_) => return None,
57 let strukt_def = ctx.sema.to_def(&strukt)?;
59 let target = strukt.syntax().text_range();
61 AssistId("convert_tuple_struct_to_named_struct", AssistKind::RefactorRewrite),
62 "Convert to named struct",
65 let names = generate_names(tuple_fields.fields());
66 edit_field_references(ctx, edit, tuple_fields.fields(), &names);
67 edit_struct_references(ctx, edit, strukt_def, &names);
68 edit_struct_def(ctx, edit, &strukt, tuple_fields, names);
75 edit: &mut AssistBuilder,
77 tuple_fields: ast::TupleFieldList,
78 names: Vec<ast::Name>,
80 let record_fields = tuple_fields
83 .filter_map(|(f, name)| Some(ast::make::record_field(f.visibility(), name, f.ty()?)));
84 let record_fields = ast::make::record_field_list(record_fields);
85 let tuple_fields_text_range = tuple_fields.syntax().text_range();
87 edit.edit_file(ctx.frange.file_id);
89 if let Some(w) = strukt.where_clause() {
90 edit.delete(w.syntax().text_range());
91 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text());
92 edit.insert(tuple_fields_text_range.start(), w.syntax().text());
93 edit.insert(tuple_fields_text_range.start(), ",");
94 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text());
96 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
99 edit.replace(tuple_fields_text_range, record_fields.to_string());
100 strukt.semicolon_token().map(|t| edit.delete(t.text_range()));
103 fn edit_struct_references(
105 edit: &mut AssistBuilder,
109 let strukt_def = Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(strukt)));
110 let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
112 let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> {
115 ast::TupleStructPat(tuple_struct_pat) => {
117 tuple_struct_pat.syntax().text_range(),
118 ast::make::record_pat_with_fields(
119 tuple_struct_pat.path()?,
120 ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map(
122 ast::make::record_pat_field(
123 ast::make::name_ref(&name.to_string()),
132 // for tuple struct creations like Foo(42)
133 ast::CallExpr(call_expr) => {
134 let path = call_expr.syntax().descendants().find_map(ast::PathExpr::cast).and_then(|expr| expr.path())?;
136 // this also includes method calls like Foo::new(42), we should skip them
137 if let Some(name_ref) = path.segment().and_then(|s| s.name_ref()) {
138 match NameRefClass::classify(&ctx.sema, &name_ref) {
139 Some(NameRefClass::Definition(Definition::SelfType(_))) => {},
140 Some(NameRefClass::Definition(def)) if def == strukt_def => {},
145 let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?;
148 call_expr.syntax().text_range(),
149 ast::make::record_expr(
151 ast::make::record_expr_field_list(arg_list.args().zip(names).map(
153 ast::make::record_expr_field(
154 ast::make::name_ref(&name.to_string()),
169 for (file_id, refs) in usages {
170 edit.edit_file(file_id);
172 for node in r.name.syntax().ancestors() {
173 if edit_node(edit, node).is_some() {
181 fn edit_field_references(
183 edit: &mut AssistBuilder,
184 fields: impl Iterator<Item = ast::TupleField>,
187 for (field, name) in fields.zip(names) {
188 let field = match ctx.sema.to_def(&field) {
192 let def = Definition::Field(field);
193 let usages = def.usages(&ctx.sema).all();
194 for (file_id, refs) in usages {
195 edit.edit_file(file_id);
197 if let Some(name_ref) = r.name.as_name_ref() {
198 edit.replace(name_ref.syntax().text_range(), name.text());
205 fn generate_names(fields: impl Iterator<Item = ast::TupleField>) -> Vec<ast::Name> {
206 fields.enumerate().map(|(i, _)| ast::make::name(&format!("field{}", i + 1))).collect()
211 use crate::tests::{check_assist, check_assist_not_applicable};
216 fn not_applicable_other_than_tuple_struct() {
217 check_assist_not_applicable(
218 convert_tuple_struct_to_named_struct,
219 r#"struct Foo$0 { bar: u32 };"#,
221 check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"struct Foo$0;"#);
225 fn convert_simple_struct() {
227 convert_tuple_struct_to_named_struct,
233 fn new(inner: Inner) -> A {
237 fn new_with_default() -> A {
241 fn into_inner(self) -> Inner {
247 struct A { field1: Inner }
250 fn new(inner: Inner) -> A {
254 fn new_with_default() -> A {
258 fn into_inner(self) -> Inner {
266 fn convert_struct_referenced_via_self_kw() {
268 convert_tuple_struct_to_named_struct,
274 fn new(inner: Inner) -> Self {
278 fn new_with_default() -> Self {
282 fn into_inner(self) -> Inner {
288 struct A { field1: Inner }
291 fn new(inner: Inner) -> Self {
292 Self { field1: inner }
295 fn new_with_default() -> Self {
299 fn into_inner(self) -> Inner {
307 fn convert_destructured_struct() {
309 convert_tuple_struct_to_named_struct,
315 fn into_inner(self) -> Inner {
320 fn into_inner_via_self(self) -> Inner {
321 let Self(first) = self;
327 struct A { field1: Inner }
330 fn into_inner(self) -> Inner {
331 let A { field1: first } = self;
335 fn into_inner_via_self(self) -> Inner {
336 let Self { field1: first } = self;
344 fn convert_struct_with_visibility() {
346 convert_tuple_struct_to_named_struct,
348 struct A$0(pub u32, pub(crate) u64);
355 fn into_first(self) -> u32 {
359 fn into_second(self) -> u64 {
364 struct A { pub field1: u32, pub(crate) field2: u64 }
368 A { field1: 42, field2: 42 }
371 fn into_first(self) -> u32 {
375 fn into_second(self) -> u64 {
383 fn convert_struct_with_wrapped_references() {
385 convert_tuple_struct_to_named_struct,
395 fn into_inner(self) -> u32 {
399 fn into_inner_destructed(self) -> u32 {
400 let Outer(Inner(x)) = self;
405 struct Inner { field1: u32 }
410 Self(Inner { field1: 42 })
413 fn into_inner(self) -> u32 {
417 fn into_inner_destructed(self) -> u32 {
418 let Outer(Inner { field1: x }) = self;
425 convert_tuple_struct_to_named_struct,
428 struct Outer$0(Inner);
435 fn into_inner(self) -> u32 {
439 fn into_inner_destructed(self) -> u32 {
440 let Outer(Inner(x)) = self;
446 struct Outer { field1: Inner }
450 Self { field1: Inner(42) }
453 fn into_inner(self) -> u32 {
457 fn into_inner_destructed(self) -> u32 {
458 let Outer { field1: Inner(x) } = self;
466 fn convert_struct_with_multi_file_references() {
468 convert_tuple_struct_to_named_struct,
477 use crate::{A, Inner};
485 struct A { field1: Inner }
490 use crate::{A, Inner};
492 let a = A { field1: Inner };
499 fn convert_struct_with_where_clause() {
501 convert_tuple_struct_to_named_struct,