]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
Fix incorrectly replacing method calls in "Convert to named struct" assist
[rust.git] / crates / ide_assists / src / handlers / convert_tuple_struct_to_named_struct.rs
1 use hir::{Adt, ModuleDef};
2 use ide_db::defs::{Definition, NameRefClass};
3 use syntax::{
4     ast::{self, AstNode, GenericParamsOwner, VisibilityOwner},
5     match_ast,
6 };
7
8 use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
9
10 // Assist: convert_tuple_struct_to_named_struct
11 //
12 // Converts tuple struct to struct with named fields.
13 //
14 // ```
15 // struct Inner;
16 // struct A$0(Inner);
17 // ```
18 // ->
19 // ```
20 // struct Inner;
21 // struct A { field1: Inner }
22 // ```
23 pub(crate) fn convert_tuple_struct_to_named_struct(
24     acc: &mut Assists,
25     ctx: &AssistContext,
26 ) -> Option<()> {
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,
31     };
32
33     let target = strukt.syntax().text_range();
34     acc.add(
35         AssistId("convert_tuple_struct_to_named_struct", AssistKind::RefactorRewrite),
36         "Convert to named struct",
37         target,
38         |edit| {
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);
43         },
44     )
45 }
46
47 fn edit_struct_def(
48     ctx: &AssistContext,
49     edit: &mut AssistBuilder,
50     strukt: &ast::Struct,
51     tuple_fields: ast::TupleFieldList,
52     names: Vec<ast::Name>,
53 ) {
54     let record_fields = tuple_fields
55         .fields()
56         .zip(names)
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();
60
61     edit.edit_file(ctx.frange.file_id);
62
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());
69     } else {
70         edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
71     }
72
73     edit.replace(tuple_fields_text_range, record_fields.to_string());
74     strukt.semicolon_token().map(|t| edit.delete(t.text_range()));
75 }
76
77 fn edit_struct_references(
78     ctx: &AssistContext,
79     edit: &mut AssistBuilder,
80     strukt: &ast::Struct,
81     names: &[ast::Name],
82 ) {
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();
86
87     for (file_id, refs) in usages {
88         edit.edit_file(file_id);
89         for r in refs {
90             for node in r.name.syntax().ancestors() {
91                 match_ast! {
92                     match node {
93                         ast::TupleStructPat(tuple_struct_pat) => {
94                             edit.replace(
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(
99                                         |(pat, name)| {
100                                             ast::make::record_pat_field(
101                                                 ast::make::name_ref(&name.to_string()),
102                                                 pat,
103                                             )
104                                         },
105                                     )),
106                                 )
107                                 .to_string(),
108                             );
109                         },
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();
113
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 => {},
119                                     _ => continue,
120                                 };
121                             }
122
123                             let arg_list =
124                                 call_expr.syntax().descendants().find_map(ast::ArgList::cast).unwrap();
125
126                             edit.replace(
127                                 call_expr.syntax().text_range(),
128                                 ast::make::record_expr(
129                                     path,
130                                     ast::make::record_expr_field_list(arg_list.args().zip(names).map(
131                                         |(expr, name)| {
132                                             ast::make::record_expr_field(
133                                                 ast::make::name_ref(&name.to_string()),
134                                                 Some(expr),
135                                             )
136                                         },
137                                     )),
138                                 )
139                                 .to_string(),
140                             );
141                         },
142                         _ => ()
143                     }
144                 }
145             }
146         }
147     }
148 }
149
150 fn edit_field_references(
151     ctx: &AssistContext,
152     edit: &mut AssistBuilder,
153     fields: impl Iterator<Item = ast::TupleField>,
154     names: &[ast::Name],
155 ) {
156     for (field, name) in fields.zip(names) {
157         let field = match ctx.sema.to_def(&field) {
158             Some(it) => it,
159             None => continue,
160         };
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);
165             for r in refs {
166                 if let Some(name_ref) = r.name.as_name_ref() {
167                     edit.replace(name_ref.syntax().text_range(), name.text());
168                 }
169             }
170         }
171     }
172 }
173
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()
176 }
177
178 #[cfg(test)]
179 mod tests {
180     use crate::tests::{check_assist, check_assist_not_applicable};
181
182     use super::*;
183
184     #[test]
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 };"#,
189         );
190         check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"struct Foo$0;"#);
191     }
192
193     #[test]
194     fn convert_simple_struct() {
195         check_assist(
196             convert_tuple_struct_to_named_struct,
197             r#"
198 struct Inner;
199 struct A$0(Inner);
200
201 impl A {
202     fn new(inner: Inner) -> A {
203         A(inner)
204     }
205
206     fn new_with_default() -> A {
207         A::new(Inner)
208     }
209
210     fn into_inner(self) -> Inner {
211         self.0
212     }
213 }"#,
214             r#"
215 struct Inner;
216 struct A { field1: Inner }
217
218 impl A {
219     fn new(inner: Inner) -> A {
220         A { field1: inner }
221     }
222
223     fn new_with_default() -> A {
224         A::new(Inner)
225     }
226
227     fn into_inner(self) -> Inner {
228         self.field1
229     }
230 }"#,
231         );
232     }
233
234     #[test]
235     fn convert_struct_referenced_via_self_kw() {
236         check_assist(
237             convert_tuple_struct_to_named_struct,
238             r#"
239 struct Inner;
240 struct A$0(Inner);
241
242 impl A {
243     fn new(inner: Inner) -> Self {
244         Self(inner)
245     }
246
247     fn new_with_default() -> Self {
248         Self::new(Inner)
249     }
250
251     fn into_inner(self) -> Inner {
252         self.0
253     }
254 }"#,
255             r#"
256 struct Inner;
257 struct A { field1: Inner }
258
259 impl A {
260     fn new(inner: Inner) -> Self {
261         Self { field1: inner }
262     }
263
264     fn new_with_default() -> Self {
265         Self::new(Inner)
266     }
267
268     fn into_inner(self) -> Inner {
269         self.field1
270     }
271 }"#,
272         );
273     }
274
275     #[test]
276     fn convert_destructured_struct() {
277         check_assist(
278             convert_tuple_struct_to_named_struct,
279             r#"
280 struct Inner;
281 struct A$0(Inner);
282
283 impl A {
284     fn into_inner(self) -> Inner {
285         let A(first) = self;
286         first
287     }
288
289     fn into_inner_via_self(self) -> Inner {
290         let Self(first) = self;
291         first
292     }
293 }"#,
294             r#"
295 struct Inner;
296 struct A { field1: Inner }
297
298 impl A {
299     fn into_inner(self) -> Inner {
300         let A { field1: first } = self;
301         first
302     }
303
304     fn into_inner_via_self(self) -> Inner {
305         let Self { field1: first } = self;
306         first
307     }
308 }"#,
309         );
310     }
311
312     #[test]
313     fn convert_struct_with_visibility() {
314         check_assist(
315             convert_tuple_struct_to_named_struct,
316             r#"
317 struct A$0(pub u32, pub(crate) u64);
318
319 impl A {
320     fn new() -> A {
321         A(42, 42)
322     }
323
324     fn into_first(self) -> u32 {
325         self.0
326     }
327
328     fn into_second(self) -> u64 {
329         self.1
330     }
331 }"#,
332             r#"
333 struct A { pub field1: u32, pub(crate) field2: u64 }
334
335 impl A {
336     fn new() -> A {
337         A { field1: 42, field2: 42 }
338     }
339
340     fn into_first(self) -> u32 {
341         self.field1
342     }
343
344     fn into_second(self) -> u64 {
345         self.field2
346     }
347 }"#,
348         );
349     }
350
351     #[test]
352     fn convert_struct_with_where_clause() {
353         check_assist(
354             convert_tuple_struct_to_named_struct,
355             r#"
356 struct Wrap$0<T>(T)
357 where
358     T: Display;
359 "#,
360             r#"
361 struct Wrap<T>
362 where
363     T: Display,
364 { field1: T }
365
366 "#,
367         );
368     }
369 }