diff options
Diffstat (limited to 'crates/ide_assists/src')
-rw-r--r-- | crates/ide_assists/src/ast_transform.rs | 33 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/auto_import.rs | 10 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs | 516 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/extract_function.rs | 36 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs | 208 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/merge_imports.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/reorder_fields.rs | 8 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/reorder_impl.rs | 20 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs | 34 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/tests.rs | 5 | ||||
-rw-r--r-- | crates/ide_assists/src/tests/generated.rs | 41 | ||||
-rw-r--r-- | crates/ide_assists/src/utils.rs | 3 |
13 files changed, 747 insertions, 171 deletions
diff --git a/crates/ide_assists/src/ast_transform.rs b/crates/ide_assists/src/ast_transform.rs index 4a3ed7783..e5ae718c9 100644 --- a/crates/ide_assists/src/ast_transform.rs +++ b/crates/ide_assists/src/ast_transform.rs | |||
@@ -3,20 +3,27 @@ use hir::{HirDisplay, PathResolution, SemanticsScope}; | |||
3 | use ide_db::helpers::mod_path_to_ast; | 3 | use ide_db::helpers::mod_path_to_ast; |
4 | use rustc_hash::FxHashMap; | 4 | use rustc_hash::FxHashMap; |
5 | use syntax::{ | 5 | use syntax::{ |
6 | algo::SyntaxRewriter, | ||
7 | ast::{self, AstNode}, | 6 | ast::{self, AstNode}, |
8 | SyntaxNode, | 7 | ted, SyntaxNode, |
9 | }; | 8 | }; |
10 | 9 | ||
11 | pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N { | 10 | pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: &N) { |
12 | SyntaxRewriter::from_fn(|element| match element { | 11 | let mut skip_to = None; |
13 | syntax::SyntaxElement::Node(n) => { | 12 | for event in node.syntax().preorder() { |
14 | let replacement = transformer.get_substitution(&n, transformer)?; | 13 | match event { |
15 | Some(replacement.into()) | 14 | syntax::WalkEvent::Enter(node) if skip_to.is_none() => { |
15 | skip_to = transformer.get_substitution(&node, transformer).zip(Some(node)); | ||
16 | } | ||
17 | syntax::WalkEvent::Enter(_) => (), | ||
18 | syntax::WalkEvent::Leave(node) => match &skip_to { | ||
19 | Some((replacement, skip_target)) if *skip_target == node => { | ||
20 | ted::replace(node, replacement.clone_for_update()); | ||
21 | skip_to.take(); | ||
22 | } | ||
23 | _ => (), | ||
24 | }, | ||
16 | } | 25 | } |
17 | _ => None, | 26 | } |
18 | }) | ||
19 | .rewrite_ast(&node) | ||
20 | } | 27 | } |
21 | 28 | ||
22 | /// `AstTransform` helps with applying bulk transformations to syntax nodes. | 29 | /// `AstTransform` helps with applying bulk transformations to syntax nodes. |
@@ -191,11 +198,9 @@ impl<'a> AstTransform<'a> for QualifyPaths<'a> { | |||
191 | let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?; | 198 | let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?; |
192 | let mut path = mod_path_to_ast(&found_path); | 199 | let mut path = mod_path_to_ast(&found_path); |
193 | 200 | ||
194 | let type_args = p | 201 | let type_args = p.segment().and_then(|s| s.generic_arg_list()); |
195 | .segment() | ||
196 | .and_then(|s| s.generic_arg_list()) | ||
197 | .map(|arg_list| apply(recur, arg_list)); | ||
198 | if let Some(type_args) = type_args { | 202 | if let Some(type_args) = type_args { |
203 | apply(recur, &type_args); | ||
199 | let last_segment = path.segment().unwrap(); | 204 | let last_segment = path.segment().unwrap(); |
200 | path = path.with_segment(last_segment.with_generic_args(type_args)) | 205 | path = path.with_segment(last_segment.with_generic_args(type_args)) |
201 | } | 206 | } |
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs index 49aa70f74..a454a2af3 100644 --- a/crates/ide_assists/src/handlers/auto_import.rs +++ b/crates/ide_assists/src/handlers/auto_import.rs | |||
@@ -93,7 +93,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
93 | 93 | ||
94 | let range = ctx.sema.original_range(&syntax_under_caret).range; | 94 | let range = ctx.sema.original_range(&syntax_under_caret).range; |
95 | let group_label = group_label(import_assets.import_candidate()); | 95 | let group_label = group_label(import_assets.import_candidate()); |
96 | let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?; | 96 | let scope = ImportScope::find_insert_use_container_with_macros(&syntax_under_caret, &ctx.sema)?; |
97 | for import in proposed_imports { | 97 | for import in proposed_imports { |
98 | acc.add_group( | 98 | acc.add_group( |
99 | &group_label, | 99 | &group_label, |
@@ -101,9 +101,11 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
101 | format!("Import `{}`", import.import_path), | 101 | format!("Import `{}`", import.import_path), |
102 | range, | 102 | range, |
103 | |builder| { | 103 | |builder| { |
104 | let rewriter = | 104 | let scope = match scope.clone() { |
105 | insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use); | 105 | ImportScope::File(it) => ImportScope::File(builder.make_ast_mut(it)), |
106 | builder.rewrite(rewriter); | 106 | ImportScope::Module(it) => ImportScope::Module(builder.make_ast_mut(it)), |
107 | }; | ||
108 | insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use); | ||
107 | }, | 109 | }, |
108 | ); | 110 | ); |
109 | } | 111 | } |
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..b5b5ada5e --- /dev/null +++ b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs | |||
@@ -0,0 +1,516 @@ | |||
1 | use ide_db::defs::{Definition, NameRefClass}; | ||
2 | use syntax::{ | ||
3 | ast::{self, AstNode, GenericParamsOwner, VisibilityOwner}, | ||
4 | match_ast, SyntaxNode, | ||
5 | }; | ||
6 | |||
7 | use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists}; | ||
8 | |||
9 | // Assist: convert_tuple_struct_to_named_struct | ||
10 | // | ||
11 | // Converts tuple struct to struct with named fields. | ||
12 | // | ||
13 | // ``` | ||
14 | // struct Point$0(f32, f32); | ||
15 | // | ||
16 | // impl Point { | ||
17 | // pub fn new(x: f32, y: f32) -> Self { | ||
18 | // Point(x, y) | ||
19 | // } | ||
20 | // | ||
21 | // pub fn x(&self) -> f32 { | ||
22 | // self.0 | ||
23 | // } | ||
24 | // | ||
25 | // pub fn y(&self) -> f32 { | ||
26 | // self.1 | ||
27 | // } | ||
28 | // } | ||
29 | // ``` | ||
30 | // -> | ||
31 | // ``` | ||
32 | // struct Point { field1: f32, field2: f32 } | ||
33 | // | ||
34 | // impl Point { | ||
35 | // pub fn new(x: f32, y: f32) -> Self { | ||
36 | // Point { field1: x, field2: y } | ||
37 | // } | ||
38 | // | ||
39 | // pub fn x(&self) -> f32 { | ||
40 | // self.field1 | ||
41 | // } | ||
42 | // | ||
43 | // pub fn y(&self) -> f32 { | ||
44 | // self.field2 | ||
45 | // } | ||
46 | // } | ||
47 | // ``` | ||
48 | pub(crate) fn convert_tuple_struct_to_named_struct( | ||
49 | acc: &mut Assists, | ||
50 | ctx: &AssistContext, | ||
51 | ) -> Option<()> { | ||
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, | ||
56 | }; | ||
57 | let strukt_def = ctx.sema.to_def(&strukt)?; | ||
58 | |||
59 | let target = strukt.syntax().text_range(); | ||
60 | acc.add( | ||
61 | AssistId("convert_tuple_struct_to_named_struct", AssistKind::RefactorRewrite), | ||
62 | "Convert to named struct", | ||
63 | target, | ||
64 | |edit| { | ||
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); | ||
69 | }, | ||
70 | ) | ||
71 | } | ||
72 | |||
73 | fn edit_struct_def( | ||
74 | ctx: &AssistContext, | ||
75 | edit: &mut AssistBuilder, | ||
76 | strukt: &ast::Struct, | ||
77 | tuple_fields: ast::TupleFieldList, | ||
78 | names: Vec<ast::Name>, | ||
79 | ) { | ||
80 | let record_fields = tuple_fields | ||
81 | .fields() | ||
82 | .zip(names) | ||
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(); | ||
86 | |||
87 | edit.edit_file(ctx.frange.file_id); | ||
88 | |||
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()); | ||
95 | } else { | ||
96 | edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text()); | ||
97 | } | ||
98 | |||
99 | edit.replace(tuple_fields_text_range, record_fields.to_string()); | ||
100 | strukt.semicolon_token().map(|t| edit.delete(t.text_range())); | ||
101 | } | ||
102 | |||
103 | fn edit_struct_references( | ||
104 | ctx: &AssistContext, | ||
105 | edit: &mut AssistBuilder, | ||
106 | strukt: hir::Struct, | ||
107 | names: &[ast::Name], | ||
108 | ) { | ||
109 | let strukt_def = Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(strukt))); | ||
110 | let usages = strukt_def.usages(&ctx.sema).include_self_kw_refs(true).all(); | ||
111 | |||
112 | let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> { | ||
113 | match_ast! { | ||
114 | match node { | ||
115 | ast::TupleStructPat(tuple_struct_pat) => { | ||
116 | edit.replace( | ||
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( | ||
121 | |(pat, name)| { | ||
122 | ast::make::record_pat_field( | ||
123 | ast::make::name_ref(&name.to_string()), | ||
124 | pat, | ||
125 | ) | ||
126 | }, | ||
127 | )), | ||
128 | ) | ||
129 | .to_string(), | ||
130 | ); | ||
131 | }, | ||
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())?; | ||
135 | |||
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 => {}, | ||
141 | _ => return None, | ||
142 | }; | ||
143 | } | ||
144 | |||
145 | let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?; | ||
146 | |||
147 | edit.replace( | ||
148 | call_expr.syntax().text_range(), | ||
149 | ast::make::record_expr( | ||
150 | path, | ||
151 | ast::make::record_expr_field_list(arg_list.args().zip(names).map( | ||
152 | |(expr, name)| { | ||
153 | ast::make::record_expr_field( | ||
154 | ast::make::name_ref(&name.to_string()), | ||
155 | Some(expr), | ||
156 | ) | ||
157 | }, | ||
158 | )), | ||
159 | ) | ||
160 | .to_string(), | ||
161 | ); | ||
162 | }, | ||
163 | _ => return None, | ||
164 | } | ||
165 | } | ||
166 | Some(()) | ||
167 | }; | ||
168 | |||
169 | for (file_id, refs) in usages { | ||
170 | edit.edit_file(file_id); | ||
171 | for r in refs { | ||
172 | for node in r.name.syntax().ancestors() { | ||
173 | if edit_node(edit, node).is_some() { | ||
174 | break; | ||
175 | } | ||
176 | } | ||
177 | } | ||
178 | } | ||
179 | } | ||
180 | |||
181 | fn edit_field_references( | ||
182 | ctx: &AssistContext, | ||
183 | edit: &mut AssistBuilder, | ||
184 | fields: impl Iterator<Item = ast::TupleField>, | ||
185 | names: &[ast::Name], | ||
186 | ) { | ||
187 | for (field, name) in fields.zip(names) { | ||
188 | let field = match ctx.sema.to_def(&field) { | ||
189 | Some(it) => it, | ||
190 | None => continue, | ||
191 | }; | ||
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); | ||
196 | for r in refs { | ||
197 | if let Some(name_ref) = r.name.as_name_ref() { | ||
198 | edit.replace(name_ref.syntax().text_range(), name.text()); | ||
199 | } | ||
200 | } | ||
201 | } | ||
202 | } | ||
203 | } | ||
204 | |||
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() | ||
207 | } | ||
208 | |||
209 | #[cfg(test)] | ||
210 | mod tests { | ||
211 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
212 | |||
213 | use super::*; | ||
214 | |||
215 | #[test] | ||
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 };"#, | ||
220 | ); | ||
221 | check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"struct Foo$0;"#); | ||
222 | } | ||
223 | |||
224 | #[test] | ||
225 | fn convert_simple_struct() { | ||
226 | check_assist( | ||
227 | convert_tuple_struct_to_named_struct, | ||
228 | r#" | ||
229 | struct Inner; | ||
230 | struct A$0(Inner); | ||
231 | |||
232 | impl A { | ||
233 | fn new(inner: Inner) -> A { | ||
234 | A(inner) | ||
235 | } | ||
236 | |||
237 | fn new_with_default() -> A { | ||
238 | A::new(Inner) | ||
239 | } | ||
240 | |||
241 | fn into_inner(self) -> Inner { | ||
242 | self.0 | ||
243 | } | ||
244 | }"#, | ||
245 | r#" | ||
246 | struct Inner; | ||
247 | struct A { field1: Inner } | ||
248 | |||
249 | impl A { | ||
250 | fn new(inner: Inner) -> A { | ||
251 | A { field1: inner } | ||
252 | } | ||
253 | |||
254 | fn new_with_default() -> A { | ||
255 | A::new(Inner) | ||
256 | } | ||
257 | |||
258 | fn into_inner(self) -> Inner { | ||
259 | self.field1 | ||
260 | } | ||
261 | }"#, | ||
262 | ); | ||
263 | } | ||
264 | |||
265 | #[test] | ||
266 | fn convert_struct_referenced_via_self_kw() { | ||
267 | check_assist( | ||
268 | convert_tuple_struct_to_named_struct, | ||
269 | r#" | ||
270 | struct Inner; | ||
271 | struct A$0(Inner); | ||
272 | |||
273 | impl A { | ||
274 | fn new(inner: Inner) -> Self { | ||
275 | Self(inner) | ||
276 | } | ||
277 | |||
278 | fn new_with_default() -> Self { | ||
279 | Self::new(Inner) | ||
280 | } | ||
281 | |||
282 | fn into_inner(self) -> Inner { | ||
283 | self.0 | ||
284 | } | ||
285 | }"#, | ||
286 | r#" | ||
287 | struct Inner; | ||
288 | struct A { field1: Inner } | ||
289 | |||
290 | impl A { | ||
291 | fn new(inner: Inner) -> Self { | ||
292 | Self { field1: inner } | ||
293 | } | ||
294 | |||
295 | fn new_with_default() -> Self { | ||
296 | Self::new(Inner) | ||
297 | } | ||
298 | |||
299 | fn into_inner(self) -> Inner { | ||
300 | self.field1 | ||
301 | } | ||
302 | }"#, | ||
303 | ); | ||
304 | } | ||
305 | |||
306 | #[test] | ||
307 | fn convert_destructured_struct() { | ||
308 | check_assist( | ||
309 | convert_tuple_struct_to_named_struct, | ||
310 | r#" | ||
311 | struct Inner; | ||
312 | struct A$0(Inner); | ||
313 | |||
314 | impl A { | ||
315 | fn into_inner(self) -> Inner { | ||
316 | let A(first) = self; | ||
317 | first | ||
318 | } | ||
319 | |||
320 | fn into_inner_via_self(self) -> Inner { | ||
321 | let Self(first) = self; | ||
322 | first | ||
323 | } | ||
324 | }"#, | ||
325 | r#" | ||
326 | struct Inner; | ||
327 | struct A { field1: Inner } | ||
328 | |||
329 | impl A { | ||
330 | fn into_inner(self) -> Inner { | ||
331 | let A { field1: first } = self; | ||
332 | first | ||
333 | } | ||
334 | |||
335 | fn into_inner_via_self(self) -> Inner { | ||
336 | let Self { field1: first } = self; | ||
337 | first | ||
338 | } | ||
339 | }"#, | ||
340 | ); | ||
341 | } | ||
342 | |||
343 | #[test] | ||
344 | fn convert_struct_with_visibility() { | ||
345 | check_assist( | ||
346 | convert_tuple_struct_to_named_struct, | ||
347 | r#" | ||
348 | struct A$0(pub u32, pub(crate) u64); | ||
349 | |||
350 | impl A { | ||
351 | fn new() -> A { | ||
352 | A(42, 42) | ||
353 | } | ||
354 | |||
355 | fn into_first(self) -> u32 { | ||
356 | self.0 | ||
357 | } | ||
358 | |||
359 | fn into_second(self) -> u64 { | ||
360 | self.1 | ||
361 | } | ||
362 | }"#, | ||
363 | r#" | ||
364 | struct A { pub field1: u32, pub(crate) field2: u64 } | ||
365 | |||
366 | impl A { | ||
367 | fn new() -> A { | ||
368 | A { field1: 42, field2: 42 } | ||
369 | } | ||
370 | |||
371 | fn into_first(self) -> u32 { | ||
372 | self.field1 | ||
373 | } | ||
374 | |||
375 | fn into_second(self) -> u64 { | ||
376 | self.field2 | ||
377 | } | ||
378 | }"#, | ||
379 | ); | ||
380 | } | ||
381 | |||
382 | #[test] | ||
383 | fn convert_struct_with_wrapped_references() { | ||
384 | check_assist( | ||
385 | convert_tuple_struct_to_named_struct, | ||
386 | r#" | ||
387 | struct Inner$0(u32); | ||
388 | struct Outer(Inner); | ||
389 | |||
390 | impl Outer { | ||
391 | fn new() -> Self { | ||
392 | Self(Inner(42)) | ||
393 | } | ||
394 | |||
395 | fn into_inner(self) -> u32 { | ||
396 | (self.0).0 | ||
397 | } | ||
398 | |||
399 | fn into_inner_destructed(self) -> u32 { | ||
400 | let Outer(Inner(x)) = self; | ||
401 | x | ||
402 | } | ||
403 | }"#, | ||
404 | r#" | ||
405 | struct Inner { field1: u32 } | ||
406 | struct Outer(Inner); | ||
407 | |||
408 | impl Outer { | ||
409 | fn new() -> Self { | ||
410 | Self(Inner { field1: 42 }) | ||
411 | } | ||
412 | |||
413 | fn into_inner(self) -> u32 { | ||
414 | (self.0).field1 | ||
415 | } | ||
416 | |||
417 | fn into_inner_destructed(self) -> u32 { | ||
418 | let Outer(Inner { field1: x }) = self; | ||
419 | x | ||
420 | } | ||
421 | }"#, | ||
422 | ); | ||
423 | |||
424 | check_assist( | ||
425 | convert_tuple_struct_to_named_struct, | ||
426 | r#" | ||
427 | struct Inner(u32); | ||
428 | struct Outer$0(Inner); | ||
429 | |||
430 | impl Outer { | ||
431 | fn new() -> Self { | ||
432 | Self(Inner(42)) | ||
433 | } | ||
434 | |||
435 | fn into_inner(self) -> u32 { | ||
436 | (self.0).0 | ||
437 | } | ||
438 | |||
439 | fn into_inner_destructed(self) -> u32 { | ||
440 | let Outer(Inner(x)) = self; | ||
441 | x | ||
442 | } | ||
443 | }"#, | ||
444 | r#" | ||
445 | struct Inner(u32); | ||
446 | struct Outer { field1: Inner } | ||
447 | |||
448 | impl Outer { | ||
449 | fn new() -> Self { | ||
450 | Self { field1: Inner(42) } | ||
451 | } | ||
452 | |||
453 | fn into_inner(self) -> u32 { | ||
454 | (self.field1).0 | ||
455 | } | ||
456 | |||
457 | fn into_inner_destructed(self) -> u32 { | ||
458 | let Outer { field1: Inner(x) } = self; | ||
459 | x | ||
460 | } | ||
461 | }"#, | ||
462 | ); | ||
463 | } | ||
464 | |||
465 | #[test] | ||
466 | fn convert_struct_with_multi_file_references() { | ||
467 | check_assist( | ||
468 | convert_tuple_struct_to_named_struct, | ||
469 | r#" | ||
470 | //- /main.rs | ||
471 | struct Inner; | ||
472 | struct A$0(Inner); | ||
473 | |||
474 | mod foo; | ||
475 | |||
476 | //- /foo.rs | ||
477 | use crate::{A, Inner}; | ||
478 | fn f() { | ||
479 | let a = A(Inner); | ||
480 | } | ||
481 | "#, | ||
482 | r#" | ||
483 | //- /main.rs | ||
484 | struct Inner; | ||
485 | struct A { field1: Inner } | ||
486 | |||
487 | mod foo; | ||
488 | |||
489 | //- /foo.rs | ||
490 | use crate::{A, Inner}; | ||
491 | fn f() { | ||
492 | let a = A { field1: Inner }; | ||
493 | } | ||
494 | "#, | ||
495 | ); | ||
496 | } | ||
497 | |||
498 | #[test] | ||
499 | fn convert_struct_with_where_clause() { | ||
500 | check_assist( | ||
501 | convert_tuple_struct_to_named_struct, | ||
502 | r#" | ||
503 | struct Wrap$0<T>(T) | ||
504 | where | ||
505 | T: Display; | ||
506 | "#, | ||
507 | r#" | ||
508 | struct Wrap<T> | ||
509 | where | ||
510 | T: Display, | ||
511 | { field1: T } | ||
512 | |||
513 | "#, | ||
514 | ); | ||
515 | } | ||
516 | } | ||
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs index 78a57fbdc..5f80a40c8 100644 --- a/crates/ide_assists/src/handlers/extract_function.rs +++ b/crates/ide_assists/src/handlers/extract_function.rs | |||
@@ -1227,9 +1227,19 @@ fn make_body( | |||
1227 | FunctionBody::Expr(expr) => { | 1227 | FunctionBody::Expr(expr) => { |
1228 | let expr = rewrite_body_segment(ctx, &fun.params, &handler, expr.syntax()); | 1228 | let expr = rewrite_body_segment(ctx, &fun.params, &handler, expr.syntax()); |
1229 | let expr = ast::Expr::cast(expr).unwrap(); | 1229 | let expr = ast::Expr::cast(expr).unwrap(); |
1230 | let expr = expr.dedent(old_indent).indent(IndentLevel(1)); | 1230 | match expr { |
1231 | ast::Expr::BlockExpr(block) => { | ||
1232 | // If the extracted expression is itself a block, there is no need to wrap it inside another block. | ||
1233 | let block = block.dedent(old_indent); | ||
1234 | // Recreate the block for formatting consistency with other extracted functions. | ||
1235 | make::block_expr(block.statements(), block.tail_expr()) | ||
1236 | } | ||
1237 | _ => { | ||
1238 | let expr = expr.dedent(old_indent).indent(IndentLevel(1)); | ||
1231 | 1239 | ||
1232 | make::block_expr(Vec::new(), Some(expr)) | 1240 | make::block_expr(Vec::new(), Some(expr)) |
1241 | } | ||
1242 | } | ||
1233 | } | 1243 | } |
1234 | FunctionBody::Span { parent, text_range } => { | 1244 | FunctionBody::Span { parent, text_range } => { |
1235 | let mut elements: Vec<_> = parent | 1245 | let mut elements: Vec<_> = parent |
@@ -1544,7 +1554,7 @@ fn foo() { | |||
1544 | } | 1554 | } |
1545 | 1555 | ||
1546 | fn $0fun_name() -> i32 { | 1556 | fn $0fun_name() -> i32 { |
1547 | { 1 + 1 } | 1557 | 1 + 1 |
1548 | }"#, | 1558 | }"#, |
1549 | ); | 1559 | ); |
1550 | } | 1560 | } |
@@ -2526,17 +2536,15 @@ fn foo() { | |||
2526 | } | 2536 | } |
2527 | 2537 | ||
2528 | fn $0fun_name(n: &mut i32) { | 2538 | fn $0fun_name(n: &mut i32) { |
2529 | { | 2539 | *n += *n; |
2530 | *n += *n; | 2540 | bar(*n); |
2531 | bar(*n); | 2541 | bar(*n+1); |
2532 | bar(*n+1); | 2542 | bar(*n**n); |
2533 | bar(*n**n); | 2543 | bar(&*n); |
2534 | bar(&*n); | 2544 | n.inc(); |
2535 | n.inc(); | 2545 | let v = n; |
2536 | let v = n; | 2546 | *v = v.succ(); |
2537 | *v = v.succ(); | 2547 | n.succ(); |
2538 | n.succ(); | ||
2539 | } | ||
2540 | }", | 2548 | }", |
2541 | ); | 2549 | ); |
2542 | } | 2550 | } |
diff --git a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs index a8d6355bd..66f274fa7 100644 --- a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs | |||
@@ -5,7 +5,7 @@ use hir::{Module, ModuleDef, Name, Variant}; | |||
5 | use ide_db::{ | 5 | use ide_db::{ |
6 | defs::Definition, | 6 | defs::Definition, |
7 | helpers::{ | 7 | helpers::{ |
8 | insert_use::{insert_use, ImportScope}, | 8 | insert_use::{insert_use, ImportScope, InsertUseConfig}, |
9 | mod_path_to_ast, | 9 | mod_path_to_ast, |
10 | }, | 10 | }, |
11 | search::FileReference, | 11 | search::FileReference, |
@@ -13,9 +13,9 @@ use ide_db::{ | |||
13 | }; | 13 | }; |
14 | use rustc_hash::FxHashSet; | 14 | use rustc_hash::FxHashSet; |
15 | use syntax::{ | 15 | use syntax::{ |
16 | algo::{find_node_at_offset, SyntaxRewriter}, | 16 | algo::find_node_at_offset, |
17 | ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner}, | 17 | ast::{self, make, AstNode, NameOwner, VisibilityOwner}, |
18 | SourceFile, SyntaxElement, SyntaxNode, T, | 18 | ted, SyntaxNode, T, |
19 | }; | 19 | }; |
20 | 20 | ||
21 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 21 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
@@ -62,40 +62,50 @@ pub(crate) fn extract_struct_from_enum_variant( | |||
62 | let mut visited_modules_set = FxHashSet::default(); | 62 | let mut visited_modules_set = FxHashSet::default(); |
63 | let current_module = enum_hir.module(ctx.db()); | 63 | let current_module = enum_hir.module(ctx.db()); |
64 | visited_modules_set.insert(current_module); | 64 | visited_modules_set.insert(current_module); |
65 | let mut def_rewriter = None; | 65 | // record file references of the file the def resides in, we only want to swap to the edited file in the builder once |
66 | let mut def_file_references = None; | ||
66 | for (file_id, references) in usages { | 67 | for (file_id, references) in usages { |
67 | let mut rewriter = SyntaxRewriter::default(); | ||
68 | let source_file = ctx.sema.parse(file_id); | ||
69 | for reference in references { | ||
70 | update_reference( | ||
71 | ctx, | ||
72 | &mut rewriter, | ||
73 | reference, | ||
74 | &source_file, | ||
75 | &enum_module_def, | ||
76 | &variant_hir_name, | ||
77 | &mut visited_modules_set, | ||
78 | ); | ||
79 | } | ||
80 | if file_id == ctx.frange.file_id { | 68 | if file_id == ctx.frange.file_id { |
81 | def_rewriter = Some(rewriter); | 69 | def_file_references = Some(references); |
82 | continue; | 70 | continue; |
83 | } | 71 | } |
84 | builder.edit_file(file_id); | 72 | builder.edit_file(file_id); |
85 | builder.rewrite(rewriter); | 73 | let source_file = builder.make_ast_mut(ctx.sema.parse(file_id)); |
74 | let processed = process_references( | ||
75 | ctx, | ||
76 | &mut visited_modules_set, | ||
77 | source_file.syntax(), | ||
78 | &enum_module_def, | ||
79 | &variant_hir_name, | ||
80 | references, | ||
81 | ); | ||
82 | processed.into_iter().for_each(|(path, node, import)| { | ||
83 | apply_references(ctx.config.insert_use, path, node, import) | ||
84 | }); | ||
86 | } | 85 | } |
87 | let mut rewriter = def_rewriter.unwrap_or_default(); | ||
88 | update_variant(&mut rewriter, &variant); | ||
89 | extract_struct_def( | ||
90 | &mut rewriter, | ||
91 | &enum_ast, | ||
92 | variant_name.clone(), | ||
93 | &field_list, | ||
94 | &variant.parent_enum().syntax().clone().into(), | ||
95 | enum_ast.visibility(), | ||
96 | ); | ||
97 | builder.edit_file(ctx.frange.file_id); | 86 | builder.edit_file(ctx.frange.file_id); |
98 | builder.rewrite(rewriter); | 87 | let source_file = builder.make_ast_mut(ctx.sema.parse(ctx.frange.file_id)); |
88 | let variant = builder.make_ast_mut(variant.clone()); | ||
89 | if let Some(references) = def_file_references { | ||
90 | let processed = process_references( | ||
91 | ctx, | ||
92 | &mut visited_modules_set, | ||
93 | source_file.syntax(), | ||
94 | &enum_module_def, | ||
95 | &variant_hir_name, | ||
96 | references, | ||
97 | ); | ||
98 | processed.into_iter().for_each(|(path, node, import)| { | ||
99 | apply_references(ctx.config.insert_use, path, node, import) | ||
100 | }); | ||
101 | } | ||
102 | |||
103 | let def = create_struct_def(variant_name.clone(), &field_list, enum_ast.visibility()); | ||
104 | let start_offset = &variant.parent_enum().syntax().clone(); | ||
105 | ted::insert_raw(ted::Position::before(start_offset), def.syntax()); | ||
106 | ted::insert_raw(ted::Position::before(start_offset), &make::tokens::blank_line()); | ||
107 | |||
108 | update_variant(&variant); | ||
99 | }, | 109 | }, |
100 | ) | 110 | ) |
101 | } | 111 | } |
@@ -136,34 +146,11 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Va | |||
136 | .any(|(name, _)| name.to_string() == variant_name.to_string()) | 146 | .any(|(name, _)| name.to_string() == variant_name.to_string()) |
137 | } | 147 | } |
138 | 148 | ||
139 | fn insert_import( | 149 | fn create_struct_def( |
140 | ctx: &AssistContext, | ||
141 | rewriter: &mut SyntaxRewriter, | ||
142 | scope_node: &SyntaxNode, | ||
143 | module: &Module, | ||
144 | enum_module_def: &ModuleDef, | ||
145 | variant_hir_name: &Name, | ||
146 | ) -> Option<()> { | ||
147 | let db = ctx.db(); | ||
148 | let mod_path = | ||
149 | module.find_use_path_prefixed(db, *enum_module_def, ctx.config.insert_use.prefix_kind); | ||
150 | if let Some(mut mod_path) = mod_path { | ||
151 | mod_path.pop_segment(); | ||
152 | mod_path.push_segment(variant_hir_name.clone()); | ||
153 | let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?; | ||
154 | *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use); | ||
155 | } | ||
156 | Some(()) | ||
157 | } | ||
158 | |||
159 | fn extract_struct_def( | ||
160 | rewriter: &mut SyntaxRewriter, | ||
161 | enum_: &ast::Enum, | ||
162 | variant_name: ast::Name, | 150 | variant_name: ast::Name, |
163 | field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>, | 151 | field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>, |
164 | start_offset: &SyntaxElement, | ||
165 | visibility: Option<ast::Visibility>, | 152 | visibility: Option<ast::Visibility>, |
166 | ) -> Option<()> { | 153 | ) -> ast::Struct { |
167 | let pub_vis = Some(make::visibility_pub()); | 154 | let pub_vis = Some(make::visibility_pub()); |
168 | let field_list = match field_list { | 155 | let field_list = match field_list { |
169 | Either::Left(field_list) => { | 156 | Either::Left(field_list) => { |
@@ -180,65 +167,90 @@ fn extract_struct_def( | |||
180 | .into(), | 167 | .into(), |
181 | }; | 168 | }; |
182 | 169 | ||
183 | rewriter.insert_before( | 170 | make::struct_(visibility, variant_name, None, field_list).clone_for_update() |
184 | start_offset, | ||
185 | make::struct_(visibility, variant_name, None, field_list).syntax(), | ||
186 | ); | ||
187 | rewriter.insert_before(start_offset, &make::tokens::blank_line()); | ||
188 | |||
189 | if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize { | ||
190 | rewriter | ||
191 | .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level))); | ||
192 | } | ||
193 | Some(()) | ||
194 | } | 171 | } |
195 | 172 | ||
196 | fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> { | 173 | fn update_variant(variant: &ast::Variant) -> Option<()> { |
197 | let name = variant.name()?; | 174 | let name = variant.name()?; |
198 | let tuple_field = make::tuple_field(None, make::ty(&name.text())); | 175 | let tuple_field = make::tuple_field(None, make::ty(&name.text())); |
199 | let replacement = make::variant( | 176 | let replacement = make::variant( |
200 | name, | 177 | name, |
201 | Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))), | 178 | Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))), |
202 | ); | 179 | ) |
203 | rewriter.replace(variant.syntax(), replacement.syntax()); | 180 | .clone_for_update(); |
181 | ted::replace(variant.syntax(), replacement.syntax()); | ||
204 | Some(()) | 182 | Some(()) |
205 | } | 183 | } |
206 | 184 | ||
207 | fn update_reference( | 185 | fn apply_references( |
186 | insert_use_cfg: InsertUseConfig, | ||
187 | segment: ast::PathSegment, | ||
188 | node: SyntaxNode, | ||
189 | import: Option<(ImportScope, hir::ModPath)>, | ||
190 | ) { | ||
191 | if let Some((scope, path)) = import { | ||
192 | insert_use(&scope, mod_path_to_ast(&path), insert_use_cfg); | ||
193 | } | ||
194 | ted::insert_raw( | ||
195 | ted::Position::before(segment.syntax()), | ||
196 | make::path_from_text(&format!("{}", segment)).clone_for_update().syntax(), | ||
197 | ); | ||
198 | ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['('])); | ||
199 | ted::insert_raw(ted::Position::after(&node), make::token(T![')'])); | ||
200 | } | ||
201 | |||
202 | fn process_references( | ||
208 | ctx: &AssistContext, | 203 | ctx: &AssistContext, |
209 | rewriter: &mut SyntaxRewriter, | 204 | visited_modules: &mut FxHashSet<Module>, |
210 | reference: FileReference, | 205 | source_file: &SyntaxNode, |
211 | source_file: &SourceFile, | ||
212 | enum_module_def: &ModuleDef, | 206 | enum_module_def: &ModuleDef, |
213 | variant_hir_name: &Name, | 207 | variant_hir_name: &Name, |
214 | visited_modules_set: &mut FxHashSet<Module>, | 208 | refs: Vec<FileReference>, |
215 | ) -> Option<()> { | 209 | ) -> Vec<(ast::PathSegment, SyntaxNode, Option<(ImportScope, hir::ModPath)>)> { |
210 | // we have to recollect here eagerly as we are about to edit the tree we need to calculate the changes | ||
211 | // and corresponding nodes up front | ||
212 | refs.into_iter() | ||
213 | .flat_map(|reference| { | ||
214 | let (segment, scope_node, module) = | ||
215 | reference_to_node(&ctx.sema, source_file, reference)?; | ||
216 | if !visited_modules.contains(&module) { | ||
217 | let mod_path = module.find_use_path_prefixed( | ||
218 | ctx.sema.db, | ||
219 | *enum_module_def, | ||
220 | ctx.config.insert_use.prefix_kind, | ||
221 | ); | ||
222 | if let Some(mut mod_path) = mod_path { | ||
223 | mod_path.pop_segment(); | ||
224 | mod_path.push_segment(variant_hir_name.clone()); | ||
225 | let scope = ImportScope::find_insert_use_container(&scope_node)?; | ||
226 | visited_modules.insert(module); | ||
227 | return Some((segment, scope_node, Some((scope, mod_path)))); | ||
228 | } | ||
229 | } | ||
230 | Some((segment, scope_node, None)) | ||
231 | }) | ||
232 | .collect() | ||
233 | } | ||
234 | |||
235 | fn reference_to_node( | ||
236 | sema: &hir::Semantics<RootDatabase>, | ||
237 | source_file: &SyntaxNode, | ||
238 | reference: FileReference, | ||
239 | ) -> Option<(ast::PathSegment, SyntaxNode, hir::Module)> { | ||
216 | let offset = reference.range.start(); | 240 | let offset = reference.range.start(); |
217 | let (segment, expr) = if let Some(path_expr) = | 241 | if let Some(path_expr) = find_node_at_offset::<ast::PathExpr>(source_file, offset) { |
218 | find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset) | ||
219 | { | ||
220 | // tuple variant | 242 | // tuple variant |
221 | (path_expr.path()?.segment()?, path_expr.syntax().parent()?) | 243 | Some((path_expr.path()?.segment()?, path_expr.syntax().parent()?)) |
222 | } else if let Some(record_expr) = | 244 | } else if let Some(record_expr) = find_node_at_offset::<ast::RecordExpr>(source_file, offset) { |
223 | find_node_at_offset::<ast::RecordExpr>(source_file.syntax(), offset) | ||
224 | { | ||
225 | // record variant | 245 | // record variant |
226 | (record_expr.path()?.segment()?, record_expr.syntax().clone()) | 246 | Some((record_expr.path()?.segment()?, record_expr.syntax().clone())) |
227 | } else { | 247 | } else { |
228 | return None; | 248 | None |
229 | }; | ||
230 | |||
231 | let module = ctx.sema.scope(&expr).module()?; | ||
232 | if !visited_modules_set.contains(&module) { | ||
233 | if insert_import(ctx, rewriter, &expr, &module, enum_module_def, variant_hir_name).is_some() | ||
234 | { | ||
235 | visited_modules_set.insert(module); | ||
236 | } | ||
237 | } | 249 | } |
238 | rewriter.insert_after(segment.syntax(), &make::token(T!['('])); | 250 | .and_then(|(segment, expr)| { |
239 | rewriter.insert_after(segment.syntax(), segment.syntax()); | 251 | let module = sema.scope(&expr).module()?; |
240 | rewriter.insert_after(&expr, &make::token(T![')'])); | 252 | Some((segment, expr, module)) |
241 | Some(()) | 253 | }) |
242 | } | 254 | } |
243 | 255 | ||
244 | #[cfg(test)] | 256 | #[cfg(test)] |
@@ -345,7 +357,7 @@ mod my_mod { | |||
345 | 357 | ||
346 | pub struct MyField(pub u8, pub u8); | 358 | pub struct MyField(pub u8, pub u8); |
347 | 359 | ||
348 | pub enum MyEnum { | 360 | pub enum MyEnum { |
349 | MyField(MyField), | 361 | MyField(MyField), |
350 | } | 362 | } |
351 | } | 363 | } |
diff --git a/crates/ide_assists/src/handlers/merge_imports.rs b/crates/ide_assists/src/handlers/merge_imports.rs index 8e0794218..add7b8e37 100644 --- a/crates/ide_assists/src/handlers/merge_imports.rs +++ b/crates/ide_assists/src/handlers/merge_imports.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use ide_db::helpers::insert_use::{try_merge_imports, try_merge_trees, MergeBehavior}; | 1 | use ide_db::helpers::merge_imports::{try_merge_imports, try_merge_trees, MergeBehavior}; |
2 | use syntax::{algo::neighbor, ast, ted, AstNode}; | 2 | use syntax::{algo::neighbor, ast, ted, AstNode}; |
3 | 3 | ||
4 | use crate::{ | 4 | use crate::{ |
diff --git a/crates/ide_assists/src/handlers/reorder_fields.rs b/crates/ide_assists/src/handlers/reorder_fields.rs index 1a95135ca..e90bbdbcf 100644 --- a/crates/ide_assists/src/handlers/reorder_fields.rs +++ b/crates/ide_assists/src/handlers/reorder_fields.rs | |||
@@ -83,11 +83,9 @@ fn replace<T: AstNode + PartialEq>( | |||
83 | fields: impl Iterator<Item = T>, | 83 | fields: impl Iterator<Item = T>, |
84 | sorted_fields: impl IntoIterator<Item = T>, | 84 | sorted_fields: impl IntoIterator<Item = T>, |
85 | ) { | 85 | ) { |
86 | fields.zip(sorted_fields).filter(|(field, sorted)| field != sorted).for_each( | 86 | fields.zip(sorted_fields).for_each(|(field, sorted_field)| { |
87 | |(field, sorted_field)| { | 87 | ted::replace(field.syntax(), sorted_field.syntax().clone_for_update()) |
88 | ted::replace(field.syntax(), sorted_field.syntax().clone_for_update()); | 88 | }); |
89 | }, | ||
90 | ); | ||
91 | } | 89 | } |
92 | 90 | ||
93 | fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { | 91 | fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { |
diff --git a/crates/ide_assists/src/handlers/reorder_impl.rs b/crates/ide_assists/src/handlers/reorder_impl.rs index f976e73ad..72d889248 100644 --- a/crates/ide_assists/src/handlers/reorder_impl.rs +++ b/crates/ide_assists/src/handlers/reorder_impl.rs | |||
@@ -4,9 +4,8 @@ use rustc_hash::FxHashMap; | |||
4 | use hir::{PathResolution, Semantics}; | 4 | use hir::{PathResolution, Semantics}; |
5 | use ide_db::RootDatabase; | 5 | use ide_db::RootDatabase; |
6 | use syntax::{ | 6 | use syntax::{ |
7 | algo, | ||
8 | ast::{self, NameOwner}, | 7 | ast::{self, NameOwner}, |
9 | AstNode, | 8 | ted, AstNode, |
10 | }; | 9 | }; |
11 | 10 | ||
12 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 11 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
@@ -75,13 +74,16 @@ pub(crate) fn reorder_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
75 | } | 74 | } |
76 | 75 | ||
77 | let target = items.syntax().text_range(); | 76 | let target = items.syntax().text_range(); |
78 | acc.add(AssistId("reorder_impl", AssistKind::RefactorRewrite), "Sort methods", target, |edit| { | 77 | acc.add( |
79 | let mut rewriter = algo::SyntaxRewriter::default(); | 78 | AssistId("reorder_impl", AssistKind::RefactorRewrite), |
80 | for (old, new) in methods.iter().zip(&sorted) { | 79 | "Sort methods", |
81 | rewriter.replace(old.syntax(), new.syntax()); | 80 | target, |
82 | } | 81 | |builder| { |
83 | edit.rewrite(rewriter); | 82 | methods.into_iter().zip(sorted).for_each(|(old, new)| { |
84 | }) | 83 | ted::replace(builder.make_ast_mut(old).syntax(), new.clone_for_update().syntax()) |
84 | }); | ||
85 | }, | ||
86 | ) | ||
85 | } | 87 | } |
86 | 88 | ||
87 | fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { | 89 | fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { |
diff --git a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs index 36d2e0331..99ba79860 100644 --- a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs +++ b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs | |||
@@ -1,5 +1,5 @@ | |||
1 | use ide_db::helpers::insert_use::{insert_use, ImportScope}; | 1 | use ide_db::helpers::insert_use::{insert_use, ImportScope}; |
2 | use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode}; | 2 | use syntax::{ast, match_ast, ted, AstNode, SyntaxNode}; |
3 | 3 | ||
4 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 4 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
5 | 5 | ||
@@ -31,7 +31,7 @@ pub(crate) fn replace_qualified_name_with_use( | |||
31 | } | 31 | } |
32 | 32 | ||
33 | let target = path.syntax().text_range(); | 33 | let target = path.syntax().text_range(); |
34 | let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?; | 34 | let scope = ImportScope::find_insert_use_container_with_macros(path.syntax(), &ctx.sema)?; |
35 | let syntax = scope.as_syntax_node(); | 35 | let syntax = scope.as_syntax_node(); |
36 | acc.add( | 36 | acc.add( |
37 | AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite), | 37 | AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite), |
@@ -40,18 +40,17 @@ pub(crate) fn replace_qualified_name_with_use( | |||
40 | |builder| { | 40 | |builder| { |
41 | // Now that we've brought the name into scope, re-qualify all paths that could be | 41 | // Now that we've brought the name into scope, re-qualify all paths that could be |
42 | // affected (that is, all paths inside the node we added the `use` to). | 42 | // affected (that is, all paths inside the node we added the `use` to). |
43 | let mut rewriter = SyntaxRewriter::default(); | 43 | let syntax = builder.make_mut(syntax.clone()); |
44 | shorten_paths(&mut rewriter, syntax.clone(), &path); | ||
45 | if let Some(ref import_scope) = ImportScope::from(syntax.clone()) { | 44 | if let Some(ref import_scope) = ImportScope::from(syntax.clone()) { |
46 | rewriter += insert_use(import_scope, path, ctx.config.insert_use); | 45 | shorten_paths(&syntax, &path.clone_for_update()); |
47 | builder.rewrite(rewriter); | 46 | insert_use(import_scope, path, ctx.config.insert_use); |
48 | } | 47 | } |
49 | }, | 48 | }, |
50 | ) | 49 | ) |
51 | } | 50 | } |
52 | 51 | ||
53 | /// Adds replacements to `re` that shorten `path` in all descendants of `node`. | 52 | /// Adds replacements to `re` that shorten `path` in all descendants of `node`. |
54 | fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ast::Path) { | 53 | fn shorten_paths(node: &SyntaxNode, path: &ast::Path) { |
55 | for child in node.children() { | 54 | for child in node.children() { |
56 | match_ast! { | 55 | match_ast! { |
57 | match child { | 56 | match child { |
@@ -60,34 +59,26 @@ fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: | |||
60 | ast::Use(_it) => continue, | 59 | ast::Use(_it) => continue, |
61 | // Don't descend into submodules, they don't have the same `use` items in scope. | 60 | // Don't descend into submodules, they don't have the same `use` items in scope. |
62 | ast::Module(_it) => continue, | 61 | ast::Module(_it) => continue, |
63 | 62 | ast::Path(p) => if maybe_replace_path(p.clone(), path.clone()).is_none() { | |
64 | ast::Path(p) => { | 63 | shorten_paths(p.syntax(), path); |
65 | match maybe_replace_path(rewriter, p.clone(), path.clone()) { | ||
66 | Some(()) => {}, | ||
67 | None => shorten_paths(rewriter, p.syntax().clone(), path), | ||
68 | } | ||
69 | }, | 64 | }, |
70 | _ => shorten_paths(rewriter, child, path), | 65 | _ => shorten_paths(&child, path), |
71 | } | 66 | } |
72 | } | 67 | } |
73 | } | 68 | } |
74 | } | 69 | } |
75 | 70 | ||
76 | fn maybe_replace_path( | 71 | fn maybe_replace_path(path: ast::Path, target: ast::Path) -> Option<()> { |
77 | rewriter: &mut SyntaxRewriter<'static>, | ||
78 | path: ast::Path, | ||
79 | target: ast::Path, | ||
80 | ) -> Option<()> { | ||
81 | if !path_eq(path.clone(), target) { | 72 | if !path_eq(path.clone(), target) { |
82 | return None; | 73 | return None; |
83 | } | 74 | } |
84 | 75 | ||
85 | // Shorten `path`, leaving only its last segment. | 76 | // Shorten `path`, leaving only its last segment. |
86 | if let Some(parent) = path.qualifier() { | 77 | if let Some(parent) = path.qualifier() { |
87 | rewriter.delete(parent.syntax()); | 78 | ted::remove(parent.syntax()); |
88 | } | 79 | } |
89 | if let Some(double_colon) = path.coloncolon_token() { | 80 | if let Some(double_colon) = path.coloncolon_token() { |
90 | rewriter.delete(&double_colon); | 81 | ted::remove(&double_colon); |
91 | } | 82 | } |
92 | 83 | ||
93 | Some(()) | 84 | Some(()) |
@@ -150,6 +141,7 @@ Debug | |||
150 | ", | 141 | ", |
151 | ); | 142 | ); |
152 | } | 143 | } |
144 | |||
153 | #[test] | 145 | #[test] |
154 | fn test_replace_add_use_no_anchor_with_item_below() { | 146 | fn test_replace_add_use_no_anchor_with_item_below() { |
155 | check_assist( | 147 | check_assist( |
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 8996c1b61..88ae5c9a9 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -120,6 +120,7 @@ mod handlers { | |||
120 | mod convert_comment_block; | 120 | mod convert_comment_block; |
121 | mod convert_iter_for_each_to_for; | 121 | mod convert_iter_for_each_to_for; |
122 | mod convert_into_to_from; | 122 | mod convert_into_to_from; |
123 | mod convert_tuple_struct_to_named_struct; | ||
123 | mod early_return; | 124 | mod early_return; |
124 | mod expand_glob_import; | 125 | mod expand_glob_import; |
125 | mod extract_function; | 126 | mod extract_function; |
@@ -190,6 +191,7 @@ mod handlers { | |||
190 | convert_comment_block::convert_comment_block, | 191 | convert_comment_block::convert_comment_block, |
191 | convert_iter_for_each_to_for::convert_iter_for_each_to_for, | 192 | convert_iter_for_each_to_for::convert_iter_for_each_to_for, |
192 | convert_into_to_from::convert_into_to_from, | 193 | convert_into_to_from::convert_into_to_from, |
194 | convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct, | ||
193 | early_return::convert_to_guarded_return, | 195 | early_return::convert_to_guarded_return, |
194 | expand_glob_import::expand_glob_import, | 196 | expand_glob_import::expand_glob_import, |
195 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, | 197 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, |
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs index 49533e7d2..6f4f97361 100644 --- a/crates/ide_assists/src/tests.rs +++ b/crates/ide_assists/src/tests.rs | |||
@@ -4,10 +4,7 @@ use expect_test::expect; | |||
4 | use hir::Semantics; | 4 | use hir::Semantics; |
5 | use ide_db::{ | 5 | use ide_db::{ |
6 | base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}, | 6 | base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}, |
7 | helpers::{ | 7 | helpers::{insert_use::InsertUseConfig, merge_imports::MergeBehavior, SnippetCap}, |
8 | insert_use::{InsertUseConfig, MergeBehavior}, | ||
9 | SnippetCap, | ||
10 | }, | ||
11 | source_change::FileSystemEdit, | 8 | source_change::FileSystemEdit, |
12 | RootDatabase, | 9 | RootDatabase, |
13 | }; | 10 | }; |
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 41559b43a..59bcef8fb 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs | |||
@@ -292,6 +292,47 @@ fn main() { | |||
292 | } | 292 | } |
293 | 293 | ||
294 | #[test] | 294 | #[test] |
295 | fn doctest_convert_tuple_struct_to_named_struct() { | ||
296 | check_doc_test( | ||
297 | "convert_tuple_struct_to_named_struct", | ||
298 | r#####" | ||
299 | struct Point$0(f32, f32); | ||
300 | |||
301 | impl Point { | ||
302 | pub fn new(x: f32, y: f32) -> Self { | ||
303 | Point(x, y) | ||
304 | } | ||
305 | |||
306 | pub fn x(&self) -> f32 { | ||
307 | self.0 | ||
308 | } | ||
309 | |||
310 | pub fn y(&self) -> f32 { | ||
311 | self.1 | ||
312 | } | ||
313 | } | ||
314 | "#####, | ||
315 | r#####" | ||
316 | struct Point { field1: f32, field2: f32 } | ||
317 | |||
318 | impl Point { | ||
319 | pub fn new(x: f32, y: f32) -> Self { | ||
320 | Point { field1: x, field2: y } | ||
321 | } | ||
322 | |||
323 | pub fn x(&self) -> f32 { | ||
324 | self.field1 | ||
325 | } | ||
326 | |||
327 | pub fn y(&self) -> f32 { | ||
328 | self.field2 | ||
329 | } | ||
330 | } | ||
331 | "#####, | ||
332 | ) | ||
333 | } | ||
334 | |||
335 | #[test] | ||
295 | fn doctest_expand_glob_import() { | 336 | fn doctest_expand_glob_import() { |
296 | check_doc_test( | 337 | check_doc_test( |
297 | "expand_glob_import", | 338 | "expand_glob_import", |
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs index d67524937..5a90ad715 100644 --- a/crates/ide_assists/src/utils.rs +++ b/crates/ide_assists/src/utils.rs | |||
@@ -140,7 +140,8 @@ pub fn add_trait_assoc_items_to_impl( | |||
140 | 140 | ||
141 | let items = items | 141 | let items = items |
142 | .into_iter() | 142 | .into_iter() |
143 | .map(|it| ast_transform::apply(&*ast_transform, it)) | 143 | .map(|it| it.clone_for_update()) |
144 | .inspect(|it| ast_transform::apply(&*ast_transform, it)) | ||
144 | .map(|it| match it { | 145 | .map(|it| match it { |
145 | ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)), | 146 | ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)), |
146 | ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()), | 147 | ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()), |