1 use hir::{Adt, ModuleDef};
2 use ide_db::defs::{Definition, NameRefClass};
4 ast::{self, AstNode, GenericParamsOwner, VisibilityOwner},
8 use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
10 // Assist: convert_tuple_struct_to_named_struct
12 // Converts tuple struct to struct with named fields.
21 // struct A { field1: Inner }
23 pub(crate) fn convert_tuple_struct_to_named_struct(
27 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
28 let tuple_fields = match strukt.field_list()? {
29 ast::FieldList::TupleFieldList(it) => it,
30 ast::FieldList::RecordFieldList(_) => return None,
33 let target = strukt.syntax().text_range();
35 AssistId("convert_tuple_struct_to_named_struct", AssistKind::RefactorRewrite),
36 "Convert to named struct",
39 let names = generate_names(tuple_fields.fields());
40 edit_field_references(ctx, edit, tuple_fields.fields(), &names);
41 edit_struct_references(ctx, edit, &strukt, &names);
42 edit_struct_def(ctx, edit, &strukt, tuple_fields, names);
49 edit: &mut AssistBuilder,
51 tuple_fields: ast::TupleFieldList,
52 names: Vec<ast::Name>,
54 let record_fields = tuple_fields
57 .map(|(f, name)| ast::make::record_field(f.visibility(), name, f.ty().unwrap()));
58 let record_fields = ast::make::record_field_list(record_fields);
59 let tuple_fields_text_range = tuple_fields.syntax().text_range();
61 edit.edit_file(ctx.frange.file_id);
63 if let Some(w) = strukt.where_clause() {
64 edit.delete(w.syntax().text_range());
65 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text());
66 edit.insert(tuple_fields_text_range.start(), w.syntax().text());
67 edit.insert(tuple_fields_text_range.start(), ",");
68 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text());
70 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
73 edit.replace(tuple_fields_text_range, record_fields.to_string());
74 strukt.semicolon_token().map(|t| edit.delete(t.text_range()));
77 fn edit_struct_references(
79 edit: &mut AssistBuilder,
83 let strukt = ctx.sema.to_def(strukt).unwrap();
84 let strukt_def = Definition::ModuleDef(ModuleDef::Adt(Adt::Struct(strukt)));
85 let usages = strukt_def.usages(&ctx.sema).include_self_kw_refs(true).all();
87 for (file_id, refs) in usages {
88 edit.edit_file(file_id);
90 for node in r.name.syntax().ancestors() {
93 ast::TupleStructPat(tuple_struct_pat) => {
95 tuple_struct_pat.syntax().text_range(),
96 ast::make::record_pat_with_fields(
97 tuple_struct_pat.path().unwrap(),
98 ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map(
100 ast::make::record_pat_field(
101 ast::make::name_ref(&name.to_string()),
110 // for tuple struct creations like Foo(42)
111 ast::CallExpr(call_expr) => {
112 let path = call_expr.syntax().descendants().find_map(ast::PathExpr::cast).unwrap().path().unwrap();
114 // this also includes method calls like Foo::new(42), we should skip them
115 if let Some(Some(name_ref)) = path.segment().map(|s| s.name_ref()) {
116 match NameRefClass::classify(&ctx.sema, &name_ref) {
117 Some(NameRefClass::Definition(Definition::SelfType(_))) => {},
118 Some(NameRefClass::Definition(def)) if def == strukt_def => {},
124 call_expr.syntax().descendants().find_map(ast::ArgList::cast).unwrap();
127 call_expr.syntax().text_range(),
128 ast::make::record_expr(
130 ast::make::record_expr_field_list(arg_list.args().zip(names).map(
132 ast::make::record_expr_field(
133 ast::make::name_ref(&name.to_string()),
150 fn edit_field_references(
152 edit: &mut AssistBuilder,
153 fields: impl Iterator<Item = ast::TupleField>,
156 for (field, name) in fields.zip(names) {
157 let field = match ctx.sema.to_def(&field) {
161 let def = Definition::Field(field);
162 let usages = def.usages(&ctx.sema).all();
163 for (file_id, refs) in usages {
164 edit.edit_file(file_id);
166 if let Some(name_ref) = r.name.as_name_ref() {
167 edit.replace(name_ref.syntax().text_range(), name.text());
174 fn generate_names(fields: impl Iterator<Item = ast::TupleField>) -> Vec<ast::Name> {
175 fields.enumerate().map(|(i, _)| ast::make::name(&format!("field{}", i + 1))).collect()
180 use crate::tests::{check_assist, check_assist_not_applicable};
185 fn not_applicable_other_than_tuple_struct() {
186 check_assist_not_applicable(
187 convert_tuple_struct_to_named_struct,
188 r#"struct Foo$0 { bar: u32 };"#,
190 check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"struct Foo$0;"#);
194 fn convert_simple_struct() {
196 convert_tuple_struct_to_named_struct,
202 fn new(inner: Inner) -> A {
206 fn new_with_default() -> A {
210 fn into_inner(self) -> Inner {
216 struct A { field1: Inner }
219 fn new(inner: Inner) -> A {
223 fn new_with_default() -> A {
227 fn into_inner(self) -> Inner {
235 fn convert_struct_referenced_via_self_kw() {
237 convert_tuple_struct_to_named_struct,
243 fn new(inner: Inner) -> Self {
247 fn new_with_default() -> Self {
251 fn into_inner(self) -> Inner {
257 struct A { field1: Inner }
260 fn new(inner: Inner) -> Self {
261 Self { field1: inner }
264 fn new_with_default() -> Self {
268 fn into_inner(self) -> Inner {
276 fn convert_destructured_struct() {
278 convert_tuple_struct_to_named_struct,
284 fn into_inner(self) -> Inner {
289 fn into_inner_via_self(self) -> Inner {
290 let Self(first) = self;
296 struct A { field1: Inner }
299 fn into_inner(self) -> Inner {
300 let A { field1: first } = self;
304 fn into_inner_via_self(self) -> Inner {
305 let Self { field1: first } = self;
313 fn convert_struct_with_visibility() {
315 convert_tuple_struct_to_named_struct,
317 struct A$0(pub u32, pub(crate) u64);
324 fn into_first(self) -> u32 {
328 fn into_second(self) -> u64 {
333 struct A { pub field1: u32, pub(crate) field2: u64 }
337 A { field1: 42, field2: 42 }
340 fn into_first(self) -> u32 {
344 fn into_second(self) -> u64 {
352 fn convert_struct_with_where_clause() {
354 convert_tuple_struct_to_named_struct,