aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs345
-rw-r--r--crates/ide_assists/src/lib.rs2
-rw-r--r--crates/ide_assists/src/tests/generated.rs15
-rw-r--r--crates/ide_db/src/search.rs18
-rw-r--r--crates/syntax/src/ast/make.rs26
5 files changed, 397 insertions, 9 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
new file mode 100644
index 000000000..b2f7be011
--- /dev/null
+++ b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
@@ -0,0 +1,345 @@
1use hir::{Adt, ModuleDef};
2use ide_db::defs::Definition;
3use syntax::{
4 ast::{self, AstNode, GenericParamsOwner, VisibilityOwner},
5 match_ast,
6};
7
8use 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// ```
23pub(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
47fn 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
77fn edit_struct_references(
78 ctx: &AssistContext,
79 edit: &mut AssistBuilder,
80 strukt: &ast::Struct,
81 names: &[ast::Name],
82) {
83 let strukt_def = ctx.sema.to_def(strukt).unwrap();
84 let usages = Definition::ModuleDef(ModuleDef::Adt(Adt::Struct(strukt_def)))
85 .usages(&ctx.sema)
86 .include_self_kw_refs(true)
87 .all();
88
89 for (file_id, refs) in usages {
90 edit.edit_file(file_id);
91 for r in refs {
92 for node in r.name.syntax().ancestors() {
93 match_ast! {
94 match node {
95 ast::TupleStructPat(tuple_struct_pat) => {
96 edit.replace(
97 tuple_struct_pat.syntax().text_range(),
98 ast::make::record_pat_with_fields(
99 tuple_struct_pat.path().unwrap(),
100 ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map(
101 |(pat, name)| {
102 ast::make::record_pat_field(
103 ast::make::name_ref(&name.to_string()),
104 pat,
105 )
106 },
107 )),
108 )
109 .to_string(),
110 );
111 },
112 // for tuple struct creations like: Foo(42)
113 ast::CallExpr(call_expr) => {
114 let path = call_expr.syntax().descendants().find_map(ast::PathExpr::cast).unwrap();
115 let arg_list =
116 call_expr.syntax().descendants().find_map(ast::ArgList::cast).unwrap();
117
118 edit.replace(
119 call_expr.syntax().text_range(),
120 ast::make::record_expr(
121 path.path().unwrap(),
122 ast::make::record_expr_field_list(arg_list.args().zip(names).map(
123 |(expr, name)| {
124 ast::make::record_expr_field(
125 ast::make::name_ref(&name.to_string()),
126 Some(expr),
127 )
128 },
129 )),
130 )
131 .to_string(),
132 );
133 },
134 _ => ()
135 }
136 }
137 }
138 }
139 }
140}
141
142fn edit_field_references(
143 ctx: &AssistContext,
144 edit: &mut AssistBuilder,
145 fields: impl Iterator<Item = ast::TupleField>,
146 names: &[ast::Name],
147) {
148 for (field, name) in fields.zip(names) {
149 let field = match ctx.sema.to_def(&field) {
150 Some(it) => it,
151 None => continue,
152 };
153 let def = Definition::Field(field);
154 let usages = def.usages(&ctx.sema).all();
155 for (file_id, refs) in usages {
156 edit.edit_file(file_id);
157 for r in refs {
158 if let Some(name_ref) = r.name.as_name_ref() {
159 edit.replace(name_ref.syntax().text_range(), name.text());
160 }
161 }
162 }
163 }
164}
165
166fn generate_names(fields: impl Iterator<Item = ast::TupleField>) -> Vec<ast::Name> {
167 fields.enumerate().map(|(i, _)| ast::make::name(&format!("field{}", i + 1))).collect()
168}
169
170#[cfg(test)]
171mod tests {
172 use crate::tests::{check_assist, check_assist_not_applicable};
173
174 use super::*;
175
176 #[test]
177 fn not_applicable_other_than_tuple_struct() {
178 check_assist_not_applicable(
179 convert_tuple_struct_to_named_struct,
180 r#"struct Foo$0 { bar: u32 };"#,
181 );
182 check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"struct Foo$0;"#);
183 }
184
185 #[test]
186 fn convert_simple_struct() {
187 check_assist(
188 convert_tuple_struct_to_named_struct,
189 r#"
190struct Inner;
191struct A$0(Inner);
192
193impl A {
194 fn new() -> A {
195 A(Inner)
196 }
197
198 fn into_inner(self) -> Inner {
199 self.0
200 }
201}"#,
202 r#"
203struct Inner;
204struct A { field1: Inner }
205
206impl A {
207 fn new() -> A {
208 A { field1: Inner }
209 }
210
211 fn into_inner(self) -> Inner {
212 self.field1
213 }
214}"#,
215 );
216 }
217
218 #[test]
219 fn convert_struct_referenced_via_self_kw() {
220 check_assist(
221 convert_tuple_struct_to_named_struct,
222 r#"
223struct Inner;
224struct A$0(Inner);
225
226impl A {
227 fn new() -> Self {
228 Self(Inner)
229 }
230
231 fn into_inner(self) -> Inner {
232 self.0
233 }
234}"#,
235 r#"
236struct Inner;
237struct A { field1: Inner }
238
239impl A {
240 fn new() -> Self {
241 Self { field1: Inner }
242 }
243
244 fn into_inner(self) -> Inner {
245 self.field1
246 }
247}"#,
248 );
249 }
250
251 #[test]
252 fn convert_destructured_struct() {
253 check_assist(
254 convert_tuple_struct_to_named_struct,
255 r#"
256struct Inner;
257struct A$0(Inner);
258
259impl A {
260 fn into_inner(self) -> Inner {
261 let A(first) = self;
262 first
263 }
264
265 fn into_inner_via_self(self) -> Inner {
266 let Self(first) = self;
267 first
268 }
269}"#,
270 r#"
271struct Inner;
272struct A { field1: Inner }
273
274impl A {
275 fn into_inner(self) -> Inner {
276 let A { field1: first } = self;
277 first
278 }
279
280 fn into_inner_via_self(self) -> Inner {
281 let Self { field1: first } = self;
282 first
283 }
284}"#,
285 );
286 }
287
288 #[test]
289 fn convert_struct_with_visibility() {
290 check_assist(
291 convert_tuple_struct_to_named_struct,
292 r#"
293struct A$0(pub u32, pub(crate) u64);
294
295impl A {
296 fn new() -> A {
297 A(42, 42)
298 }
299
300 fn into_first(self) -> u32 {
301 self.0
302 }
303
304 fn into_second(self) -> u64 {
305 self.1
306 }
307}"#,
308 r#"
309struct A { pub field1: u32, pub(crate) field2: u64 }
310
311impl A {
312 fn new() -> A {
313 A { field1: 42, field2: 42 }
314 }
315
316 fn into_first(self) -> u32 {
317 self.field1
318 }
319
320 fn into_second(self) -> u64 {
321 self.field2
322 }
323}"#,
324 );
325 }
326
327 #[test]
328 fn convert_struct_with_where_clause() {
329 check_assist(
330 convert_tuple_struct_to_named_struct,
331 r#"
332struct Wrap$0<T>(T)
333where
334 T: Display;
335"#,
336 r#"
337struct Wrap<T>
338where
339 T: Display,
340{ field1: T }
341
342"#,
343 );
344 }
345}
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index 3e2c82dac..1c55b9fbf 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -118,6 +118,7 @@ mod handlers {
118 mod convert_comment_block; 118 mod convert_comment_block;
119 mod convert_iter_for_each_to_for; 119 mod convert_iter_for_each_to_for;
120 mod convert_into_to_from; 120 mod convert_into_to_from;
121 mod convert_tuple_struct_to_named_struct;
121 mod early_return; 122 mod early_return;
122 mod expand_glob_import; 123 mod expand_glob_import;
123 mod extract_function; 124 mod extract_function;
@@ -187,6 +188,7 @@ mod handlers {
187 convert_comment_block::convert_comment_block, 188 convert_comment_block::convert_comment_block,
188 convert_iter_for_each_to_for::convert_iter_for_each_to_for, 189 convert_iter_for_each_to_for::convert_iter_for_each_to_for,
189 convert_into_to_from::convert_into_to_from, 190 convert_into_to_from::convert_into_to_from,
191 convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct,
190 early_return::convert_to_guarded_return, 192 early_return::convert_to_guarded_return,
191 expand_glob_import::expand_glob_import, 193 expand_glob_import::expand_glob_import,
192 extract_struct_from_enum_variant::extract_struct_from_enum_variant, 194 extract_struct_from_enum_variant::extract_struct_from_enum_variant,
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 27a22ca10..53f455adf 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -292,6 +292,21 @@ fn main() {
292} 292}
293 293
294#[test] 294#[test]
295fn doctest_convert_tuple_struct_to_named_struct() {
296 check_doc_test(
297 "convert_tuple_struct_to_named_struct",
298 r#####"
299struct Inner;
300struct A$0(Inner);
301"#####,
302 r#####"
303struct Inner;
304struct A { field1: Inner }
305"#####,
306 )
307}
308
309#[test]
295fn doctest_expand_glob_import() { 310fn doctest_expand_glob_import() {
296 check_doc_test( 311 check_doc_test(
297 "expand_glob_import", 312 "expand_glob_import",
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs
index 02f5e514b..90e4e7b03 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -430,6 +430,15 @@ impl<'a> FindUsages<'a> {
430 sink: &mut dyn FnMut(FileId, FileReference) -> bool, 430 sink: &mut dyn FnMut(FileId, FileReference) -> bool,
431 ) -> bool { 431 ) -> bool {
432 match NameRefClass::classify(self.sema, &name_ref) { 432 match NameRefClass::classify(self.sema, &name_ref) {
433 Some(NameRefClass::Definition(def)) if &def == self.def => {
434 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
435 let reference = FileReference {
436 range,
437 name: ast::NameLike::NameRef(name_ref.clone()),
438 access: reference_access(&def, &name_ref),
439 };
440 sink(file_id, reference)
441 }
433 Some(NameRefClass::Definition(Definition::SelfType(impl_))) => { 442 Some(NameRefClass::Definition(Definition::SelfType(impl_))) => {
434 let ty = impl_.self_ty(self.sema.db); 443 let ty = impl_.self_ty(self.sema.db);
435 444
@@ -448,15 +457,6 @@ impl<'a> FindUsages<'a> {
448 457
449 false 458 false
450 } 459 }
451 Some(NameRefClass::Definition(def)) if &def == self.def => {
452 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
453 let reference = FileReference {
454 range,
455 name: ast::NameLike::NameRef(name_ref.clone()),
456 access: reference_access(&def, &name_ref),
457 };
458 sink(file_id, reference)
459 }
460 Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => { 460 Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => {
461 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); 461 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
462 let reference = match self.def { 462 let reference = match self.def {
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index c6a7b99b7..3a588e540 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -133,6 +133,17 @@ pub fn use_(visibility: Option<ast::Visibility>, use_tree: ast::UseTree) -> ast:
133 ast_from_text(&format!("{}use {};", visibility, use_tree)) 133 ast_from_text(&format!("{}use {};", visibility, use_tree))
134} 134}
135 135
136pub fn record_expr(path: ast::Path, fields: ast::RecordExprFieldList) -> ast::RecordExpr {
137 ast_from_text(&format!("fn f() {{ {} {} }}", path, fields))
138}
139
140pub fn record_expr_field_list(
141 fields: impl IntoIterator<Item = ast::RecordExprField>,
142) -> ast::RecordExprFieldList {
143 let fields = fields.into_iter().join(", ");
144 ast_from_text(&format!("fn f() {{ S {{ {} }} }}", fields))
145}
146
136pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField { 147pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField {
137 return match expr { 148 return match expr {
138 Some(expr) => from_text(&format!("{}: {}", name, expr)), 149 Some(expr) => from_text(&format!("{}: {}", name, expr)),
@@ -325,6 +336,21 @@ pub fn record_pat(path: ast::Path, pats: impl IntoIterator<Item = ast::Pat>) ->
325 } 336 }
326} 337}
327 338
339pub fn record_pat_with_fields(path: ast::Path, fields: ast::RecordPatFieldList) -> ast::RecordPat {
340 ast_from_text(&format!("fn f({} {}: ()))", path, fields))
341}
342
343pub fn record_pat_field_list(
344 fields: impl IntoIterator<Item = ast::RecordPatField>,
345) -> ast::RecordPatFieldList {
346 let fields = fields.into_iter().join(", ");
347 ast_from_text(&format!("fn f(S {{ {} }}: ()))", fields))
348}
349
350pub fn record_pat_field(name_ref: ast::NameRef, pat: ast::Pat) -> ast::RecordPatField {
351 ast_from_text(&format!("fn f(S {{ {}: {} }}: ()))", name_ref, pat))
352}
353
328/// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise. 354/// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise.
329pub fn path_pat(path: ast::Path) -> ast::Pat { 355pub fn path_pat(path: ast::Path) -> ast::Pat {
330 return from_text(&path.to_string()); 356 return from_text(&path.to_string());