aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/handlers')
-rw-r--r--crates/ide_assists/src/handlers/auto_import.rs43
-rw-r--r--crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs516
-rw-r--r--crates/ide_assists/src/handlers/extract_function.rs278
-rw-r--r--crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs208
-rw-r--r--crates/ide_assists/src/handlers/fill_match_arms.rs370
-rw-r--r--crates/ide_assists/src/handlers/flip_comma.rs18
-rw-r--r--crates/ide_assists/src/handlers/generate_deref.rs227
-rw-r--r--crates/ide_assists/src/handlers/generate_from_impl_for_enum.rs2
-rw-r--r--crates/ide_assists/src/handlers/inline_local_variable.rs223
-rw-r--r--crates/ide_assists/src/handlers/introduce_named_lifetime.rs112
-rw-r--r--crates/ide_assists/src/handlers/merge_imports.rs2
-rw-r--r--crates/ide_assists/src/handlers/remove_dbg.rs2
-rw-r--r--crates/ide_assists/src/handlers/reorder_fields.rs8
-rw-r--r--crates/ide_assists/src/handlers/reorder_impl.rs20
-rw-r--r--crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs108
-rw-r--r--crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs34
16 files changed, 1793 insertions, 378 deletions
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs
index 5ccd7f7a2..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 }
@@ -934,4 +936,37 @@ fn main() {
934", 936",
935 ); 937 );
936 } 938 }
939
940 #[test]
941 fn inner_items() {
942 check_assist(
943 auto_import,
944 r#"
945mod baz {
946 pub struct Foo {}
947}
948
949mod bar {
950 fn bar() {
951 Foo$0;
952 println!("Hallo");
953 }
954}
955"#,
956 r#"
957mod baz {
958 pub struct Foo {}
959}
960
961mod bar {
962 use crate::baz::Foo;
963
964 fn bar() {
965 Foo;
966 println!("Hallo");
967 }
968}
969"#,
970 );
971 }
937} 972}
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 @@
1use ide_db::defs::{Definition, NameRefClass};
2use syntax::{
3 ast::{self, AstNode, GenericParamsOwner, VisibilityOwner},
4 match_ast, SyntaxNode,
5};
6
7use 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// ```
48pub(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
73fn 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
103fn 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
181fn 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
205fn 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)]
210mod 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#"
229struct Inner;
230struct A$0(Inner);
231
232impl 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#"
246struct Inner;
247struct A { field1: Inner }
248
249impl 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#"
270struct Inner;
271struct A$0(Inner);
272
273impl 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#"
287struct Inner;
288struct A { field1: Inner }
289
290impl 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#"
311struct Inner;
312struct A$0(Inner);
313
314impl 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#"
326struct Inner;
327struct A { field1: Inner }
328
329impl 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#"
348struct A$0(pub u32, pub(crate) u64);
349
350impl 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#"
364struct A { pub field1: u32, pub(crate) field2: u64 }
365
366impl 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#"
387struct Inner$0(u32);
388struct Outer(Inner);
389
390impl 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#"
405struct Inner { field1: u32 }
406struct Outer(Inner);
407
408impl 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#"
427struct Inner(u32);
428struct Outer$0(Inner);
429
430impl 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#"
445struct Inner(u32);
446struct Outer { field1: Inner }
447
448impl 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
471struct Inner;
472struct A$0(Inner);
473
474mod foo;
475
476//- /foo.rs
477use crate::{A, Inner};
478fn f() {
479 let a = A(Inner);
480}
481"#,
482 r#"
483//- /main.rs
484struct Inner;
485struct A { field1: Inner }
486
487mod foo;
488
489//- /foo.rs
490use crate::{A, Inner};
491fn 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#"
503struct Wrap$0<T>(T)
504where
505 T: Display;
506"#,
507 r#"
508struct Wrap<T>
509where
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 5fdc8bf38..b30652a9d 100644
--- a/crates/ide_assists/src/handlers/extract_function.rs
+++ b/crates/ide_assists/src/handlers/extract_function.rs
@@ -75,7 +75,8 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
75 let insert_after = scope_for_fn_insertion(&body, anchor)?; 75 let insert_after = scope_for_fn_insertion(&body, anchor)?;
76 let module = ctx.sema.scope(&insert_after).module()?; 76 let module = ctx.sema.scope(&insert_after).module()?;
77 77
78 let vars_defined_in_body_and_outlive = vars_defined_in_body_and_outlive(ctx, &body); 78 let vars_defined_in_body_and_outlive =
79 vars_defined_in_body_and_outlive(ctx, &body, &node.parent().as_ref().unwrap_or(&node));
79 let ret_ty = body_return_ty(ctx, &body)?; 80 let ret_ty = body_return_ty(ctx, &body)?;
80 81
81 // FIXME: we compute variables that outlive here just to check `never!` condition 82 // FIXME: we compute variables that outlive here just to check `never!` condition
@@ -257,7 +258,7 @@ struct Function {
257 control_flow: ControlFlow, 258 control_flow: ControlFlow,
258 ret_ty: RetType, 259 ret_ty: RetType,
259 body: FunctionBody, 260 body: FunctionBody,
260 vars_defined_in_body_and_outlive: Vec<Local>, 261 vars_defined_in_body_and_outlive: Vec<OutlivedLocal>,
261} 262}
262 263
263#[derive(Debug)] 264#[derive(Debug)]
@@ -296,9 +297,9 @@ impl Function {
296 RetType::Expr(ty) => FunType::Single(ty.clone()), 297 RetType::Expr(ty) => FunType::Single(ty.clone()),
297 RetType::Stmt => match self.vars_defined_in_body_and_outlive.as_slice() { 298 RetType::Stmt => match self.vars_defined_in_body_and_outlive.as_slice() {
298 [] => FunType::Unit, 299 [] => FunType::Unit,
299 [var] => FunType::Single(var.ty(ctx.db())), 300 [var] => FunType::Single(var.local.ty(ctx.db())),
300 vars => { 301 vars => {
301 let types = vars.iter().map(|v| v.ty(ctx.db())).collect(); 302 let types = vars.iter().map(|v| v.local.ty(ctx.db())).collect();
302 FunType::Tuple(types) 303 FunType::Tuple(types)
303 } 304 }
304 }, 305 },
@@ -562,6 +563,12 @@ impl HasTokenAtOffset for FunctionBody {
562 } 563 }
563} 564}
564 565
566#[derive(Debug)]
567struct OutlivedLocal {
568 local: Local,
569 mut_usage_outside_body: bool,
570}
571
565/// Try to guess what user wants to extract 572/// Try to guess what user wants to extract
566/// 573///
567/// We have basically have two cases: 574/// We have basically have two cases:
@@ -592,7 +599,12 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu
592 // we have selected a few statements in a block 599 // we have selected a few statements in a block
593 // so covering_element returns the whole block 600 // so covering_element returns the whole block
594 if node.kind() == BLOCK_EXPR { 601 if node.kind() == BLOCK_EXPR {
595 let body = FunctionBody::from_range(node.clone(), selection_range); 602 // Extract the full statements.
603 let statements_range = node
604 .children()
605 .filter(|c| selection_range.intersect(c.text_range()).is_some())
606 .fold(selection_range, |acc, c| acc.cover(c.text_range()));
607 let body = FunctionBody::from_range(node.clone(), statements_range);
596 if body.is_some() { 608 if body.is_some() {
597 return body; 609 return body;
598 } 610 }
@@ -603,7 +615,8 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu
603 // so we try to expand covering_element to parent and repeat the previous 615 // so we try to expand covering_element to parent and repeat the previous
604 if let Some(parent) = node.parent() { 616 if let Some(parent) = node.parent() {
605 if parent.kind() == BLOCK_EXPR { 617 if parent.kind() == BLOCK_EXPR {
606 let body = FunctionBody::from_range(parent, selection_range); 618 // Extract the full statement.
619 let body = FunctionBody::from_range(parent, node.text_range());
607 if body.is_some() { 620 if body.is_some() {
608 return body; 621 return body;
609 } 622 }
@@ -707,10 +720,10 @@ fn has_exclusive_usages(ctx: &AssistContext, usages: &LocalUsages, body: &Functi
707 .any(|reference| reference_is_exclusive(reference, body, ctx)) 720 .any(|reference| reference_is_exclusive(reference, body, ctx))
708} 721}
709 722
710/// checks if this reference requires `&mut` access inside body 723/// checks if this reference requires `&mut` access inside node
711fn reference_is_exclusive( 724fn reference_is_exclusive(
712 reference: &FileReference, 725 reference: &FileReference,
713 body: &FunctionBody, 726 node: &dyn HasTokenAtOffset,
714 ctx: &AssistContext, 727 ctx: &AssistContext,
715) -> bool { 728) -> bool {
716 // we directly modify variable with set: `n = 0`, `n += 1` 729 // we directly modify variable with set: `n = 0`, `n += 1`
@@ -719,7 +732,7 @@ fn reference_is_exclusive(
719 } 732 }
720 733
721 // we take `&mut` reference to variable: `&mut v` 734 // we take `&mut` reference to variable: `&mut v`
722 let path = match path_element_of_reference(body, reference) { 735 let path = match path_element_of_reference(node, reference) {
723 Some(path) => path, 736 Some(path) => path,
724 None => return false, 737 None => return false,
725 }; 738 };
@@ -729,6 +742,14 @@ fn reference_is_exclusive(
729 742
730/// checks if this expr requires `&mut` access, recurses on field access 743/// checks if this expr requires `&mut` access, recurses on field access
731fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> { 744fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> {
745 match expr {
746 ast::Expr::MacroCall(_) => {
747 // FIXME: expand macro and check output for mutable usages of the variable?
748 return None;
749 }
750 _ => (),
751 }
752
732 let parent = expr.syntax().parent()?; 753 let parent = expr.syntax().parent()?;
733 754
734 if let Some(bin_expr) = ast::BinExpr::cast(parent.clone()) { 755 if let Some(bin_expr) = ast::BinExpr::cast(parent.clone()) {
@@ -787,7 +808,7 @@ impl HasTokenAtOffset for SyntaxNode {
787 } 808 }
788} 809}
789 810
790/// find relevant `ast::PathExpr` for reference 811/// find relevant `ast::Expr` for reference
791/// 812///
792/// # Preconditions 813/// # Preconditions
793/// 814///
@@ -804,7 +825,11 @@ fn path_element_of_reference(
804 stdx::never!(false, "cannot find path parent of variable usage: {:?}", token); 825 stdx::never!(false, "cannot find path parent of variable usage: {:?}", token);
805 None 826 None
806 })?; 827 })?;
807 stdx::always!(matches!(path, ast::Expr::PathExpr(_))); 828 stdx::always!(
829 matches!(path, ast::Expr::PathExpr(_) | ast::Expr::MacroCall(_)),
830 "unexpected expression type for variable usage: {:?}",
831 path
832 );
808 Some(path) 833 Some(path)
809} 834}
810 835
@@ -820,10 +845,16 @@ fn vars_defined_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local>
820} 845}
821 846
822/// list local variables defined inside `body` that should be returned from extracted function 847/// list local variables defined inside `body` that should be returned from extracted function
823fn vars_defined_in_body_and_outlive(ctx: &AssistContext, body: &FunctionBody) -> Vec<Local> { 848fn vars_defined_in_body_and_outlive(
824 let mut vars_defined_in_body = vars_defined_in_body(&body, ctx); 849 ctx: &AssistContext,
825 vars_defined_in_body.retain(|var| var_outlives_body(ctx, body, var)); 850 body: &FunctionBody,
851 parent: &SyntaxNode,
852) -> Vec<OutlivedLocal> {
853 let vars_defined_in_body = vars_defined_in_body(&body, ctx);
826 vars_defined_in_body 854 vars_defined_in_body
855 .into_iter()
856 .filter_map(|var| var_outlives_body(ctx, body, var, parent))
857 .collect()
827} 858}
828 859
829/// checks if the relevant local was defined before(outside of) body 860/// checks if the relevant local was defined before(outside of) body
@@ -843,11 +874,23 @@ fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode {
843 } 874 }
844} 875}
845 876
846/// checks if local variable is used after(outside of) body 877/// returns usage details if local variable is used after(outside of) body
847fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool { 878fn var_outlives_body(
848 let usages = LocalUsages::find(ctx, *var); 879 ctx: &AssistContext,
880 body: &FunctionBody,
881 var: Local,
882 parent: &SyntaxNode,
883) -> Option<OutlivedLocal> {
884 let usages = LocalUsages::find(ctx, var);
849 let has_usages = usages.iter().any(|reference| body.preceedes_range(reference.range)); 885 let has_usages = usages.iter().any(|reference| body.preceedes_range(reference.range));
850 has_usages 886 if !has_usages {
887 return None;
888 }
889 let has_mut_usages = usages
890 .iter()
891 .filter(|reference| body.preceedes_range(reference.range))
892 .any(|reference| reference_is_exclusive(reference, parent, ctx));
893 Some(OutlivedLocal { local: var, mut_usage_outside_body: has_mut_usages })
851} 894}
852 895
853fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> { 896fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> {
@@ -927,16 +970,25 @@ fn format_replacement(ctx: &AssistContext, fun: &Function, indent: IndentLevel)
927 let mut buf = String::new(); 970 let mut buf = String::new();
928 match fun.vars_defined_in_body_and_outlive.as_slice() { 971 match fun.vars_defined_in_body_and_outlive.as_slice() {
929 [] => {} 972 [] => {}
930 [var] => format_to!(buf, "let {} = ", var.name(ctx.db()).unwrap()), 973 [var] => {
974 format_to!(buf, "let {}{} = ", mut_modifier(var), var.local.name(ctx.db()).unwrap())
975 }
931 [v0, vs @ ..] => { 976 [v0, vs @ ..] => {
932 buf.push_str("let ("); 977 buf.push_str("let (");
933 format_to!(buf, "{}", v0.name(ctx.db()).unwrap()); 978 format_to!(buf, "{}{}", mut_modifier(v0), v0.local.name(ctx.db()).unwrap());
934 for var in vs { 979 for var in vs {
935 format_to!(buf, ", {}", var.name(ctx.db()).unwrap()); 980 format_to!(buf, ", {}{}", mut_modifier(var), var.local.name(ctx.db()).unwrap());
936 } 981 }
937 buf.push_str(") = "); 982 buf.push_str(") = ");
938 } 983 }
939 } 984 }
985 fn mut_modifier(var: &OutlivedLocal) -> &'static str {
986 if var.mut_usage_outside_body {
987 "mut "
988 } else {
989 ""
990 }
991 }
940 format_to!(buf, "{}", expr); 992 format_to!(buf, "{}", expr);
941 if fun.ret_ty.is_unit() 993 if fun.ret_ty.is_unit()
942 && (!fun.vars_defined_in_body_and_outlive.is_empty() || !expr.is_block_like()) 994 && (!fun.vars_defined_in_body_and_outlive.is_empty() || !expr.is_block_like())
@@ -1131,7 +1183,7 @@ fn make_ret_ty(ctx: &AssistContext, module: hir::Module, fun: &Function) -> Opti
1131 } 1183 }
1132 FlowHandler::Try { kind: TryKind::Result { ty: parent_ret_ty } } => { 1184 FlowHandler::Try { kind: TryKind::Result { ty: parent_ret_ty } } => {
1133 let handler_ty = parent_ret_ty 1185 let handler_ty = parent_ret_ty
1134 .type_parameters() 1186 .type_arguments()
1135 .nth(1) 1187 .nth(1)
1136 .map(|ty| make_ty(&ty, ctx, module)) 1188 .map(|ty| make_ty(&ty, ctx, module))
1137 .unwrap_or_else(make::ty_unit); 1189 .unwrap_or_else(make::ty_unit);
@@ -1175,9 +1227,19 @@ fn make_body(
1175 FunctionBody::Expr(expr) => { 1227 FunctionBody::Expr(expr) => {
1176 let expr = rewrite_body_segment(ctx, &fun.params, &handler, expr.syntax()); 1228 let expr = rewrite_body_segment(ctx, &fun.params, &handler, expr.syntax());
1177 let expr = ast::Expr::cast(expr).unwrap(); 1229 let expr = ast::Expr::cast(expr).unwrap();
1178 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));
1179 1239
1180 make::block_expr(Vec::new(), Some(expr)) 1240 make::block_expr(Vec::new(), Some(expr))
1241 }
1242 }
1181 } 1243 }
1182 FunctionBody::Span { parent, text_range } => { 1244 FunctionBody::Span { parent, text_range } => {
1183 let mut elements: Vec<_> = parent 1245 let mut elements: Vec<_> = parent
@@ -1199,10 +1261,10 @@ fn make_body(
1199 match fun.vars_defined_in_body_and_outlive.as_slice() { 1261 match fun.vars_defined_in_body_and_outlive.as_slice() {
1200 [] => {} 1262 [] => {}
1201 [var] => { 1263 [var] => {
1202 tail_expr = Some(path_expr_from_local(ctx, *var)); 1264 tail_expr = Some(path_expr_from_local(ctx, var.local));
1203 } 1265 }
1204 vars => { 1266 vars => {
1205 let exprs = vars.iter().map(|var| path_expr_from_local(ctx, *var)); 1267 let exprs = vars.iter().map(|var| path_expr_from_local(ctx, var.local));
1206 let expr = make::expr_tuple(exprs); 1268 let expr = make::expr_tuple(exprs);
1207 tail_expr = Some(expr); 1269 tail_expr = Some(expr);
1208 } 1270 }
@@ -1492,7 +1554,7 @@ fn foo() {
1492} 1554}
1493 1555
1494fn $0fun_name() -> i32 { 1556fn $0fun_name() -> i32 {
1495 { 1 + 1 } 1557 1 + 1
1496}"#, 1558}"#,
1497 ); 1559 );
1498 } 1560 }
@@ -1739,6 +1801,60 @@ fn $0fun_name() -> i32 {
1739 } 1801 }
1740 1802
1741 #[test] 1803 #[test]
1804 fn extract_partial_block_single_line() {
1805 check_assist(
1806 extract_function,
1807 r#"
1808fn foo() {
1809 let n = 1;
1810 let mut v = $0n * n;$0
1811 v += 1;
1812}"#,
1813 r#"
1814fn foo() {
1815 let n = 1;
1816 let mut v = fun_name(n);
1817 v += 1;
1818}
1819
1820fn $0fun_name(n: i32) -> i32 {
1821 let mut v = n * n;
1822 v
1823}"#,
1824 );
1825 }
1826
1827 #[test]
1828 fn extract_partial_block() {
1829 check_assist(
1830 extract_function,
1831 r#"
1832fn foo() {
1833 let m = 2;
1834 let n = 1;
1835 let mut v = m $0* n;
1836 let mut w = 3;$0
1837 v += 1;
1838 w += 1;
1839}"#,
1840 r#"
1841fn foo() {
1842 let m = 2;
1843 let n = 1;
1844 let (mut v, mut w) = fun_name(m, n);
1845 v += 1;
1846 w += 1;
1847}
1848
1849fn $0fun_name(m: i32, n: i32) -> (i32, i32) {
1850 let mut v = m * n;
1851 let mut w = 3;
1852 (v, w)
1853}"#,
1854 );
1855 }
1856
1857 #[test]
1742 fn argument_form_expr() { 1858 fn argument_form_expr() {
1743 check_assist( 1859 check_assist(
1744 extract_function, 1860 extract_function,
@@ -2111,6 +2227,30 @@ fn $0fun_name(n: i32) -> i32 {
2111 } 2227 }
2112 2228
2113 #[test] 2229 #[test]
2230 fn variable_defined_inside_and_used_after_mutably_no_ret() {
2231 check_assist(
2232 extract_function,
2233 r"
2234fn foo() {
2235 let n = 1;
2236 $0let mut k = n * n;$0
2237 k += 1;
2238}",
2239 r"
2240fn foo() {
2241 let n = 1;
2242 let mut k = fun_name(n);
2243 k += 1;
2244}
2245
2246fn $0fun_name(n: i32) -> i32 {
2247 let mut k = n * n;
2248 k
2249}",
2250 );
2251 }
2252
2253 #[test]
2114 fn two_variables_defined_inside_and_used_after_no_ret() { 2254 fn two_variables_defined_inside_and_used_after_no_ret() {
2115 check_assist( 2255 check_assist(
2116 extract_function, 2256 extract_function,
@@ -2137,6 +2277,38 @@ fn $0fun_name(n: i32) -> (i32, i32) {
2137 } 2277 }
2138 2278
2139 #[test] 2279 #[test]
2280 fn multi_variables_defined_inside_and_used_after_mutably_no_ret() {
2281 check_assist(
2282 extract_function,
2283 r"
2284fn foo() {
2285 let n = 1;
2286 $0let mut k = n * n;
2287 let mut m = k + 2;
2288 let mut o = m + 3;
2289 o += 1;$0
2290 k += o;
2291 m = 1;
2292}",
2293 r"
2294fn foo() {
2295 let n = 1;
2296 let (mut k, mut m, o) = fun_name(n);
2297 k += o;
2298 m = 1;
2299}
2300
2301fn $0fun_name(n: i32) -> (i32, i32, i32) {
2302 let mut k = n * n;
2303 let mut m = k + 2;
2304 let mut o = m + 3;
2305 o += 1;
2306 (k, m, o)
2307}",
2308 );
2309 }
2310
2311 #[test]
2140 fn nontrivial_patterns_define_variables() { 2312 fn nontrivial_patterns_define_variables() {
2141 check_assist( 2313 check_assist(
2142 extract_function, 2314 extract_function,
@@ -2364,17 +2536,15 @@ fn foo() {
2364} 2536}
2365 2537
2366fn $0fun_name(n: &mut i32) { 2538fn $0fun_name(n: &mut i32) {
2367 { 2539 *n += *n;
2368 *n += *n; 2540 bar(*n);
2369 bar(*n); 2541 bar(*n+1);
2370 bar(*n+1); 2542 bar(*n**n);
2371 bar(*n**n); 2543 bar(&*n);
2372 bar(&*n); 2544 n.inc();
2373 n.inc(); 2545 let v = n;
2374 let v = n; 2546 *v = v.succ();
2375 *v = v.succ(); 2547 n.succ();
2376 n.succ();
2377 }
2378}", 2548}",
2379 ); 2549 );
2380 } 2550 }
@@ -3372,4 +3542,36 @@ fn foo() -> Result<(), i64> {
3372}"##, 3542}"##,
3373 ); 3543 );
3374 } 3544 }
3545
3546 #[test]
3547 fn param_usage_in_macro() {
3548 check_assist(
3549 extract_function,
3550 r"
3551macro_rules! m {
3552 ($val:expr) => { $val };
3553}
3554
3555fn foo() {
3556 let n = 1;
3557 $0let k = n * m!(n);$0
3558 let m = k + 1;
3559}",
3560 r"
3561macro_rules! m {
3562 ($val:expr) => { $val };
3563}
3564
3565fn foo() {
3566 let n = 1;
3567 let k = fun_name(n);
3568 let m = k + 1;
3569}
3570
3571fn $0fun_name(n: i32) -> i32 {
3572 let k = n * m!(n);
3573 k
3574}",
3575 );
3576 }
3375} 3577}
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};
5use ide_db::{ 5use 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};
14use rustc_hash::FxHashSet; 14use rustc_hash::FxHashSet;
15use syntax::{ 15use 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
21use crate::{AssistContext, AssistId, AssistKind, Assists}; 21use 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
139fn insert_import( 149fn 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
159fn 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
196fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> { 173fn 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
207fn update_reference( 185fn 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
202fn 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
235fn 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 { 360pub enum MyEnum {
349 MyField(MyField), 361 MyField(MyField),
350 } 362 }
351 } 363 }
diff --git a/crates/ide_assists/src/handlers/fill_match_arms.rs b/crates/ide_assists/src/handlers/fill_match_arms.rs
index 878b3a3fa..be927cc1c 100644
--- a/crates/ide_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ide_assists/src/handlers/fill_match_arms.rs
@@ -1,5 +1,6 @@
1use std::iter; 1use std::iter;
2 2
3use either::Either;
3use hir::{Adt, HasSource, ModuleDef, Semantics}; 4use hir::{Adt, HasSource, ModuleDef, Semantics};
4use ide_db::helpers::{mod_path_to_ast, FamousDefs}; 5use ide_db::helpers::{mod_path_to_ast, FamousDefs};
5use ide_db::RootDatabase; 6use ide_db::RootDatabase;
@@ -7,7 +8,7 @@ use itertools::Itertools;
7use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; 8use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
8 9
9use crate::{ 10use crate::{
10 utils::{does_pat_match_variant, render_snippet, Cursor}, 11 utils::{self, render_snippet, Cursor},
11 AssistContext, AssistId, AssistKind, Assists, 12 AssistContext, AssistId, AssistKind, Assists,
12}; 13};
13 14
@@ -48,6 +49,18 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
48 } 49 }
49 } 50 }
50 51
52 let top_lvl_pats: Vec<_> = arms
53 .iter()
54 .filter_map(ast::MatchArm::pat)
55 .flat_map(|pat| match pat {
56 // Special case OrPat as separate top-level pats
57 Pat::OrPat(or_pat) => Either::Left(or_pat.pats()),
58 _ => Either::Right(iter::once(pat)),
59 })
60 // Exclude top level wildcards so that they are expanded by this assist, retains status quo in #8129.
61 .filter(|pat| !matches!(pat, Pat::WildcardPat(_)))
62 .collect();
63
51 let module = ctx.sema.scope(expr.syntax()).module()?; 64 let module = ctx.sema.scope(expr.syntax()).module()?;
52 65
53 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { 66 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
@@ -56,27 +69,20 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
56 let mut variants = variants 69 let mut variants = variants
57 .into_iter() 70 .into_iter()
58 .filter_map(|variant| build_pat(ctx.db(), module, variant)) 71 .filter_map(|variant| build_pat(ctx.db(), module, variant))
59 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) 72 .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat))
60 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) 73 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
61 .collect::<Vec<_>>(); 74 .collect::<Vec<_>>();
62 if Some(enum_def) == FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option() { 75 if Some(enum_def)
76 == FamousDefs(&ctx.sema, Some(module.krate()))
77 .core_option_Option()
78 .map(|x| lift_enum(x))
79 {
63 // Match `Some` variant first. 80 // Match `Some` variant first.
64 cov_mark::hit!(option_order); 81 cov_mark::hit!(option_order);
65 variants.reverse() 82 variants.reverse()
66 } 83 }
67 variants 84 variants
68 } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) { 85 } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
69 // Partial fill not currently supported for tuple of enums.
70 if !arms.is_empty() {
71 return None;
72 }
73
74 // We do not currently support filling match arms for a tuple
75 // containing a single enum.
76 if enum_defs.len() < 2 {
77 return None;
78 }
79
80 // When calculating the match arms for a tuple of enums, we want 86 // When calculating the match arms for a tuple of enums, we want
81 // to create a match arm for each possible combination of enum 87 // to create a match arm for each possible combination of enum
82 // values. The `multi_cartesian_product` method transforms 88 // values. The `multi_cartesian_product` method transforms
@@ -91,7 +97,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
91 variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant)); 97 variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant));
92 ast::Pat::from(make::tuple_pat(patterns)) 98 ast::Pat::from(make::tuple_pat(patterns))
93 }) 99 })
94 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) 100 .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat))
95 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) 101 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
96 .collect() 102 .collect()
97 } else { 103 } else {
@@ -134,61 +140,114 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
134 ) 140 )
135} 141}
136 142
137fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool { 143fn is_variant_missing(existing_pats: &[Pat], var: &Pat) -> bool {
138 existing_arms.iter().filter_map(|arm| arm.pat()).all(|pat| { 144 !existing_pats.iter().any(|pat| does_pat_match_variant(pat, var))
139 // Special casee OrPat as separate top-level pats 145}
140 let top_level_pats: Vec<Pat> = match pat {
141 Pat::OrPat(pats) => pats.pats().collect::<Vec<_>>(),
142 _ => vec![pat],
143 };
144 146
145 !top_level_pats.iter().any(|pat| does_pat_match_variant(pat, var)) 147// Fixme: this is still somewhat limited, use hir_ty::diagnostics::match_check?
146 }) 148fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
149 match (pat, var) {
150 (Pat::WildcardPat(_), _) => true,
151 (Pat::TuplePat(tpat), Pat::TuplePat(tvar)) => {
152 tpat.fields().zip(tvar.fields()).all(|(p, v)| does_pat_match_variant(&p, &v))
153 }
154 _ => utils::does_pat_match_variant(pat, var),
155 }
156}
157
158#[derive(Eq, PartialEq, Clone)]
159enum ExtendedEnum {
160 Bool,
161 Enum(hir::Enum),
162}
163
164#[derive(Eq, PartialEq, Clone)]
165enum ExtendedVariant {
166 True,
167 False,
168 Variant(hir::Variant),
169}
170
171fn lift_enum(e: hir::Enum) -> ExtendedEnum {
172 ExtendedEnum::Enum(e)
173}
174
175impl ExtendedEnum {
176 fn variants(&self, db: &RootDatabase) -> Vec<ExtendedVariant> {
177 match self {
178 ExtendedEnum::Enum(e) => {
179 e.variants(db).into_iter().map(|x| ExtendedVariant::Variant(x)).collect::<Vec<_>>()
180 }
181 ExtendedEnum::Bool => {
182 Vec::<ExtendedVariant>::from([ExtendedVariant::True, ExtendedVariant::False])
183 }
184 }
185 }
147} 186}
148 187
149fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> { 188fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<ExtendedEnum> {
150 sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() { 189 sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
151 Some(Adt::Enum(e)) => Some(e), 190 Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)),
152 _ => None, 191 _ => {
192 if ty.is_bool() {
193 Some(ExtendedEnum::Bool)
194 } else {
195 None
196 }
197 }
153 }) 198 })
154} 199}
155 200
156fn resolve_tuple_of_enum_def( 201fn resolve_tuple_of_enum_def(
157 sema: &Semantics<RootDatabase>, 202 sema: &Semantics<RootDatabase>,
158 expr: &ast::Expr, 203 expr: &ast::Expr,
159) -> Option<Vec<hir::Enum>> { 204) -> Option<Vec<ExtendedEnum>> {
160 sema.type_of_expr(&expr)? 205 sema.type_of_expr(&expr)?
161 .tuple_fields(sema.db) 206 .tuple_fields(sema.db)
162 .iter() 207 .iter()
163 .map(|ty| { 208 .map(|ty| {
164 ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() { 209 ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
165 Some(Adt::Enum(e)) => Some(e), 210 Some(Adt::Enum(e)) => Some(lift_enum(e)),
166 // For now we only handle expansion for a tuple of enums. Here 211 // For now we only handle expansion for a tuple of enums. Here
167 // we map non-enum items to None and rely on `collect` to 212 // we map non-enum items to None and rely on `collect` to
168 // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>. 213 // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
169 _ => None, 214 _ => {
215 if ty.is_bool() {
216 Some(ExtendedEnum::Bool)
217 } else {
218 None
219 }
220 }
170 }) 221 })
171 }) 222 })
172 .collect() 223 .collect()
173} 224}
174 225
175fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::Variant) -> Option<ast::Pat> { 226fn build_pat(db: &RootDatabase, module: hir::Module, var: ExtendedVariant) -> Option<ast::Pat> {
176 let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?); 227 match var {
228 ExtendedVariant::Variant(var) => {
229 let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?);
230
231 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
232 let pat: ast::Pat = match var.source(db)?.value.kind() {
233 ast::StructKind::Tuple(field_list) => {
234 let pats =
235 iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count());
236 make::tuple_struct_pat(path, pats).into()
237 }
238 ast::StructKind::Record(field_list) => {
239 let pats =
240 field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into());
241 make::record_pat(path, pats).into()
242 }
243 ast::StructKind::Unit => make::path_pat(path),
244 };
177 245
178 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though 246 Some(pat)
179 let pat: ast::Pat = match var.source(db)?.value.kind() {
180 ast::StructKind::Tuple(field_list) => {
181 let pats = iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count());
182 make::tuple_struct_pat(path, pats).into()
183 }
184 ast::StructKind::Record(field_list) => {
185 let pats = field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into());
186 make::record_pat(path, pats).into()
187 } 247 }
188 ast::StructKind::Unit => make::path_pat(path), 248 ExtendedVariant::True => Some(ast::Pat::from(make::literal_pat("true"))),
189 }; 249 ExtendedVariant::False => Some(ast::Pat::from(make::literal_pat("false"))),
190 250 }
191 Some(pat)
192} 251}
193 252
194#[cfg(test)] 253#[cfg(test)]
@@ -221,6 +280,21 @@ mod tests {
221 } 280 }
222 281
223 #[test] 282 #[test]
283 fn all_boolean_match_arms_provided() {
284 check_assist_not_applicable(
285 fill_match_arms,
286 r#"
287 fn foo(a: bool) {
288 match a$0 {
289 true => {}
290 false => {}
291 }
292 }
293 "#,
294 )
295 }
296
297 #[test]
224 fn tuple_of_non_enum() { 298 fn tuple_of_non_enum() {
225 // for now this case is not handled, although it potentially could be 299 // for now this case is not handled, although it potentially could be
226 // in the future 300 // in the future
@@ -236,6 +310,113 @@ mod tests {
236 } 310 }
237 311
238 #[test] 312 #[test]
313 fn fill_match_arms_boolean() {
314 check_assist(
315 fill_match_arms,
316 r#"
317 fn foo(a: bool) {
318 match a$0 {
319 }
320 }
321 "#,
322 r#"
323 fn foo(a: bool) {
324 match a {
325 $0true => {}
326 false => {}
327 }
328 }
329 "#,
330 )
331 }
332
333 #[test]
334 fn partial_fill_boolean() {
335 check_assist(
336 fill_match_arms,
337 r#"
338 fn foo(a: bool) {
339 match a$0 {
340 true => {}
341 }
342 }
343 "#,
344 r#"
345 fn foo(a: bool) {
346 match a {
347 true => {}
348 $0false => {}
349 }
350 }
351 "#,
352 )
353 }
354
355 #[test]
356 fn all_boolean_tuple_arms_provided() {
357 check_assist_not_applicable(
358 fill_match_arms,
359 r#"
360 fn foo(a: bool) {
361 match (a, a)$0 {
362 (true, true) => {}
363 (true, false) => {}
364 (false, true) => {}
365 (false, false) => {}
366 }
367 }
368 "#,
369 )
370 }
371
372 #[test]
373 fn fill_boolean_tuple() {
374 check_assist(
375 fill_match_arms,
376 r#"
377 fn foo(a: bool) {
378 match (a, a)$0 {
379 }
380 }
381 "#,
382 r#"
383 fn foo(a: bool) {
384 match (a, a) {
385 $0(true, true) => {}
386 (true, false) => {}
387 (false, true) => {}
388 (false, false) => {}
389 }
390 }
391 "#,
392 )
393 }
394
395 #[test]
396 fn partial_fill_boolean_tuple() {
397 check_assist(
398 fill_match_arms,
399 r#"
400 fn foo(a: bool) {
401 match (a, a)$0 {
402 (false, true) => {}
403 }
404 }
405 "#,
406 r#"
407 fn foo(a: bool) {
408 match (a, a) {
409 (false, true) => {}
410 $0(true, true) => {}
411 (true, false) => {}
412 (false, false) => {}
413 }
414 }
415 "#,
416 )
417 }
418
419 #[test]
239 fn partial_fill_record_tuple() { 420 fn partial_fill_record_tuple() {
240 check_assist( 421 check_assist(
241 fill_match_arms, 422 fill_match_arms,
@@ -473,20 +654,81 @@ fn main() {
473 654
474 #[test] 655 #[test]
475 fn fill_match_arms_tuple_of_enum_partial() { 656 fn fill_match_arms_tuple_of_enum_partial() {
476 check_assist_not_applicable( 657 check_assist(
477 fill_match_arms, 658 fill_match_arms,
478 r#" 659 r#"
479 enum A { One, Two } 660enum A { One, Two }
480 enum B { One, Two } 661enum B { One, Two }
481 662
482 fn main() { 663fn main() {
483 let a = A::One; 664 let a = A::One;
484 let b = B::One; 665 let b = B::One;
485 match (a$0, b) { 666 match (a$0, b) {
486 (A::Two, B::One) => {} 667 (A::Two, B::One) => {}
487 } 668 }
488 } 669}
489 "#, 670"#,
671 r#"
672enum A { One, Two }
673enum B { One, Two }
674
675fn main() {
676 let a = A::One;
677 let b = B::One;
678 match (a, b) {
679 (A::Two, B::One) => {}
680 $0(A::One, B::One) => {}
681 (A::One, B::Two) => {}
682 (A::Two, B::Two) => {}
683 }
684}
685"#,
686 );
687 }
688
689 #[test]
690 fn fill_match_arms_tuple_of_enum_partial_with_wildcards() {
691 let ra_fixture = r#"
692fn main() {
693 let a = Some(1);
694 let b = Some(());
695 match (a$0, b) {
696 (Some(_), _) => {}
697 (None, Some(_)) => {}
698 }
699}
700"#;
701 check_assist(
702 fill_match_arms,
703 &format!("//- /main.rs crate:main deps:core{}{}", ra_fixture, FamousDefs::FIXTURE),
704 r#"
705fn main() {
706 let a = Some(1);
707 let b = Some(());
708 match (a, b) {
709 (Some(_), _) => {}
710 (None, Some(_)) => {}
711 $0(None, None) => {}
712 }
713}
714"#,
715 );
716 }
717
718 #[test]
719 fn fill_match_arms_partial_with_deep_pattern() {
720 // Fixme: cannot handle deep patterns
721 let ra_fixture = r#"
722fn main() {
723 match $0Some(true) {
724 Some(true) => {}
725 None => {}
726 }
727}
728"#;
729 check_assist_not_applicable(
730 fill_match_arms,
731 &format!("//- /main.rs crate:main deps:core{}{}", ra_fixture, FamousDefs::FIXTURE),
490 ); 732 );
491 } 733 }
492 734
@@ -514,10 +756,7 @@ fn main() {
514 756
515 #[test] 757 #[test]
516 fn fill_match_arms_single_element_tuple_of_enum() { 758 fn fill_match_arms_single_element_tuple_of_enum() {
517 // For now we don't hande the case of a single element tuple, but 759 check_assist(
518 // we could handle this in the future if `make::tuple_pat` allowed
519 // creating a tuple with a single pattern.
520 check_assist_not_applicable(
521 fill_match_arms, 760 fill_match_arms,
522 r#" 761 r#"
523 enum A { One, Two } 762 enum A { One, Two }
@@ -528,6 +767,17 @@ fn main() {
528 } 767 }
529 } 768 }
530 "#, 769 "#,
770 r#"
771 enum A { One, Two }
772
773 fn main() {
774 let a = A::One;
775 match (a, ) {
776 $0(A::One,) => {}
777 (A::Two,) => {}
778 }
779 }
780 "#,
531 ); 781 );
532 } 782 }
533 783
diff --git a/crates/ide_assists/src/handlers/flip_comma.rs b/crates/ide_assists/src/handlers/flip_comma.rs
index 7f5e75d34..99be5e090 100644
--- a/crates/ide_assists/src/handlers/flip_comma.rs
+++ b/crates/ide_assists/src/handlers/flip_comma.rs
@@ -66,26 +66,12 @@ mod tests {
66 } 66 }
67 67
68 #[test] 68 #[test]
69 #[should_panic]
70 fn flip_comma_before_punct() { 69 fn flip_comma_before_punct() {
71 // See https://github.com/rust-analyzer/rust-analyzer/issues/1619 70 // See https://github.com/rust-analyzer/rust-analyzer/issues/1619
72 // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct 71 // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct
73 // declaration body. 72 // declaration body.
74 check_assist_target( 73 check_assist_not_applicable(flip_comma, "pub enum Test { A,$0 }");
75 flip_comma, 74 check_assist_not_applicable(flip_comma, "pub struct Test { foo: usize,$0 }");
76 "pub enum Test { \
77 A,$0 \
78 }",
79 ",",
80 );
81
82 check_assist_target(
83 flip_comma,
84 "pub struct Test { \
85 foo: usize,$0 \
86 }",
87 ",",
88 );
89 } 75 }
90 76
91 #[test] 77 #[test]
diff --git a/crates/ide_assists/src/handlers/generate_deref.rs b/crates/ide_assists/src/handlers/generate_deref.rs
new file mode 100644
index 000000000..4998ff7a4
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_deref.rs
@@ -0,0 +1,227 @@
1use std::fmt::Display;
2
3use ide_db::{helpers::FamousDefs, RootDatabase};
4use syntax::{
5 ast::{self, NameOwner},
6 AstNode, SyntaxNode,
7};
8
9use crate::{
10 assist_context::{AssistBuilder, AssistContext, Assists},
11 utils::generate_trait_impl_text,
12 AssistId, AssistKind,
13};
14
15// Assist: generate_deref
16//
17// Generate `Deref` impl using the given struct field.
18//
19// ```
20// struct A;
21// struct B {
22// $0a: A
23// }
24// ```
25// ->
26// ```
27// struct A;
28// struct B {
29// a: A
30// }
31//
32// impl std::ops::Deref for B {
33// type Target = A;
34//
35// fn deref(&self) -> &Self::Target {
36// &self.a
37// }
38// }
39// ```
40pub(crate) fn generate_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
41 generate_record_deref(acc, ctx).or_else(|| generate_tuple_deref(acc, ctx))
42}
43
44fn generate_record_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
45 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
46 let field = ctx.find_node_at_offset::<ast::RecordField>()?;
47
48 if existing_deref_impl(&ctx.sema, &strukt).is_some() {
49 cov_mark::hit!(test_add_record_deref_impl_already_exists);
50 return None;
51 }
52
53 let field_type = field.ty()?;
54 let field_name = field.name()?;
55 let target = field.syntax().text_range();
56 acc.add(
57 AssistId("generate_deref", AssistKind::Generate),
58 format!("Generate `Deref` impl using `{}`", field_name),
59 target,
60 |edit| generate_edit(edit, strukt, field_type.syntax(), field_name.syntax()),
61 )
62}
63
64fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
65 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
66 let field = ctx.find_node_at_offset::<ast::TupleField>()?;
67 let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?;
68 let field_list_index =
69 field_list.syntax().children().into_iter().position(|s| &s == field.syntax())?;
70
71 if existing_deref_impl(&ctx.sema, &strukt).is_some() {
72 cov_mark::hit!(test_add_field_deref_impl_already_exists);
73 return None;
74 }
75
76 let field_type = field.ty()?;
77 let target = field.syntax().text_range();
78 acc.add(
79 AssistId("generate_deref", AssistKind::Generate),
80 format!("Generate `Deref` impl using `{}`", field.syntax()),
81 target,
82 |edit| generate_edit(edit, strukt, field_type.syntax(), field_list_index),
83 )
84}
85
86fn generate_edit(
87 edit: &mut AssistBuilder,
88 strukt: ast::Struct,
89 field_type_syntax: &SyntaxNode,
90 field_name: impl Display,
91) {
92 let start_offset = strukt.syntax().text_range().end();
93 let impl_code = format!(
94 r#" type Target = {0};
95
96 fn deref(&self) -> &Self::Target {{
97 &self.{1}
98 }}"#,
99 field_type_syntax, field_name
100 );
101 let strukt_adt = ast::Adt::Struct(strukt);
102 let deref_impl = generate_trait_impl_text(&strukt_adt, "std::ops::Deref", &impl_code);
103 edit.insert(start_offset, deref_impl);
104}
105
106fn existing_deref_impl(
107 sema: &'_ hir::Semantics<'_, RootDatabase>,
108 strukt: &ast::Struct,
109) -> Option<()> {
110 let strukt = sema.to_def(strukt)?;
111 let krate = strukt.module(sema.db).krate();
112
113 let deref_trait = FamousDefs(sema, Some(krate)).core_ops_Deref()?;
114 let strukt_type = strukt.ty(sema.db);
115
116 if strukt_type.impls_trait(sema.db, deref_trait, &[]) {
117 Some(())
118 } else {
119 None
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::tests::{check_assist, check_assist_not_applicable};
126
127 use super::*;
128
129 #[test]
130 fn test_generate_record_deref() {
131 check_assist(
132 generate_deref,
133 r#"struct A { }
134struct B { $0a: A }"#,
135 r#"struct A { }
136struct B { a: A }
137
138impl std::ops::Deref for B {
139 type Target = A;
140
141 fn deref(&self) -> &Self::Target {
142 &self.a
143 }
144}"#,
145 );
146 }
147
148 #[test]
149 fn test_generate_field_deref_idx_0() {
150 check_assist(
151 generate_deref,
152 r#"struct A { }
153struct B($0A);"#,
154 r#"struct A { }
155struct B(A);
156
157impl std::ops::Deref for B {
158 type Target = A;
159
160 fn deref(&self) -> &Self::Target {
161 &self.0
162 }
163}"#,
164 );
165 }
166 #[test]
167 fn test_generate_field_deref_idx_1() {
168 check_assist(
169 generate_deref,
170 r#"struct A { }
171struct B(u8, $0A);"#,
172 r#"struct A { }
173struct B(u8, A);
174
175impl std::ops::Deref for B {
176 type Target = A;
177
178 fn deref(&self) -> &Self::Target {
179 &self.1
180 }
181}"#,
182 );
183 }
184
185 fn check_not_applicable(ra_fixture: &str) {
186 let fixture = format!(
187 "//- /main.rs crate:main deps:core,std\n{}\n{}",
188 ra_fixture,
189 FamousDefs::FIXTURE
190 );
191 check_assist_not_applicable(generate_deref, &fixture)
192 }
193
194 #[test]
195 fn test_generate_record_deref_not_applicable_if_already_impl() {
196 cov_mark::check!(test_add_record_deref_impl_already_exists);
197 check_not_applicable(
198 r#"struct A { }
199struct B { $0a: A }
200
201impl std::ops::Deref for B {
202 type Target = A;
203
204 fn deref(&self) -> &Self::Target {
205 &self.a
206 }
207}"#,
208 )
209 }
210
211 #[test]
212 fn test_generate_field_deref_not_applicable_if_already_impl() {
213 cov_mark::check!(test_add_field_deref_impl_already_exists);
214 check_not_applicable(
215 r#"struct A { }
216struct B($0A)
217
218impl std::ops::Deref for B {
219 type Target = A;
220
221 fn deref(&self) -> &Self::Target {
222 &self.0
223 }
224}"#,
225 )
226 }
227}
diff --git a/crates/ide_assists/src/handlers/generate_from_impl_for_enum.rs b/crates/ide_assists/src/handlers/generate_from_impl_for_enum.rs
index c13c6eebe..ce6998d82 100644
--- a/crates/ide_assists/src/handlers/generate_from_impl_for_enum.rs
+++ b/crates/ide_assists/src/handlers/generate_from_impl_for_enum.rs
@@ -91,7 +91,7 @@ fn existing_from_impl(
91 91
92 let enum_type = enum_.ty(sema.db); 92 let enum_type = enum_.ty(sema.db);
93 93
94 let wrapped_type = variant.fields(sema.db).get(0)?.signature_ty(sema.db); 94 let wrapped_type = variant.fields(sema.db).get(0)?.ty(sema.db);
95 95
96 if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) { 96 if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) {
97 Some(()) 97 Some(())
diff --git a/crates/ide_assists/src/handlers/inline_local_variable.rs b/crates/ide_assists/src/handlers/inline_local_variable.rs
index ea1466dc8..f5dafc8cb 100644
--- a/crates/ide_assists/src/handlers/inline_local_variable.rs
+++ b/crates/ide_assists/src/handlers/inline_local_variable.rs
@@ -1,7 +1,9 @@
1use ide_db::{defs::Definition, search::FileReference}; 1use either::Either;
2use hir::PathResolution;
3use ide_db::{base_db::FileId, defs::Definition, search::FileReference};
2use rustc_hash::FxHashMap; 4use rustc_hash::FxHashMap;
3use syntax::{ 5use syntax::{
4 ast::{self, AstNode, AstToken}, 6 ast::{self, AstNode, AstToken, NameOwner},
5 TextRange, 7 TextRange,
6}; 8};
7 9
@@ -27,44 +29,28 @@ use crate::{
27// } 29// }
28// ``` 30// ```
29pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 31pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
30 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; 32 let InlineData { let_stmt, delete_let, replace_usages, target } =
31 let bind_pat = match let_stmt.pat()? { 33 inline_let(ctx).or_else(|| inline_usage(ctx))?;
32 ast::Pat::IdentPat(pat) => pat,
33 _ => return None,
34 };
35 if bind_pat.mut_token().is_some() {
36 cov_mark::hit!(test_not_inline_mut_variable);
37 return None;
38 }
39 if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) {
40 cov_mark::hit!(not_applicable_outside_of_bind_pat);
41 return None;
42 }
43 let initializer_expr = let_stmt.initializer()?; 34 let initializer_expr = let_stmt.initializer()?;
44 35
45 let def = ctx.sema.to_def(&bind_pat)?; 36 let delete_range = if delete_let {
46 let def = Definition::Local(def); 37 if let Some(whitespace) = let_stmt
47 let usages = def.usages(&ctx.sema).all(); 38 .syntax()
48 if usages.is_empty() { 39 .next_sibling_or_token()
49 cov_mark::hit!(test_not_applicable_if_variable_unused); 40 .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone()))
50 return None; 41 {
51 }; 42 Some(TextRange::new(
52 43 let_stmt.syntax().text_range().start(),
53 let delete_range = if let Some(whitespace) = let_stmt 44 whitespace.syntax().text_range().end(),
54 .syntax() 45 ))
55 .next_sibling_or_token() 46 } else {
56 .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone())) 47 Some(let_stmt.syntax().text_range())
57 { 48 }
58 TextRange::new(
59 let_stmt.syntax().text_range().start(),
60 whitespace.syntax().text_range().end(),
61 )
62 } else { 49 } else {
63 let_stmt.syntax().text_range() 50 None
64 }; 51 };
65 52
66 let wrap_in_parens = usages 53 let wrap_in_parens = replace_usages
67 .references
68 .iter() 54 .iter()
69 .map(|(&file_id, refs)| { 55 .map(|(&file_id, refs)| {
70 refs.iter() 56 refs.iter()
@@ -114,14 +100,20 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
114 let init_str = initializer_expr.syntax().text().to_string(); 100 let init_str = initializer_expr.syntax().text().to_string();
115 let init_in_paren = format!("({})", &init_str); 101 let init_in_paren = format!("({})", &init_str);
116 102
117 let target = bind_pat.syntax().text_range(); 103 let target = match target {
104 ast::NameOrNameRef::Name(it) => it.syntax().text_range(),
105 ast::NameOrNameRef::NameRef(it) => it.syntax().text_range(),
106 };
107
118 acc.add( 108 acc.add(
119 AssistId("inline_local_variable", AssistKind::RefactorInline), 109 AssistId("inline_local_variable", AssistKind::RefactorInline),
120 "Inline variable", 110 "Inline variable",
121 target, 111 target,
122 move |builder| { 112 move |builder| {
123 builder.delete(delete_range); 113 if let Some(range) = delete_range {
124 for (file_id, references) in usages.references { 114 builder.delete(range);
115 }
116 for (file_id, references) in replace_usages {
125 for (&should_wrap, reference) in wrap_in_parens[&file_id].iter().zip(references) { 117 for (&should_wrap, reference) in wrap_in_parens[&file_id].iter().zip(references) {
126 let replacement = 118 let replacement =
127 if should_wrap { init_in_paren.clone() } else { init_str.clone() }; 119 if should_wrap { init_in_paren.clone() } else { init_str.clone() };
@@ -140,6 +132,81 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
140 ) 132 )
141} 133}
142 134
135struct InlineData {
136 let_stmt: ast::LetStmt,
137 delete_let: bool,
138 target: ast::NameOrNameRef,
139 replace_usages: FxHashMap<FileId, Vec<FileReference>>,
140}
141
142fn inline_let(ctx: &AssistContext) -> Option<InlineData> {
143 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
144 let bind_pat = match let_stmt.pat()? {
145 ast::Pat::IdentPat(pat) => pat,
146 _ => return None,
147 };
148 if bind_pat.mut_token().is_some() {
149 cov_mark::hit!(test_not_inline_mut_variable);
150 return None;
151 }
152 if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) {
153 cov_mark::hit!(not_applicable_outside_of_bind_pat);
154 return None;
155 }
156
157 let def = ctx.sema.to_def(&bind_pat)?;
158 let def = Definition::Local(def);
159 let usages = def.usages(&ctx.sema).all();
160 if usages.is_empty() {
161 cov_mark::hit!(test_not_applicable_if_variable_unused);
162 return None;
163 };
164
165 Some(InlineData {
166 let_stmt,
167 delete_let: true,
168 target: ast::NameOrNameRef::Name(bind_pat.name()?),
169 replace_usages: usages.references,
170 })
171}
172
173fn inline_usage(ctx: &AssistContext) -> Option<InlineData> {
174 let path_expr = ctx.find_node_at_offset::<ast::PathExpr>()?;
175 let path = path_expr.path()?;
176 let name = match path.as_single_segment()?.kind()? {
177 ast::PathSegmentKind::Name(name) => name,
178 _ => return None,
179 };
180
181 let local = match ctx.sema.resolve_path(&path)? {
182 PathResolution::Local(local) => local,
183 _ => return None,
184 };
185
186 let bind_pat = match local.source(ctx.db()).value {
187 Either::Left(ident) => ident,
188 _ => return None,
189 };
190
191 let let_stmt = ast::LetStmt::cast(bind_pat.syntax().parent()?)?;
192
193 let def = Definition::Local(local);
194 let mut usages = def.usages(&ctx.sema).all();
195
196 let delete_let = usages.references.values().map(|v| v.len()).sum::<usize>() == 1;
197
198 for references in usages.references.values_mut() {
199 references.retain(|reference| reference.name.as_name_ref() == Some(&name));
200 }
201
202 Some(InlineData {
203 let_stmt,
204 delete_let,
205 target: ast::NameOrNameRef::NameRef(name),
206 replace_usages: usages.references,
207 })
208}
209
143#[cfg(test)] 210#[cfg(test)]
144mod tests { 211mod tests {
145 use crate::tests::{check_assist, check_assist_not_applicable}; 212 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -726,4 +793,84 @@ fn main() {
726", 793",
727 ) 794 )
728 } 795 }
796
797 #[test]
798 fn works_on_local_usage() {
799 check_assist(
800 inline_local_variable,
801 r#"
802fn f() {
803 let xyz = 0;
804 xyz$0;
805}
806"#,
807 r#"
808fn f() {
809 0;
810}
811"#,
812 );
813 }
814
815 #[test]
816 fn does_not_remove_let_when_multiple_usages() {
817 check_assist(
818 inline_local_variable,
819 r#"
820fn f() {
821 let xyz = 0;
822 xyz$0;
823 xyz;
824}
825"#,
826 r#"
827fn f() {
828 let xyz = 0;
829 0;
830 xyz;
831}
832"#,
833 );
834 }
835
836 #[test]
837 fn not_applicable_with_non_ident_pattern() {
838 check_assist_not_applicable(
839 inline_local_variable,
840 r#"
841fn main() {
842 let (x, y) = (0, 1);
843 x$0;
844}
845"#,
846 );
847 }
848
849 #[test]
850 fn not_applicable_on_local_usage_in_macro() {
851 check_assist_not_applicable(
852 inline_local_variable,
853 r#"
854macro_rules! m {
855 ($i:ident) => { $i }
856}
857fn f() {
858 let xyz = 0;
859 m!(xyz$0); // replacing it would break the macro
860}
861"#,
862 );
863 check_assist_not_applicable(
864 inline_local_variable,
865 r#"
866macro_rules! m {
867 ($i:ident) => { $i }
868}
869fn f() {
870 let xyz$0 = 0;
871 m!(xyz); // replacing it would break the macro
872}
873"#,
874 );
875 }
729} 876}
diff --git a/crates/ide_assists/src/handlers/introduce_named_lifetime.rs b/crates/ide_assists/src/handlers/introduce_named_lifetime.rs
index 02782eb6d..9f4f71d6c 100644
--- a/crates/ide_assists/src/handlers/introduce_named_lifetime.rs
+++ b/crates/ide_assists/src/handlers/introduce_named_lifetime.rs
@@ -1,7 +1,8 @@
1use rustc_hash::FxHashSet; 1use rustc_hash::FxHashSet;
2use syntax::{ 2use syntax::{
3 ast::{self, GenericParamsOwner, NameOwner}, 3 ast::{self, edit_in_place::GenericParamsOwnerEdit, make, GenericParamsOwner},
4 AstNode, TextRange, TextSize, 4 ted::{self, Position},
5 AstNode, TextRange,
5}; 6};
6 7
7use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists}; 8use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
@@ -37,10 +38,12 @@ static ASSIST_LABEL: &str = "Introduce named lifetime";
37pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 38pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 let lifetime = 39 let lifetime =
39 ctx.find_node_at_offset::<ast::Lifetime>().filter(|lifetime| lifetime.text() == "'_")?; 40 ctx.find_node_at_offset::<ast::Lifetime>().filter(|lifetime| lifetime.text() == "'_")?;
41 let lifetime_loc = lifetime.lifetime_ident_token()?.text_range();
42
40 if let Some(fn_def) = lifetime.syntax().ancestors().find_map(ast::Fn::cast) { 43 if let Some(fn_def) = lifetime.syntax().ancestors().find_map(ast::Fn::cast) {
41 generate_fn_def_assist(acc, &fn_def, lifetime.lifetime_ident_token()?.text_range()) 44 generate_fn_def_assist(acc, fn_def, lifetime_loc, lifetime)
42 } else if let Some(impl_def) = lifetime.syntax().ancestors().find_map(ast::Impl::cast) { 45 } else if let Some(impl_def) = lifetime.syntax().ancestors().find_map(ast::Impl::cast) {
43 generate_impl_def_assist(acc, &impl_def, lifetime.lifetime_ident_token()?.text_range()) 46 generate_impl_def_assist(acc, impl_def, lifetime_loc, lifetime)
44 } else { 47 } else {
45 None 48 None
46 } 49 }
@@ -49,26 +52,26 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -
49/// Generate the assist for the fn def case 52/// Generate the assist for the fn def case
50fn generate_fn_def_assist( 53fn generate_fn_def_assist(
51 acc: &mut Assists, 54 acc: &mut Assists,
52 fn_def: &ast::Fn, 55 fn_def: ast::Fn,
53 lifetime_loc: TextRange, 56 lifetime_loc: TextRange,
57 lifetime: ast::Lifetime,
54) -> Option<()> { 58) -> Option<()> {
55 let param_list: ast::ParamList = fn_def.param_list()?; 59 let param_list: ast::ParamList = fn_def.param_list()?;
56 let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.generic_param_list())?; 60 let new_lifetime_param = generate_unique_lifetime_param_name(fn_def.generic_param_list())?;
57 let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end();
58 let self_param = 61 let self_param =
59 // use the self if it's a reference and has no explicit lifetime 62 // use the self if it's a reference and has no explicit lifetime
60 param_list.self_param().filter(|p| p.lifetime().is_none() && p.amp_token().is_some()); 63 param_list.self_param().filter(|p| p.lifetime().is_none() && p.amp_token().is_some());
61 // compute the location which implicitly has the same lifetime as the anonymous lifetime 64 // compute the location which implicitly has the same lifetime as the anonymous lifetime
62 let loc_needing_lifetime = if let Some(self_param) = self_param { 65 let loc_needing_lifetime = if let Some(self_param) = self_param {
63 // if we have a self reference, use that 66 // if we have a self reference, use that
64 Some(self_param.name()?.syntax().text_range().start()) 67 Some(NeedsLifetime::SelfParam(self_param))
65 } else { 68 } else {
66 // otherwise, if there's a single reference parameter without a named liftime, use that 69 // otherwise, if there's a single reference parameter without a named liftime, use that
67 let fn_params_without_lifetime: Vec<_> = param_list 70 let fn_params_without_lifetime: Vec<_> = param_list
68 .params() 71 .params()
69 .filter_map(|param| match param.ty() { 72 .filter_map(|param| match param.ty() {
70 Some(ast::Type::RefType(ascribed_type)) if ascribed_type.lifetime().is_none() => { 73 Some(ast::Type::RefType(ascribed_type)) if ascribed_type.lifetime().is_none() => {
71 Some(ascribed_type.amp_token()?.text_range().end()) 74 Some(NeedsLifetime::RefType(ascribed_type))
72 } 75 }
73 _ => None, 76 _ => None,
74 }) 77 })
@@ -81,30 +84,46 @@ fn generate_fn_def_assist(
81 } 84 }
82 }; 85 };
83 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| { 86 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
84 add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param); 87 let fn_def = builder.make_ast_mut(fn_def);
85 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); 88 let lifetime = builder.make_ast_mut(lifetime);
86 loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param))); 89 let loc_needing_lifetime =
90 loc_needing_lifetime.and_then(|it| it.make_mut(builder).to_position());
91
92 add_lifetime_param(fn_def.get_or_create_generic_param_list(), new_lifetime_param);
93 ted::replace(
94 lifetime.syntax(),
95 make_ast_lifetime(new_lifetime_param).clone_for_update().syntax(),
96 );
97 loc_needing_lifetime.map(|position| {
98 ted::insert(position, make_ast_lifetime(new_lifetime_param).clone_for_update().syntax())
99 });
87 }) 100 })
88} 101}
89 102
90/// Generate the assist for the impl def case 103/// Generate the assist for the impl def case
91fn generate_impl_def_assist( 104fn generate_impl_def_assist(
92 acc: &mut Assists, 105 acc: &mut Assists,
93 impl_def: &ast::Impl, 106 impl_def: ast::Impl,
94 lifetime_loc: TextRange, 107 lifetime_loc: TextRange,
108 lifetime: ast::Lifetime,
95) -> Option<()> { 109) -> Option<()> {
96 let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.generic_param_list())?; 110 let new_lifetime_param = generate_unique_lifetime_param_name(impl_def.generic_param_list())?;
97 let end_of_impl_kw = impl_def.impl_token()?.text_range().end();
98 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| { 111 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
99 add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param); 112 let impl_def = builder.make_ast_mut(impl_def);
100 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); 113 let lifetime = builder.make_ast_mut(lifetime);
114
115 add_lifetime_param(impl_def.get_or_create_generic_param_list(), new_lifetime_param);
116 ted::replace(
117 lifetime.syntax(),
118 make_ast_lifetime(new_lifetime_param).clone_for_update().syntax(),
119 );
101 }) 120 })
102} 121}
103 122
104/// Given a type parameter list, generate a unique lifetime parameter name 123/// Given a type parameter list, generate a unique lifetime parameter name
105/// which is not in the list 124/// which is not in the list
106fn generate_unique_lifetime_param_name( 125fn generate_unique_lifetime_param_name(
107 existing_type_param_list: &Option<ast::GenericParamList>, 126 existing_type_param_list: Option<ast::GenericParamList>,
108) -> Option<char> { 127) -> Option<char> {
109 match existing_type_param_list { 128 match existing_type_param_list {
110 Some(type_params) => { 129 Some(type_params) => {
@@ -118,25 +137,37 @@ fn generate_unique_lifetime_param_name(
118 } 137 }
119} 138}
120 139
121/// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise 140fn add_lifetime_param(type_params: ast::GenericParamList, new_lifetime_param: char) {
122/// add new type params brackets with the lifetime parameter at `new_type_params_loc`. 141 let generic_param =
123fn add_lifetime_param<TypeParamsOwner: ast::GenericParamsOwner>( 142 make::generic_param(format!("'{}", new_lifetime_param), None).clone_for_update();
124 type_params_owner: &TypeParamsOwner, 143 type_params.add_generic_param(generic_param);
125 builder: &mut AssistBuilder, 144}
126 new_type_params_loc: TextSize, 145
127 new_lifetime_param: char, 146fn make_ast_lifetime(new_lifetime_param: char) -> ast::Lifetime {
128) { 147 make::generic_param(format!("'{}", new_lifetime_param), None)
129 match type_params_owner.generic_param_list() { 148 .syntax()
130 // add the new lifetime parameter to an existing type param list 149 .descendants()
131 Some(type_params) => { 150 .find_map(ast::Lifetime::cast)
132 builder.insert( 151 .unwrap()
133 (u32::from(type_params.syntax().text_range().end()) - 1).into(), 152}
134 format!(", '{}", new_lifetime_param), 153
135 ); 154enum NeedsLifetime {
155 SelfParam(ast::SelfParam),
156 RefType(ast::RefType),
157}
158
159impl NeedsLifetime {
160 fn make_mut(self, builder: &mut AssistBuilder) -> Self {
161 match self {
162 Self::SelfParam(it) => Self::SelfParam(builder.make_ast_mut(it)),
163 Self::RefType(it) => Self::RefType(builder.make_ast_mut(it)),
136 } 164 }
137 // create a new type param list containing only the new lifetime parameter 165 }
138 None => { 166
139 builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param)); 167 fn to_position(self) -> Option<Position> {
168 match self {
169 Self::SelfParam(it) => Some(Position::after(it.amp_token()?)),
170 Self::RefType(it) => Some(Position::after(it.amp_token()?)),
140 } 171 }
141 } 172 }
142} 173}
@@ -312,4 +343,13 @@ mod tests {
312 r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#, 343 r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#,
313 ); 344 );
314 } 345 }
346
347 #[test]
348 fn test_function_add_lifetime_to_self_ref_mut() {
349 check_assist(
350 introduce_named_lifetime,
351 r#"fn foo(&mut self) -> &'_$0 ()"#,
352 r#"fn foo<'a>(&'a mut self) -> &'a ()"#,
353 );
354 }
315} 355}
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 @@
1use ide_db::helpers::insert_use::{try_merge_imports, try_merge_trees, MergeBehavior}; 1use ide_db::helpers::merge_imports::{try_merge_imports, try_merge_trees, MergeBehavior};
2use syntax::{algo::neighbor, ast, ted, AstNode}; 2use syntax::{algo::neighbor, ast, ted, AstNode};
3 3
4use crate::{ 4use crate::{
diff --git a/crates/ide_assists/src/handlers/remove_dbg.rs b/crates/ide_assists/src/handlers/remove_dbg.rs
index 2862cfa9c..c8226550f 100644
--- a/crates/ide_assists/src/handlers/remove_dbg.rs
+++ b/crates/ide_assists/src/handlers/remove_dbg.rs
@@ -30,7 +30,7 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
30 if new_contents.is_empty() { 30 if new_contents.is_empty() {
31 match_ast! { 31 match_ast! {
32 match it { 32 match it {
33 ast::BlockExpr(it) => { 33 ast::BlockExpr(_it) => {
34 macro_call.syntax() 34 macro_call.syntax()
35 .prev_sibling_or_token() 35 .prev_sibling_or_token()
36 .and_then(whitespace_start) 36 .and_then(whitespace_start)
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
93fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { 91fn 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;
4use hir::{PathResolution, Semantics}; 4use hir::{PathResolution, Semantics};
5use ide_db::RootDatabase; 5use ide_db::RootDatabase;
6use syntax::{ 6use syntax::{
7 algo,
8 ast::{self, NameOwner}, 7 ast::{self, NameOwner},
9 AstNode, 8 ted, AstNode,
10}; 9};
11 10
12use crate::{AssistContext, AssistId, AssistKind, Assists}; 11use 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
87fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { 89fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
index f872d20c8..694d897d1 100644
--- a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -5,7 +5,6 @@ use itertools::Itertools;
5use syntax::{ 5use syntax::{
6 ast::{self, make, AstNode, NameOwner}, 6 ast::{self, make, AstNode, NameOwner},
7 SyntaxKind::{IDENT, WHITESPACE}, 7 SyntaxKind::{IDENT, WHITESPACE},
8 TextSize,
9}; 8};
10 9
11use crate::{ 10use crate::{
@@ -43,32 +42,28 @@ pub(crate) fn replace_derive_with_manual_impl(
43 ctx: &AssistContext, 42 ctx: &AssistContext,
44) -> Option<()> { 43) -> Option<()> {
45 let attr = ctx.find_node_at_offset::<ast::Attr>()?; 44 let attr = ctx.find_node_at_offset::<ast::Attr>()?;
45 let (name, args) = attr.as_simple_call()?;
46 if name != "derive" {
47 return None;
48 }
46 49
47 let has_derive = attr 50 if !args.syntax().text_range().contains(ctx.offset()) {
48 .syntax() 51 cov_mark::hit!(outside_of_attr_args);
49 .descendants_with_tokens()
50 .filter(|t| t.kind() == IDENT)
51 .find_map(syntax::NodeOrToken::into_token)
52 .filter(|t| t.text() == "derive")
53 .is_some();
54 if !has_derive {
55 return None; 52 return None;
56 } 53 }
57 54
58 let trait_token = ctx.token_at_offset().find(|t| t.kind() == IDENT && t.text() != "derive")?; 55 let trait_token = args.syntax().token_at_offset(ctx.offset()).find(|t| t.kind() == IDENT)?;
59 let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text()))); 56 let trait_name = trait_token.text();
60 57
61 let adt = attr.syntax().parent().and_then(ast::Adt::cast)?; 58 let adt = attr.syntax().parent().and_then(ast::Adt::cast)?;
62 let annotated_name = adt.name()?;
63 let insert_pos = adt.syntax().text_range().end();
64 59
65 let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; 60 let current_module = ctx.sema.scope(adt.syntax()).module()?;
66 let current_crate = current_module.krate(); 61 let current_crate = current_module.krate();
67 62
68 let found_traits = items_locator::items_with_name( 63 let found_traits = items_locator::items_with_name(
69 &ctx.sema, 64 &ctx.sema,
70 current_crate, 65 current_crate,
71 NameToImport::Exact(trait_token.text().to_string()), 66 NameToImport::Exact(trait_name.to_string()),
72 items_locator::AssocItemSearch::Exclude, 67 items_locator::AssocItemSearch::Exclude,
73 Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT), 68 Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT),
74 ) 69 )
@@ -86,10 +81,11 @@ pub(crate) fn replace_derive_with_manual_impl(
86 81
87 let mut no_traits_found = true; 82 let mut no_traits_found = true;
88 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { 83 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) {
89 add_assist(acc, ctx, &attr, &trait_path, Some(trait_), &adt, &annotated_name, insert_pos)?; 84 add_assist(acc, ctx, &attr, &args, &trait_path, Some(trait_), &adt)?;
90 } 85 }
91 if no_traits_found { 86 if no_traits_found {
92 add_assist(acc, ctx, &attr, &trait_path, None, &adt, &annotated_name, insert_pos)?; 87 let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_name)));
88 add_assist(acc, ctx, &attr, &args, &trait_path, None, &adt)?;
93 } 89 }
94 Some(()) 90 Some(())
95} 91}
@@ -98,15 +94,14 @@ fn add_assist(
98 acc: &mut Assists, 94 acc: &mut Assists,
99 ctx: &AssistContext, 95 ctx: &AssistContext,
100 attr: &ast::Attr, 96 attr: &ast::Attr,
97 input: &ast::TokenTree,
101 trait_path: &ast::Path, 98 trait_path: &ast::Path,
102 trait_: Option<hir::Trait>, 99 trait_: Option<hir::Trait>,
103 adt: &ast::Adt, 100 adt: &ast::Adt,
104 annotated_name: &ast::Name,
105 insert_pos: TextSize,
106) -> Option<()> { 101) -> Option<()> {
107 let target = attr.syntax().text_range(); 102 let target = attr.syntax().text_range();
108 let input = attr.token_tree()?; 103 let annotated_name = adt.name()?;
109 let label = format!("Convert to manual `impl {} for {}`", trait_path, annotated_name); 104 let label = format!("Convert to manual `impl {} for {}`", trait_path, annotated_name);
110 let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?; 105 let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?;
111 106
112 acc.add( 107 acc.add(
@@ -114,8 +109,9 @@ fn add_assist(
114 label, 109 label,
115 target, 110 target,
116 |builder| { 111 |builder| {
112 let insert_pos = adt.syntax().text_range().end();
117 let impl_def_with_items = 113 let impl_def_with_items =
118 impl_def_from_trait(&ctx.sema, annotated_name, trait_, trait_path); 114 impl_def_from_trait(&ctx.sema, &annotated_name, trait_, trait_path);
119 update_attribute(builder, &input, &trait_name, &attr); 115 update_attribute(builder, &input, &trait_name, &attr);
120 let trait_path = format!("{}", trait_path); 116 let trait_path = format!("{}", trait_path);
121 match (ctx.config.snippet_cap, impl_def_with_items) { 117 match (ctx.config.snippet_cap, impl_def_with_items) {
@@ -216,7 +212,7 @@ mod tests {
216 fn add_custom_impl_debug() { 212 fn add_custom_impl_debug() {
217 check_assist( 213 check_assist(
218 replace_derive_with_manual_impl, 214 replace_derive_with_manual_impl,
219 " 215 r#"
220mod fmt { 216mod fmt {
221 pub struct Error; 217 pub struct Error;
222 pub type Result = Result<(), Error>; 218 pub type Result = Result<(), Error>;
@@ -230,8 +226,8 @@ mod fmt {
230struct Foo { 226struct Foo {
231 bar: String, 227 bar: String,
232} 228}
233", 229"#,
234 " 230 r#"
235mod fmt { 231mod fmt {
236 pub struct Error; 232 pub struct Error;
237 pub type Result = Result<(), Error>; 233 pub type Result = Result<(), Error>;
@@ -250,14 +246,14 @@ impl fmt::Debug for Foo {
250 ${0:todo!()} 246 ${0:todo!()}
251 } 247 }
252} 248}
253", 249"#,
254 ) 250 )
255 } 251 }
256 #[test] 252 #[test]
257 fn add_custom_impl_all() { 253 fn add_custom_impl_all() {
258 check_assist( 254 check_assist(
259 replace_derive_with_manual_impl, 255 replace_derive_with_manual_impl,
260 " 256 r#"
261mod foo { 257mod foo {
262 pub trait Bar { 258 pub trait Bar {
263 type Qux; 259 type Qux;
@@ -272,8 +268,8 @@ mod foo {
272struct Foo { 268struct Foo {
273 bar: String, 269 bar: String,
274} 270}
275", 271"#,
276 " 272 r#"
277mod foo { 273mod foo {
278 pub trait Bar { 274 pub trait Bar {
279 type Qux; 275 type Qux;
@@ -299,20 +295,20 @@ impl foo::Bar for Foo {
299 todo!() 295 todo!()
300 } 296 }
301} 297}
302", 298"#,
303 ) 299 )
304 } 300 }
305 #[test] 301 #[test]
306 fn add_custom_impl_for_unique_input() { 302 fn add_custom_impl_for_unique_input() {
307 check_assist( 303 check_assist(
308 replace_derive_with_manual_impl, 304 replace_derive_with_manual_impl,
309 " 305 r#"
310#[derive(Debu$0g)] 306#[derive(Debu$0g)]
311struct Foo { 307struct Foo {
312 bar: String, 308 bar: String,
313} 309}
314 ", 310 "#,
315 " 311 r#"
316struct Foo { 312struct Foo {
317 bar: String, 313 bar: String,
318} 314}
@@ -320,7 +316,7 @@ struct Foo {
320impl Debug for Foo { 316impl Debug for Foo {
321 $0 317 $0
322} 318}
323 ", 319 "#,
324 ) 320 )
325 } 321 }
326 322
@@ -328,13 +324,13 @@ impl Debug for Foo {
328 fn add_custom_impl_for_with_visibility_modifier() { 324 fn add_custom_impl_for_with_visibility_modifier() {
329 check_assist( 325 check_assist(
330 replace_derive_with_manual_impl, 326 replace_derive_with_manual_impl,
331 " 327 r#"
332#[derive(Debug$0)] 328#[derive(Debug$0)]
333pub struct Foo { 329pub struct Foo {
334 bar: String, 330 bar: String,
335} 331}
336 ", 332 "#,
337 " 333 r#"
338pub struct Foo { 334pub struct Foo {
339 bar: String, 335 bar: String,
340} 336}
@@ -342,7 +338,7 @@ pub struct Foo {
342impl Debug for Foo { 338impl Debug for Foo {
343 $0 339 $0
344} 340}
345 ", 341 "#,
346 ) 342 )
347 } 343 }
348 344
@@ -350,18 +346,18 @@ impl Debug for Foo {
350 fn add_custom_impl_when_multiple_inputs() { 346 fn add_custom_impl_when_multiple_inputs() {
351 check_assist( 347 check_assist(
352 replace_derive_with_manual_impl, 348 replace_derive_with_manual_impl,
353 " 349 r#"
354#[derive(Display, Debug$0, Serialize)] 350#[derive(Display, Debug$0, Serialize)]
355struct Foo {} 351struct Foo {}
356 ", 352 "#,
357 " 353 r#"
358#[derive(Display, Serialize)] 354#[derive(Display, Serialize)]
359struct Foo {} 355struct Foo {}
360 356
361impl Debug for Foo { 357impl Debug for Foo {
362 $0 358 $0
363} 359}
364 ", 360 "#,
365 ) 361 )
366 } 362 }
367 363
@@ -369,10 +365,10 @@ impl Debug for Foo {
369 fn test_ignore_derive_macro_without_input() { 365 fn test_ignore_derive_macro_without_input() {
370 check_assist_not_applicable( 366 check_assist_not_applicable(
371 replace_derive_with_manual_impl, 367 replace_derive_with_manual_impl,
372 " 368 r#"
373#[derive($0)] 369#[derive($0)]
374struct Foo {} 370struct Foo {}
375 ", 371 "#,
376 ) 372 )
377 } 373 }
378 374
@@ -380,18 +376,18 @@ struct Foo {}
380 fn test_ignore_if_cursor_on_param() { 376 fn test_ignore_if_cursor_on_param() {
381 check_assist_not_applicable( 377 check_assist_not_applicable(
382 replace_derive_with_manual_impl, 378 replace_derive_with_manual_impl,
383 " 379 r#"
384#[derive$0(Debug)] 380#[derive$0(Debug)]
385struct Foo {} 381struct Foo {}
386 ", 382 "#,
387 ); 383 );
388 384
389 check_assist_not_applicable( 385 check_assist_not_applicable(
390 replace_derive_with_manual_impl, 386 replace_derive_with_manual_impl,
391 " 387 r#"
392#[derive(Debug)$0] 388#[derive(Debug)$0]
393struct Foo {} 389struct Foo {}
394 ", 390 "#,
395 ) 391 )
396 } 392 }
397 393
@@ -399,10 +395,22 @@ struct Foo {}
399 fn test_ignore_if_not_derive() { 395 fn test_ignore_if_not_derive() {
400 check_assist_not_applicable( 396 check_assist_not_applicable(
401 replace_derive_with_manual_impl, 397 replace_derive_with_manual_impl,
402 " 398 r#"
403#[allow(non_camel_$0case_types)] 399#[allow(non_camel_$0case_types)]
404struct Foo {} 400struct Foo {}
405 ", 401 "#,
406 ) 402 )
407 } 403 }
404
405 #[test]
406 fn works_at_start_of_file() {
407 cov_mark::check!(outside_of_attr_args);
408 check_assist_not_applicable(
409 replace_derive_with_manual_impl,
410 r#"
411$0#[derive(Debug)]
412struct S;
413 "#,
414 );
415 }
408} 416}
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 @@
1use ide_db::helpers::insert_use::{insert_use, ImportScope}; 1use ide_db::helpers::insert_use::{insert_use, ImportScope};
2use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode}; 2use syntax::{ast, match_ast, ted, AstNode, SyntaxNode};
3 3
4use crate::{AssistContext, AssistId, AssistKind, Assists}; 4use 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`.
54fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ast::Path) { 53fn 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
76fn maybe_replace_path( 71fn 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(