aboutsummaryrefslogtreecommitdiff
path: root/crates/assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists')
-rw-r--r--crates/assists/src/assist_context.rs7
-rw-r--r--crates/assists/src/handlers/add_custom_impl.rs152
-rw-r--r--crates/assists/src/handlers/add_turbo_fish.rs2
-rw-r--r--crates/assists/src/handlers/auto_import.rs5
-rw-r--r--crates/assists/src/handlers/change_return_type_to_result.rs121
-rw-r--r--crates/assists/src/handlers/convert_integer_literal.rs213
-rw-r--r--crates/assists/src/handlers/expand_glob_import.rs2
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs266
-rw-r--r--crates/assists/src/handlers/flip_comma.rs2
-rw-r--r--crates/assists/src/handlers/flip_trait_bound.rs2
-rw-r--r--crates/assists/src/handlers/infer_function_return_type.rs337
-rw-r--r--crates/assists/src/handlers/introduce_named_lifetime.rs2
-rw-r--r--crates/assists/src/handlers/invert_if.rs2
-rw-r--r--crates/assists/src/handlers/raw_string.rs34
-rw-r--r--crates/assists/src/handlers/remove_mut.rs2
-rw-r--r--crates/assists/src/handlers/replace_let_with_if_let.rs2
-rw-r--r--crates/assists/src/handlers/replace_qualified_name_with_use.rs7
-rw-r--r--crates/assists/src/handlers/replace_string_with_char.rs8
-rw-r--r--crates/assists/src/handlers/split_import.rs2
-rw-r--r--crates/assists/src/handlers/unwrap_block.rs2
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/assists/src/tests/generated.rs13
-rw-r--r--crates/assists/src/utils/insert_use.rs31
23 files changed, 867 insertions, 349 deletions
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs
index d11fee196..fcfe2d6ee 100644
--- a/crates/assists/src/assist_context.rs
+++ b/crates/assists/src/assist_context.rs
@@ -12,7 +12,7 @@ use ide_db::{
12}; 12};
13use syntax::{ 13use syntax::{
14 algo::{self, find_node_at_offset, SyntaxRewriter}, 14 algo::{self, find_node_at_offset, SyntaxRewriter},
15 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxToken, TextRange, TextSize, 15 AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxToken, TextRange, TextSize,
16 TokenAtOffset, 16 TokenAtOffset,
17}; 17};
18use text_edit::{TextEdit, TextEditBuilder}; 18use text_edit::{TextEdit, TextEditBuilder};
@@ -81,9 +81,12 @@ impl<'a> AssistContext<'a> {
81 pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { 81 pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
82 self.source_file.syntax().token_at_offset(self.offset()) 82 self.source_file.syntax().token_at_offset(self.offset())
83 } 83 }
84 pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> { 84 pub(crate) fn find_token_syntax_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
85 self.token_at_offset().find(|it| it.kind() == kind) 85 self.token_at_offset().find(|it| it.kind() == kind)
86 } 86 }
87 pub(crate) fn find_token_at_offset<T: AstToken>(&self) -> Option<T> {
88 self.token_at_offset().find_map(T::cast)
89 }
87 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> { 90 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
88 find_node_at_offset(self.source_file.syntax(), self.offset()) 91 find_node_at_offset(self.source_file.syntax(), self.offset())
89 } 92 }
diff --git a/crates/assists/src/handlers/add_custom_impl.rs b/crates/assists/src/handlers/add_custom_impl.rs
index 8757fa33f..669dd9b21 100644
--- a/crates/assists/src/handlers/add_custom_impl.rs
+++ b/crates/assists/src/handlers/add_custom_impl.rs
@@ -1,13 +1,16 @@
1use ide_db::imports_locator;
1use itertools::Itertools; 2use itertools::Itertools;
2use syntax::{ 3use syntax::{
3 ast::{self, AstNode}, 4 ast::{self, make, AstNode},
4 Direction, SmolStr, 5 Direction, SmolStr,
5 SyntaxKind::{IDENT, WHITESPACE}, 6 SyntaxKind::{IDENT, WHITESPACE},
6 TextRange, TextSize, 7 TextRange, TextSize,
7}; 8};
8 9
9use crate::{ 10use crate::{
10 assist_context::{AssistContext, Assists}, 11 assist_config::SnippetCap,
12 assist_context::{AssistBuilder, AssistContext, Assists},
13 utils::mod_path_to_ast,
11 AssistId, AssistKind, 14 AssistId, AssistKind,
12}; 15};
13 16
@@ -30,72 +33,116 @@ use crate::{
30// ``` 33// ```
31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 34pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let attr = ctx.find_node_at_offset::<ast::Attr>()?; 35 let attr = ctx.find_node_at_offset::<ast::Attr>()?;
33 let input = attr.token_tree()?;
34 36
35 let attr_name = attr 37 let attr_name = attr
36 .syntax() 38 .syntax()
37 .descendants_with_tokens() 39 .descendants_with_tokens()
38 .filter(|t| t.kind() == IDENT) 40 .filter(|t| t.kind() == IDENT)
39 .find_map(|i| i.into_token()) 41 .find_map(syntax::NodeOrToken::into_token)
40 .filter(|t| *t.text() == "derive")? 42 .filter(|t| t.text() == "derive")?
41 .text() 43 .text()
42 .clone(); 44 .clone();
43 45
44 let trait_token = 46 let trait_token =
45 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?; 47 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
48 let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text())));
46 49
47 let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?; 50 let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
48 let annotated_name = annotated.syntax().text().to_string(); 51 let annotated_name = annotated.syntax().text().to_string();
49 let start_offset = annotated.syntax().parent()?.text_range().end(); 52 let insert_pos = annotated.syntax().parent()?.text_range().end();
53
54 let current_module = ctx.sema.scope(annotated.syntax()).module()?;
55 let current_crate = current_module.krate();
56
57 let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text())
58 .into_iter()
59 .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
60 either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
61 _ => None,
62 })
63 .flat_map(|trait_| {
64 current_module
65 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
66 .as_ref()
67 .map(mod_path_to_ast)
68 .zip(Some(trait_))
69 });
50 70
51 let label = 71 let mut no_traits_found = true;
52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name); 72 for (trait_path, _trait) in found_traits.inspect(|_| no_traits_found = false) {
73 add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
74 }
75 if no_traits_found {
76 add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
77 }
78 Some(())
79}
53 80
81fn add_assist(
82 acc: &mut Assists,
83 snippet_cap: Option<SnippetCap>,
84 attr: &ast::Attr,
85 trait_path: &ast::Path,
86 annotated_name: &str,
87 insert_pos: TextSize,
88) -> Option<()> {
54 let target = attr.syntax().text_range(); 89 let target = attr.syntax().text_range();
55 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| { 90 let input = attr.token_tree()?;
56 let new_attr_input = input 91 let label = format!("Add custom impl `{}` for `{}`", trait_path, annotated_name);
57 .syntax() 92 let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?;
58 .descendants_with_tokens()
59 .filter(|t| t.kind() == IDENT)
60 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
61 .filter(|t| t != trait_token.text())
62 .collect::<Vec<SmolStr>>();
63 let has_more_derives = !new_attr_input.is_empty();
64
65 if has_more_derives {
66 let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
67 builder.replace(input.syntax().text_range(), new_attr_input);
68 } else {
69 let attr_range = attr.syntax().text_range();
70 builder.delete(attr_range);
71
72 let line_break_range = attr
73 .syntax()
74 .next_sibling_or_token()
75 .filter(|t| t.kind() == WHITESPACE)
76 .map(|t| t.text_range())
77 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
78 builder.delete(line_break_range);
79 }
80 93
81 match ctx.config.snippet_cap { 94 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
95 update_attribute(builder, &input, &trait_name, &attr);
96 match snippet_cap {
82 Some(cap) => { 97 Some(cap) => {
83 builder.insert_snippet( 98 builder.insert_snippet(
84 cap, 99 cap,
85 start_offset, 100 insert_pos,
86 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name), 101 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name),
87 ); 102 );
88 } 103 }
89 None => { 104 None => {
90 builder.insert( 105 builder.insert(
91 start_offset, 106 insert_pos,
92 format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name), 107 format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name),
93 ); 108 );
94 } 109 }
95 } 110 }
96 }) 111 })
97} 112}
98 113
114fn update_attribute(
115 builder: &mut AssistBuilder,
116 input: &ast::TokenTree,
117 trait_name: &ast::NameRef,
118 attr: &ast::Attr,
119) {
120 let new_attr_input = input
121 .syntax()
122 .descendants_with_tokens()
123 .filter(|t| t.kind() == IDENT)
124 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
125 .filter(|t| t != trait_name.text())
126 .collect::<Vec<SmolStr>>();
127 let has_more_derives = !new_attr_input.is_empty();
128
129 if has_more_derives {
130 let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
131 builder.replace(input.syntax().text_range(), new_attr_input);
132 } else {
133 let attr_range = attr.syntax().text_range();
134 builder.delete(attr_range);
135
136 let line_break_range = attr
137 .syntax()
138 .next_sibling_or_token()
139 .filter(|t| t.kind() == WHITESPACE)
140 .map(|t| t.text_range())
141 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
142 builder.delete(line_break_range);
143 }
144}
145
99#[cfg(test)] 146#[cfg(test)]
100mod tests { 147mod tests {
101 use crate::tests::{check_assist, check_assist_not_applicable}; 148 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -103,6 +150,35 @@ mod tests {
103 use super::*; 150 use super::*;
104 151
105 #[test] 152 #[test]
153 fn add_custom_impl_qualified() {
154 check_assist(
155 add_custom_impl,
156 "
157mod fmt {
158 pub trait Debug {}
159}
160
161#[derive(Debu<|>g)]
162struct Foo {
163 bar: String,
164}
165",
166 "
167mod fmt {
168 pub trait Debug {}
169}
170
171struct Foo {
172 bar: String,
173}
174
175impl fmt::Debug for Foo {
176 $0
177}
178",
179 )
180 }
181 #[test]
106 fn add_custom_impl_for_unique_input() { 182 fn add_custom_impl_for_unique_input() {
107 check_assist( 183 check_assist(
108 add_custom_impl, 184 add_custom_impl,
diff --git a/crates/assists/src/handlers/add_turbo_fish.rs b/crates/assists/src/handlers/add_turbo_fish.rs
index e3d84d698..1f486c013 100644
--- a/crates/assists/src/handlers/add_turbo_fish.rs
+++ b/crates/assists/src/handlers/add_turbo_fish.rs
@@ -25,7 +25,7 @@ use crate::{
25// } 25// }
26// ``` 26// ```
27pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 27pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let ident = ctx.find_token_at_offset(SyntaxKind::IDENT).or_else(|| { 28 let ident = ctx.find_token_syntax_at_offset(SyntaxKind::IDENT).or_else(|| {
29 let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?; 29 let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?;
30 if arg_list.args().count() > 0 { 30 if arg_list.args().count() > 0 {
31 return None; 31 return None;
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index e49e641b3..37dd61266 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -99,7 +99,6 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
99 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; 99 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
100 let group = import_group_message(import_assets.import_candidate()); 100 let group = import_group_message(import_assets.import_candidate());
101 let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?; 101 let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?;
102 let syntax = scope.as_syntax_node();
103 for (import, _) in proposed_imports { 102 for (import, _) in proposed_imports {
104 acc.add_group( 103 acc.add_group(
105 &group, 104 &group,
@@ -107,9 +106,9 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
107 format!("Import `{}`", &import), 106 format!("Import `{}`", &import),
108 range, 107 range,
109 |builder| { 108 |builder| {
110 let new_syntax = 109 let rewriter =
111 insert_use(&scope, mod_path_to_ast(&import), ctx.config.insert_use.merge); 110 insert_use(&scope, mod_path_to_ast(&import), ctx.config.insert_use.merge);
112 builder.replace(syntax.text_range(), new_syntax.to_string()) 111 builder.rewrite(rewriter);
113 }, 112 },
114 ); 113 );
115 } 114 }
diff --git a/crates/assists/src/handlers/change_return_type_to_result.rs b/crates/assists/src/handlers/change_return_type_to_result.rs
index be480943c..76f33a5b6 100644
--- a/crates/assists/src/handlers/change_return_type_to_result.rs
+++ b/crates/assists/src/handlers/change_return_type_to_result.rs
@@ -2,7 +2,7 @@ use std::iter;
2 2
3use syntax::{ 3use syntax::{
4 ast::{self, make, BlockExpr, Expr, LoopBodyOwner}, 4 ast::{self, make, BlockExpr, Expr, LoopBodyOwner},
5 AstNode, SyntaxNode, 5 match_ast, AstNode, SyntaxNode,
6}; 6};
7use test_utils::mark; 7use test_utils::mark;
8 8
@@ -21,8 +21,18 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
21// ``` 21// ```
22pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 22pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
23 let ret_type = ctx.find_node_at_offset::<ast::RetType>()?; 23 let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
24 // FIXME: extend to lambdas as well 24 let parent = ret_type.syntax().parent()?;
25 let fn_def = ret_type.syntax().parent().and_then(ast::Fn::cast)?; 25 let block_expr = match_ast! {
26 match parent {
27 ast::Fn(func) => func.body()?,
28 ast::ClosureExpr(closure) => match closure.body()? {
29 Expr::BlockExpr(block) => block,
30 // closures require a block when a return type is specified
31 _ => return None,
32 },
33 _ => return None,
34 }
35 };
26 36
27 let type_ref = &ret_type.ty()?; 37 let type_ref = &ret_type.ty()?;
28 let ret_type_str = type_ref.syntax().text().to_string(); 38 let ret_type_str = type_ref.syntax().text().to_string();
@@ -34,16 +44,14 @@ pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContex
34 } 44 }
35 } 45 }
36 46
37 let block_expr = &fn_def.body()?;
38
39 acc.add( 47 acc.add(
40 AssistId("change_return_type_to_result", AssistKind::RefactorRewrite), 48 AssistId("change_return_type_to_result", AssistKind::RefactorRewrite),
41 "Wrap return type in Result", 49 "Wrap return type in Result",
42 type_ref.syntax().text_range(), 50 type_ref.syntax().text_range(),
43 |builder| { 51 |builder| {
44 let mut tail_return_expr_collector = TailReturnCollector::new(); 52 let mut tail_return_expr_collector = TailReturnCollector::new();
45 tail_return_expr_collector.collect_jump_exprs(block_expr, false); 53 tail_return_expr_collector.collect_jump_exprs(&block_expr, false);
46 tail_return_expr_collector.collect_tail_exprs(block_expr); 54 tail_return_expr_collector.collect_tail_exprs(&block_expr);
47 55
48 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap { 56 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
49 let ok_wrapped = make::expr_call( 57 let ok_wrapped = make::expr_call(
@@ -285,16 +293,20 @@ mod tests {
285 } 293 }
286 294
287 #[test] 295 #[test]
288 fn change_return_type_to_result_simple_return_type() { 296 fn change_return_type_to_result_simple_closure() {
289 check_assist( 297 check_assist(
290 change_return_type_to_result, 298 change_return_type_to_result,
291 r#"fn foo() -> i32<|> { 299 r#"fn foo() {
292 let test = "test"; 300 || -> i32<|> {
293 return 42i32; 301 let test = "test";
302 return 42i32;
303 };
294 }"#, 304 }"#,
295 r#"fn foo() -> Result<i32, ${0:_}> { 305 r#"fn foo() {
296 let test = "test"; 306 || -> Result<i32, ${0:_}> {
297 return Ok(42i32); 307 let test = "test";
308 return Ok(42i32);
309 };
298 }"#, 310 }"#,
299 ); 311 );
300 } 312 }
@@ -311,6 +323,29 @@ mod tests {
311 } 323 }
312 324
313 #[test] 325 #[test]
326 fn change_return_type_to_result_simple_return_type_bad_cursor_closure() {
327 check_assist_not_applicable(
328 change_return_type_to_result,
329 r#"fn foo() {
330 || -> i32 {
331 let test = "test";<|>
332 return 42i32;
333 };
334 }"#,
335 );
336 }
337
338 #[test]
339 fn change_return_type_to_result_closure_non_block() {
340 check_assist_not_applicable(
341 change_return_type_to_result,
342 r#"fn foo() {
343 || -> i<|>32 3;
344 }"#,
345 );
346 }
347
348 #[test]
314 fn change_return_type_to_result_simple_return_type_already_result_std() { 349 fn change_return_type_to_result_simple_return_type_already_result_std() {
315 check_assist_not_applicable( 350 check_assist_not_applicable(
316 change_return_type_to_result, 351 change_return_type_to_result,
@@ -334,6 +369,19 @@ mod tests {
334 } 369 }
335 370
336 #[test] 371 #[test]
372 fn change_return_type_to_result_simple_return_type_already_result_closure() {
373 check_assist_not_applicable(
374 change_return_type_to_result,
375 r#"fn foo() {
376 || -> Result<i32<|>, String> {
377 let test = "test";
378 return 42i32;
379 };
380 }"#,
381 );
382 }
383
384 #[test]
337 fn change_return_type_to_result_simple_with_cursor() { 385 fn change_return_type_to_result_simple_with_cursor() {
338 check_assist( 386 check_assist(
339 change_return_type_to_result, 387 change_return_type_to_result,
@@ -364,6 +412,25 @@ mod tests {
364 } 412 }
365 413
366 #[test] 414 #[test]
415 fn change_return_type_to_result_simple_with_tail_closure() {
416 check_assist(
417 change_return_type_to_result,
418 r#"fn foo() {
419 || -><|> i32 {
420 let test = "test";
421 42i32
422 };
423 }"#,
424 r#"fn foo() {
425 || -> Result<i32, ${0:_}> {
426 let test = "test";
427 Ok(42i32)
428 };
429 }"#,
430 );
431 }
432
433 #[test]
367 fn change_return_type_to_result_simple_with_tail_only() { 434 fn change_return_type_to_result_simple_with_tail_only() {
368 check_assist( 435 check_assist(
369 change_return_type_to_result, 436 change_return_type_to_result,
@@ -375,6 +442,7 @@ mod tests {
375 }"#, 442 }"#,
376 ); 443 );
377 } 444 }
445
378 #[test] 446 #[test]
379 fn change_return_type_to_result_simple_with_tail_block_like() { 447 fn change_return_type_to_result_simple_with_tail_block_like() {
380 check_assist( 448 check_assist(
@@ -397,6 +465,31 @@ mod tests {
397 } 465 }
398 466
399 #[test] 467 #[test]
468 fn change_return_type_to_result_simple_without_block_closure() {
469 check_assist(
470 change_return_type_to_result,
471 r#"fn foo() {
472 || -> i32<|> {
473 if true {
474 42i32
475 } else {
476 24i32
477 }
478 };
479 }"#,
480 r#"fn foo() {
481 || -> Result<i32, ${0:_}> {
482 if true {
483 Ok(42i32)
484 } else {
485 Ok(24i32)
486 }
487 };
488 }"#,
489 );
490 }
491
492 #[test]
400 fn change_return_type_to_result_simple_with_nested_if() { 493 fn change_return_type_to_result_simple_with_nested_if() {
401 check_assist( 494 check_assist(
402 change_return_type_to_result, 495 change_return_type_to_result,
diff --git a/crates/assists/src/handlers/convert_integer_literal.rs b/crates/assists/src/handlers/convert_integer_literal.rs
index ea35e833a..1094ed3f3 100644
--- a/crates/assists/src/handlers/convert_integer_literal.rs
+++ b/crates/assists/src/handlers/convert_integer_literal.rs
@@ -1,4 +1,4 @@
1use syntax::{ast, AstNode, SmolStr}; 1use syntax::{ast, ast::Radix, AstToken};
2 2
3use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; 3use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
4 4
@@ -15,40 +15,34 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
15// ``` 15// ```
16pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 16pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
17 let literal = ctx.find_node_at_offset::<ast::Literal>()?; 17 let literal = ctx.find_node_at_offset::<ast::Literal>()?;
18 let literal = match literal.kind() {
19 ast::LiteralKind::IntNumber(it) => it,
20 _ => return None,
21 };
22 let radix = literal.radix();
23 let value = literal.value()?;
24 let suffix = literal.suffix();
25
18 let range = literal.syntax().text_range(); 26 let range = literal.syntax().text_range();
19 let group_id = GroupLabel("Convert integer base".into()); 27 let group_id = GroupLabel("Convert integer base".into());
20 28
21 let suffix = match literal.kind() { 29 for &target_radix in Radix::ALL {
22 ast::LiteralKind::IntNumber { suffix } => suffix, 30 if target_radix == radix {
23 _ => return None,
24 };
25 let suffix_len = suffix.as_ref().map(|s| s.len()).unwrap_or(0);
26 let raw_literal_text = literal.syntax().to_string();
27
28 // Gets the literal's text without the type suffix and without underscores.
29 let literal_text = raw_literal_text
30 .chars()
31 .take(raw_literal_text.len() - suffix_len)
32 .filter(|c| *c != '_')
33 .collect::<SmolStr>();
34 let literal_base = IntegerLiteralBase::identify(&literal_text)?;
35
36 for base in IntegerLiteralBase::bases() {
37 if *base == literal_base {
38 continue; 31 continue;
39 } 32 }
40 33
41 let mut converted = literal_base.convert(&literal_text, base); 34 let mut converted = match target_radix {
42 35 Radix::Binary => format!("0b{:b}", value),
43 let label = if let Some(suffix) = &suffix { 36 Radix::Octal => format!("0o{:o}", value),
44 format!("Convert {} ({}) to {}", &literal_text, suffix, &converted) 37 Radix::Decimal => value.to_string(),
45 } else { 38 Radix::Hexadecimal => format!("0x{:X}", value),
46 format!("Convert {} to {}", &literal_text, &converted)
47 }; 39 };
48 40
41 let label = format!("Convert {} to {}{}", literal, converted, suffix.unwrap_or_default());
42
49 // Appends the type suffix back into the new literal if it exists. 43 // Appends the type suffix back into the new literal if it exists.
50 if let Some(suffix) = &suffix { 44 if let Some(suffix) = suffix {
51 converted.push_str(&suffix); 45 converted.push_str(suffix);
52 } 46 }
53 47
54 acc.add_group( 48 acc.add_group(
@@ -63,79 +57,11 @@ pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) ->
63 Some(()) 57 Some(())
64} 58}
65 59
66#[derive(Debug, PartialEq, Eq)]
67enum IntegerLiteralBase {
68 Binary,
69 Octal,
70 Decimal,
71 Hexadecimal,
72}
73
74impl IntegerLiteralBase {
75 fn identify(literal_text: &str) -> Option<Self> {
76 // We cannot express a literal in anything other than decimal in under 3 characters, so we return here if possible.
77 if literal_text.len() < 3 && literal_text.chars().all(|c| c.is_digit(10)) {
78 return Some(Self::Decimal);
79 }
80
81 let base = match &literal_text[..2] {
82 "0b" => Self::Binary,
83 "0o" => Self::Octal,
84 "0x" => Self::Hexadecimal,
85 _ => Self::Decimal,
86 };
87
88 // Checks that all characters after the base prefix are all valid digits for that base.
89 if literal_text[base.prefix_len()..].chars().all(|c| c.is_digit(base.base())) {
90 Some(base)
91 } else {
92 None
93 }
94 }
95
96 fn convert(&self, literal_text: &str, to: &IntegerLiteralBase) -> String {
97 let digits = &literal_text[self.prefix_len()..];
98 let value = u128::from_str_radix(digits, self.base()).unwrap();
99
100 match to {
101 Self::Binary => format!("0b{:b}", value),
102 Self::Octal => format!("0o{:o}", value),
103 Self::Decimal => value.to_string(),
104 Self::Hexadecimal => format!("0x{:X}", value),
105 }
106 }
107
108 const fn base(&self) -> u32 {
109 match self {
110 Self::Binary => 2,
111 Self::Octal => 8,
112 Self::Decimal => 10,
113 Self::Hexadecimal => 16,
114 }
115 }
116
117 const fn prefix_len(&self) -> usize {
118 match self {
119 Self::Decimal => 0,
120 _ => 2,
121 }
122 }
123
124 const fn bases() -> &'static [IntegerLiteralBase] {
125 &[
126 IntegerLiteralBase::Binary,
127 IntegerLiteralBase::Octal,
128 IntegerLiteralBase::Decimal,
129 IntegerLiteralBase::Hexadecimal,
130 ]
131 }
132}
133
134#[cfg(test)] 60#[cfg(test)]
135mod tests { 61mod tests {
62 use crate::tests::{check_assist_by_label, check_assist_not_applicable, check_assist_target};
136 63
137 use super::*; 64 use super::*;
138 use crate::tests::{check_assist_by_label, check_assist_target};
139 65
140 #[test] 66 #[test]
141 fn binary_target() { 67 fn binary_target() {
@@ -317,21 +243,21 @@ mod tests {
317 convert_integer_literal, 243 convert_integer_literal,
318 before, 244 before,
319 "const _: i32 = 0b1111101000;", 245 "const _: i32 = 0b1111101000;",
320 "Convert 1000 to 0b1111101000", 246 "Convert 1_00_0 to 0b1111101000",
321 ); 247 );
322 248
323 check_assist_by_label( 249 check_assist_by_label(
324 convert_integer_literal, 250 convert_integer_literal,
325 before, 251 before,
326 "const _: i32 = 0o1750;", 252 "const _: i32 = 0o1750;",
327 "Convert 1000 to 0o1750", 253 "Convert 1_00_0 to 0o1750",
328 ); 254 );
329 255
330 check_assist_by_label( 256 check_assist_by_label(
331 convert_integer_literal, 257 convert_integer_literal,
332 before, 258 before,
333 "const _: i32 = 0x3E8;", 259 "const _: i32 = 0x3E8;",
334 "Convert 1000 to 0x3E8", 260 "Convert 1_00_0 to 0x3E8",
335 ); 261 );
336 } 262 }
337 263
@@ -343,21 +269,21 @@ mod tests {
343 convert_integer_literal, 269 convert_integer_literal,
344 before, 270 before,
345 "const _: i32 = 0b1010;", 271 "const _: i32 = 0b1010;",
346 "Convert 10 to 0b1010", 272 "Convert 1_0 to 0b1010",
347 ); 273 );
348 274
349 check_assist_by_label( 275 check_assist_by_label(
350 convert_integer_literal, 276 convert_integer_literal,
351 before, 277 before,
352 "const _: i32 = 0o12;", 278 "const _: i32 = 0o12;",
353 "Convert 10 to 0o12", 279 "Convert 1_0 to 0o12",
354 ); 280 );
355 281
356 check_assist_by_label( 282 check_assist_by_label(
357 convert_integer_literal, 283 convert_integer_literal,
358 before, 284 before,
359 "const _: i32 = 0xA;", 285 "const _: i32 = 0xA;",
360 "Convert 10 to 0xA", 286 "Convert 1_0 to 0xA",
361 ); 287 );
362 } 288 }
363 289
@@ -369,21 +295,21 @@ mod tests {
369 convert_integer_literal, 295 convert_integer_literal,
370 before, 296 before,
371 "const _: i32 = 0b11111111;", 297 "const _: i32 = 0b11111111;",
372 "Convert 0xFF to 0b11111111", 298 "Convert 0x_F_F to 0b11111111",
373 ); 299 );
374 300
375 check_assist_by_label( 301 check_assist_by_label(
376 convert_integer_literal, 302 convert_integer_literal,
377 before, 303 before,
378 "const _: i32 = 0o377;", 304 "const _: i32 = 0o377;",
379 "Convert 0xFF to 0o377", 305 "Convert 0x_F_F to 0o377",
380 ); 306 );
381 307
382 check_assist_by_label( 308 check_assist_by_label(
383 convert_integer_literal, 309 convert_integer_literal,
384 before, 310 before,
385 "const _: i32 = 255;", 311 "const _: i32 = 255;",
386 "Convert 0xFF to 255", 312 "Convert 0x_F_F to 255",
387 ); 313 );
388 } 314 }
389 315
@@ -395,21 +321,21 @@ mod tests {
395 convert_integer_literal, 321 convert_integer_literal,
396 before, 322 before,
397 "const _: i32 = 0o377;", 323 "const _: i32 = 0o377;",
398 "Convert 0b11111111 to 0o377", 324 "Convert 0b1111_1111 to 0o377",
399 ); 325 );
400 326
401 check_assist_by_label( 327 check_assist_by_label(
402 convert_integer_literal, 328 convert_integer_literal,
403 before, 329 before,
404 "const _: i32 = 255;", 330 "const _: i32 = 255;",
405 "Convert 0b11111111 to 255", 331 "Convert 0b1111_1111 to 255",
406 ); 332 );
407 333
408 check_assist_by_label( 334 check_assist_by_label(
409 convert_integer_literal, 335 convert_integer_literal,
410 before, 336 before,
411 "const _: i32 = 0xFF;", 337 "const _: i32 = 0xFF;",
412 "Convert 0b11111111 to 0xFF", 338 "Convert 0b1111_1111 to 0xFF",
413 ); 339 );
414 } 340 }
415 341
@@ -421,21 +347,21 @@ mod tests {
421 convert_integer_literal, 347 convert_integer_literal,
422 before, 348 before,
423 "const _: i32 = 0b11111111;", 349 "const _: i32 = 0b11111111;",
424 "Convert 0o377 to 0b11111111", 350 "Convert 0o3_77 to 0b11111111",
425 ); 351 );
426 352
427 check_assist_by_label( 353 check_assist_by_label(
428 convert_integer_literal, 354 convert_integer_literal,
429 before, 355 before,
430 "const _: i32 = 255;", 356 "const _: i32 = 255;",
431 "Convert 0o377 to 255", 357 "Convert 0o3_77 to 255",
432 ); 358 );
433 359
434 check_assist_by_label( 360 check_assist_by_label(
435 convert_integer_literal, 361 convert_integer_literal,
436 before, 362 before,
437 "const _: i32 = 0xFF;", 363 "const _: i32 = 0xFF;",
438 "Convert 0o377 to 0xFF", 364 "Convert 0o3_77 to 0xFF",
439 ); 365 );
440 } 366 }
441 367
@@ -447,21 +373,21 @@ mod tests {
447 convert_integer_literal, 373 convert_integer_literal,
448 before, 374 before,
449 "const _: i32 = 0b1111101000i32;", 375 "const _: i32 = 0b1111101000i32;",
450 "Convert 1000 (i32) to 0b1111101000", 376 "Convert 1000i32 to 0b1111101000i32",
451 ); 377 );
452 378
453 check_assist_by_label( 379 check_assist_by_label(
454 convert_integer_literal, 380 convert_integer_literal,
455 before, 381 before,
456 "const _: i32 = 0o1750i32;", 382 "const _: i32 = 0o1750i32;",
457 "Convert 1000 (i32) to 0o1750", 383 "Convert 1000i32 to 0o1750i32",
458 ); 384 );
459 385
460 check_assist_by_label( 386 check_assist_by_label(
461 convert_integer_literal, 387 convert_integer_literal,
462 before, 388 before,
463 "const _: i32 = 0x3E8i32;", 389 "const _: i32 = 0x3E8i32;",
464 "Convert 1000 (i32) to 0x3E8", 390 "Convert 1000i32 to 0x3E8i32",
465 ); 391 );
466 } 392 }
467 393
@@ -473,21 +399,21 @@ mod tests {
473 convert_integer_literal, 399 convert_integer_literal,
474 before, 400 before,
475 "const _: i32 = 0b1010i32;", 401 "const _: i32 = 0b1010i32;",
476 "Convert 10 (i32) to 0b1010", 402 "Convert 10i32 to 0b1010i32",
477 ); 403 );
478 404
479 check_assist_by_label( 405 check_assist_by_label(
480 convert_integer_literal, 406 convert_integer_literal,
481 before, 407 before,
482 "const _: i32 = 0o12i32;", 408 "const _: i32 = 0o12i32;",
483 "Convert 10 (i32) to 0o12", 409 "Convert 10i32 to 0o12i32",
484 ); 410 );
485 411
486 check_assist_by_label( 412 check_assist_by_label(
487 convert_integer_literal, 413 convert_integer_literal,
488 before, 414 before,
489 "const _: i32 = 0xAi32;", 415 "const _: i32 = 0xAi32;",
490 "Convert 10 (i32) to 0xA", 416 "Convert 10i32 to 0xAi32",
491 ); 417 );
492 } 418 }
493 419
@@ -499,21 +425,21 @@ mod tests {
499 convert_integer_literal, 425 convert_integer_literal,
500 before, 426 before,
501 "const _: i32 = 0b11111111i32;", 427 "const _: i32 = 0b11111111i32;",
502 "Convert 0xFF (i32) to 0b11111111", 428 "Convert 0xFFi32 to 0b11111111i32",
503 ); 429 );
504 430
505 check_assist_by_label( 431 check_assist_by_label(
506 convert_integer_literal, 432 convert_integer_literal,
507 before, 433 before,
508 "const _: i32 = 0o377i32;", 434 "const _: i32 = 0o377i32;",
509 "Convert 0xFF (i32) to 0o377", 435 "Convert 0xFFi32 to 0o377i32",
510 ); 436 );
511 437
512 check_assist_by_label( 438 check_assist_by_label(
513 convert_integer_literal, 439 convert_integer_literal,
514 before, 440 before,
515 "const _: i32 = 255i32;", 441 "const _: i32 = 255i32;",
516 "Convert 0xFF (i32) to 255", 442 "Convert 0xFFi32 to 255i32",
517 ); 443 );
518 } 444 }
519 445
@@ -525,21 +451,21 @@ mod tests {
525 convert_integer_literal, 451 convert_integer_literal,
526 before, 452 before,
527 "const _: i32 = 0o377i32;", 453 "const _: i32 = 0o377i32;",
528 "Convert 0b11111111 (i32) to 0o377", 454 "Convert 0b11111111i32 to 0o377i32",
529 ); 455 );
530 456
531 check_assist_by_label( 457 check_assist_by_label(
532 convert_integer_literal, 458 convert_integer_literal,
533 before, 459 before,
534 "const _: i32 = 255i32;", 460 "const _: i32 = 255i32;",
535 "Convert 0b11111111 (i32) to 255", 461 "Convert 0b11111111i32 to 255i32",
536 ); 462 );
537 463
538 check_assist_by_label( 464 check_assist_by_label(
539 convert_integer_literal, 465 convert_integer_literal,
540 before, 466 before,
541 "const _: i32 = 0xFFi32;", 467 "const _: i32 = 0xFFi32;",
542 "Convert 0b11111111 (i32) to 0xFF", 468 "Convert 0b11111111i32 to 0xFFi32",
543 ); 469 );
544 } 470 }
545 471
@@ -551,21 +477,21 @@ mod tests {
551 convert_integer_literal, 477 convert_integer_literal,
552 before, 478 before,
553 "const _: i32 = 0b11111111i32;", 479 "const _: i32 = 0b11111111i32;",
554 "Convert 0o377 (i32) to 0b11111111", 480 "Convert 0o377i32 to 0b11111111i32",
555 ); 481 );
556 482
557 check_assist_by_label( 483 check_assist_by_label(
558 convert_integer_literal, 484 convert_integer_literal,
559 before, 485 before,
560 "const _: i32 = 255i32;", 486 "const _: i32 = 255i32;",
561 "Convert 0o377 (i32) to 255", 487 "Convert 0o377i32 to 255i32",
562 ); 488 );
563 489
564 check_assist_by_label( 490 check_assist_by_label(
565 convert_integer_literal, 491 convert_integer_literal,
566 before, 492 before,
567 "const _: i32 = 0xFFi32;", 493 "const _: i32 = 0xFFi32;",
568 "Convert 0o377 (i32) to 0xFF", 494 "Convert 0o377i32 to 0xFFi32",
569 ); 495 );
570 } 496 }
571 497
@@ -577,21 +503,21 @@ mod tests {
577 convert_integer_literal, 503 convert_integer_literal,
578 before, 504 before,
579 "const _: i32 = 0b1111101000i32;", 505 "const _: i32 = 0b1111101000i32;",
580 "Convert 1000 (i32) to 0b1111101000", 506 "Convert 1_00_0i32 to 0b1111101000i32",
581 ); 507 );
582 508
583 check_assist_by_label( 509 check_assist_by_label(
584 convert_integer_literal, 510 convert_integer_literal,
585 before, 511 before,
586 "const _: i32 = 0o1750i32;", 512 "const _: i32 = 0o1750i32;",
587 "Convert 1000 (i32) to 0o1750", 513 "Convert 1_00_0i32 to 0o1750i32",
588 ); 514 );
589 515
590 check_assist_by_label( 516 check_assist_by_label(
591 convert_integer_literal, 517 convert_integer_literal,
592 before, 518 before,
593 "const _: i32 = 0x3E8i32;", 519 "const _: i32 = 0x3E8i32;",
594 "Convert 1000 (i32) to 0x3E8", 520 "Convert 1_00_0i32 to 0x3E8i32",
595 ); 521 );
596 } 522 }
597 523
@@ -603,21 +529,21 @@ mod tests {
603 convert_integer_literal, 529 convert_integer_literal,
604 before, 530 before,
605 "const _: i32 = 0b1010i32;", 531 "const _: i32 = 0b1010i32;",
606 "Convert 10 (i32) to 0b1010", 532 "Convert 1_0i32 to 0b1010i32",
607 ); 533 );
608 534
609 check_assist_by_label( 535 check_assist_by_label(
610 convert_integer_literal, 536 convert_integer_literal,
611 before, 537 before,
612 "const _: i32 = 0o12i32;", 538 "const _: i32 = 0o12i32;",
613 "Convert 10 (i32) to 0o12", 539 "Convert 1_0i32 to 0o12i32",
614 ); 540 );
615 541
616 check_assist_by_label( 542 check_assist_by_label(
617 convert_integer_literal, 543 convert_integer_literal,
618 before, 544 before,
619 "const _: i32 = 0xAi32;", 545 "const _: i32 = 0xAi32;",
620 "Convert 10 (i32) to 0xA", 546 "Convert 1_0i32 to 0xAi32",
621 ); 547 );
622 } 548 }
623 549
@@ -629,21 +555,21 @@ mod tests {
629 convert_integer_literal, 555 convert_integer_literal,
630 before, 556 before,
631 "const _: i32 = 0b11111111i32;", 557 "const _: i32 = 0b11111111i32;",
632 "Convert 0xFF (i32) to 0b11111111", 558 "Convert 0x_F_Fi32 to 0b11111111i32",
633 ); 559 );
634 560
635 check_assist_by_label( 561 check_assist_by_label(
636 convert_integer_literal, 562 convert_integer_literal,
637 before, 563 before,
638 "const _: i32 = 0o377i32;", 564 "const _: i32 = 0o377i32;",
639 "Convert 0xFF (i32) to 0o377", 565 "Convert 0x_F_Fi32 to 0o377i32",
640 ); 566 );
641 567
642 check_assist_by_label( 568 check_assist_by_label(
643 convert_integer_literal, 569 convert_integer_literal,
644 before, 570 before,
645 "const _: i32 = 255i32;", 571 "const _: i32 = 255i32;",
646 "Convert 0xFF (i32) to 255", 572 "Convert 0x_F_Fi32 to 255i32",
647 ); 573 );
648 } 574 }
649 575
@@ -655,21 +581,21 @@ mod tests {
655 convert_integer_literal, 581 convert_integer_literal,
656 before, 582 before,
657 "const _: i32 = 0o377i32;", 583 "const _: i32 = 0o377i32;",
658 "Convert 0b11111111 (i32) to 0o377", 584 "Convert 0b1111_1111i32 to 0o377i32",
659 ); 585 );
660 586
661 check_assist_by_label( 587 check_assist_by_label(
662 convert_integer_literal, 588 convert_integer_literal,
663 before, 589 before,
664 "const _: i32 = 255i32;", 590 "const _: i32 = 255i32;",
665 "Convert 0b11111111 (i32) to 255", 591 "Convert 0b1111_1111i32 to 255i32",
666 ); 592 );
667 593
668 check_assist_by_label( 594 check_assist_by_label(
669 convert_integer_literal, 595 convert_integer_literal,
670 before, 596 before,
671 "const _: i32 = 0xFFi32;", 597 "const _: i32 = 0xFFi32;",
672 "Convert 0b11111111 (i32) to 0xFF", 598 "Convert 0b1111_1111i32 to 0xFFi32",
673 ); 599 );
674 } 600 }
675 601
@@ -681,21 +607,28 @@ mod tests {
681 convert_integer_literal, 607 convert_integer_literal,
682 before, 608 before,
683 "const _: i32 = 0b11111111i32;", 609 "const _: i32 = 0b11111111i32;",
684 "Convert 0o377 (i32) to 0b11111111", 610 "Convert 0o3_77i32 to 0b11111111i32",
685 ); 611 );
686 612
687 check_assist_by_label( 613 check_assist_by_label(
688 convert_integer_literal, 614 convert_integer_literal,
689 before, 615 before,
690 "const _: i32 = 255i32;", 616 "const _: i32 = 255i32;",
691 "Convert 0o377 (i32) to 255", 617 "Convert 0o3_77i32 to 255i32",
692 ); 618 );
693 619
694 check_assist_by_label( 620 check_assist_by_label(
695 convert_integer_literal, 621 convert_integer_literal,
696 before, 622 before,
697 "const _: i32 = 0xFFi32;", 623 "const _: i32 = 0xFFi32;",
698 "Convert 0o377 (i32) to 0xFF", 624 "Convert 0o3_77i32 to 0xFFi32",
699 ); 625 );
700 } 626 }
627
628 #[test]
629 fn convert_overflowing_literal() {
630 let before = "const _: i32 =
631 111111111111111111111111111111111111111111111111111111111111111111111111<|>;";
632 check_assist_not_applicable(convert_integer_literal, before);
633 }
701} 634}
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs
index 316a58d88..853266395 100644
--- a/crates/assists/src/handlers/expand_glob_import.rs
+++ b/crates/assists/src/handlers/expand_glob_import.rs
@@ -41,7 +41,7 @@ use crate::{
41// fn qux(bar: Bar, baz: Baz) {} 41// fn qux(bar: Bar, baz: Baz) {}
42// ``` 42// ```
43pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 43pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
44 let star = ctx.find_token_at_offset(T![*])?; 44 let star = ctx.find_token_syntax_at_offset(T![*])?;
45 let (parent, mod_path) = find_parent_and_path(&star)?; 45 let (parent, mod_path) = find_parent_and_path(&star)?;
46 let target_module = match ctx.sema.resolve_path(&mod_path)? { 46 let target_module = match ctx.sema.resolve_path(&mod_path)? {
47 PathResolution::Def(ModuleDef::Module(it)) => it, 47 PathResolution::Def(ModuleDef::Module(it)) => it,
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
index 178718c5e..14209b771 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -1,16 +1,17 @@
1use hir::{EnumVariant, Module, ModuleDef, Name}; 1use std::iter;
2use ide_db::base_db::FileId; 2
3use either::Either;
4use hir::{AsName, EnumVariant, Module, ModuleDef, Name};
3use ide_db::{defs::Definition, search::Reference, RootDatabase}; 5use ide_db::{defs::Definition, search::Reference, RootDatabase};
4use itertools::Itertools; 6use rustc_hash::{FxHashMap, FxHashSet};
5use rustc_hash::FxHashSet;
6use syntax::{ 7use syntax::{
7 algo::find_node_at_offset, 8 algo::find_node_at_offset,
8 ast::{self, edit::IndentLevel, ArgListOwner, AstNode, NameOwner, VisibilityOwner}, 9 algo::SyntaxRewriter,
9 SourceFile, TextRange, TextSize, 10 ast::{self, edit::IndentLevel, make, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
11 SourceFile, SyntaxElement,
10}; 12};
11 13
12use crate::{ 14use crate::{
13 assist_context::AssistBuilder,
14 utils::{insert_use, mod_path_to_ast, ImportScope}, 15 utils::{insert_use, mod_path_to_ast, ImportScope},
15 AssistContext, AssistId, AssistKind, Assists, 16 AssistContext, AssistId, AssistKind, Assists,
16}; 17};
@@ -33,43 +34,39 @@ pub(crate) fn extract_struct_from_enum_variant(
33 ctx: &AssistContext, 34 ctx: &AssistContext,
34) -> Option<()> { 35) -> Option<()> {
35 let variant = ctx.find_node_at_offset::<ast::Variant>()?; 36 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
36 let field_list = match variant.kind() { 37 let field_list = extract_field_list_if_applicable(&variant)?;
37 ast::StructKind::Tuple(field_list) => field_list,
38 _ => return None,
39 };
40 38
41 // skip 1-tuple variants 39 let variant_name = variant.name()?;
42 if field_list.fields().count() == 1 {
43 return None;
44 }
45
46 let variant_name = variant.name()?.to_string();
47 let variant_hir = ctx.sema.to_def(&variant)?; 40 let variant_hir = ctx.sema.to_def(&variant)?;
48 if existing_struct_def(ctx.db(), &variant_name, &variant_hir) { 41 if existing_definition(ctx.db(), &variant_name, &variant_hir) {
49 return None; 42 return None;
50 } 43 }
44
51 let enum_ast = variant.parent_enum(); 45 let enum_ast = variant.parent_enum();
52 let visibility = enum_ast.visibility();
53 let enum_hir = ctx.sema.to_def(&enum_ast)?; 46 let enum_hir = ctx.sema.to_def(&enum_ast)?;
54 let variant_hir_name = variant_hir.name(ctx.db());
55 let enum_module_def = ModuleDef::from(enum_hir);
56 let current_module = enum_hir.module(ctx.db());
57 let target = variant.syntax().text_range(); 47 let target = variant.syntax().text_range();
58 acc.add( 48 acc.add(
59 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite), 49 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
60 "Extract struct from enum variant", 50 "Extract struct from enum variant",
61 target, 51 target,
62 |builder| { 52 |builder| {
63 let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir)); 53 let variant_hir_name = variant_hir.name(ctx.db());
64 let res = definition.usages(&ctx.sema).all(); 54 let enum_module_def = ModuleDef::from(enum_hir);
65 let start_offset = variant.parent_enum().syntax().text_range().start(); 55 let usages =
56 Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir)).usages(&ctx.sema).all();
57
66 let mut visited_modules_set = FxHashSet::default(); 58 let mut visited_modules_set = FxHashSet::default();
59 let current_module = enum_hir.module(ctx.db());
67 visited_modules_set.insert(current_module); 60 visited_modules_set.insert(current_module);
68 for reference in res { 61 let mut rewriters = FxHashMap::default();
62 for reference in usages {
63 let rewriter = rewriters
64 .entry(reference.file_range.file_id)
65 .or_insert_with(SyntaxRewriter::default);
69 let source_file = ctx.sema.parse(reference.file_range.file_id); 66 let source_file = ctx.sema.parse(reference.file_range.file_id);
70 update_reference( 67 update_reference(
71 ctx, 68 ctx,
72 builder, 69 rewriter,
73 reference, 70 reference,
74 &source_file, 71 &source_file,
75 &enum_module_def, 72 &enum_module_def,
@@ -77,34 +74,62 @@ pub(crate) fn extract_struct_from_enum_variant(
77 &mut visited_modules_set, 74 &mut visited_modules_set,
78 ); 75 );
79 } 76 }
77 let mut rewriter =
78 rewriters.remove(&ctx.frange.file_id).unwrap_or_else(SyntaxRewriter::default);
79 for (file_id, rewriter) in rewriters {
80 builder.edit_file(file_id);
81 builder.rewrite(rewriter);
82 }
83 builder.edit_file(ctx.frange.file_id);
84 update_variant(&mut rewriter, &variant);
80 extract_struct_def( 85 extract_struct_def(
81 builder, 86 &mut rewriter,
82 &enum_ast, 87 &enum_ast,
83 &variant_name, 88 variant_name.clone(),
84 &field_list.to_string(), 89 &field_list,
85 start_offset, 90 &variant.parent_enum().syntax().clone().into(),
86 ctx.frange.file_id, 91 enum_ast.visibility(),
87 &visibility,
88 ); 92 );
89 let list_range = field_list.syntax().text_range(); 93 builder.rewrite(rewriter);
90 update_variant(builder, &variant_name, ctx.frange.file_id, list_range);
91 }, 94 },
92 ) 95 )
93} 96}
94 97
95fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool { 98fn extract_field_list_if_applicable(
99 variant: &ast::Variant,
100) -> Option<Either<ast::RecordFieldList, ast::TupleFieldList>> {
101 match variant.kind() {
102 ast::StructKind::Record(field_list) if field_list.fields().next().is_some() => {
103 Some(Either::Left(field_list))
104 }
105 ast::StructKind::Tuple(field_list) if field_list.fields().count() > 1 => {
106 Some(Either::Right(field_list))
107 }
108 _ => None,
109 }
110}
111
112fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &EnumVariant) -> bool {
96 variant 113 variant
97 .parent_enum(db) 114 .parent_enum(db)
98 .module(db) 115 .module(db)
99 .scope(db, None) 116 .scope(db, None)
100 .into_iter() 117 .into_iter()
101 .any(|(name, _)| name.to_string() == variant_name) 118 .filter(|(_, def)| match def {
119 // only check type-namespace
120 hir::ScopeDef::ModuleDef(def) => matches!(def,
121 ModuleDef::Module(_) | ModuleDef::Adt(_) |
122 ModuleDef::EnumVariant(_) | ModuleDef::Trait(_) |
123 ModuleDef::TypeAlias(_) | ModuleDef::BuiltinType(_)
124 ),
125 _ => false,
126 })
127 .any(|(name, _)| name == variant_name.as_name())
102} 128}
103 129
104#[allow(dead_code)]
105fn insert_import( 130fn insert_import(
106 ctx: &AssistContext, 131 ctx: &AssistContext,
107 builder: &mut AssistBuilder, 132 rewriter: &mut SyntaxRewriter,
108 path: &ast::PathExpr, 133 path: &ast::PathExpr,
109 module: &Module, 134 module: &Module,
110 enum_module_def: &ModuleDef, 135 enum_module_def: &ModuleDef,
@@ -116,69 +141,68 @@ fn insert_import(
116 mod_path.segments.pop(); 141 mod_path.segments.pop();
117 mod_path.segments.push(variant_hir_name.clone()); 142 mod_path.segments.push(variant_hir_name.clone());
118 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?; 143 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
119 let syntax = scope.as_syntax_node();
120 144
121 let new_syntax = 145 *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
122 insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
123 // FIXME: this will currently panic as multiple imports will have overlapping text ranges
124 builder.replace(syntax.text_range(), new_syntax.to_string())
125 } 146 }
126 Some(()) 147 Some(())
127} 148}
128 149
129// FIXME: this should use strongly-typed `make`, rather than string manipulation.
130fn extract_struct_def( 150fn extract_struct_def(
131 builder: &mut AssistBuilder, 151 rewriter: &mut SyntaxRewriter,
132 enum_: &ast::Enum, 152 enum_: &ast::Enum,
133 variant_name: &str, 153 variant_name: ast::Name,
134 variant_list: &str, 154 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
135 start_offset: TextSize, 155 start_offset: &SyntaxElement,
136 file_id: FileId, 156 visibility: Option<ast::Visibility>,
137 visibility: &Option<ast::Visibility>,
138) -> Option<()> { 157) -> Option<()> {
139 let visibility_string = if let Some(visibility) = visibility { 158 let pub_vis = Some(make::visibility_pub());
140 format!("{} ", visibility.to_string()) 159 let field_list = match field_list {
141 } else { 160 Either::Left(field_list) => {
142 "".to_string() 161 make::record_field_list(field_list.fields().flat_map(|field| {
162 Some(make::record_field(pub_vis.clone(), field.name()?, field.ty()?))
163 }))
164 .into()
165 }
166 Either::Right(field_list) => make::tuple_field_list(
167 field_list
168 .fields()
169 .flat_map(|field| Some(make::tuple_field(pub_vis.clone(), field.ty()?))),
170 )
171 .into(),
143 }; 172 };
144 let indent = IndentLevel::from_node(enum_.syntax()); 173
145 let struct_def = format!( 174 rewriter.insert_before(
146 r#"{}struct {}{}; 175 start_offset,
147 176 make::struct_(visibility, variant_name, None, field_list).syntax(),
148{}"#,
149 visibility_string,
150 variant_name,
151 list_with_visibility(variant_list),
152 indent
153 ); 177 );
154 builder.edit_file(file_id); 178 rewriter.insert_before(start_offset, &make::tokens::blank_line());
155 builder.insert(start_offset, struct_def); 179
180 if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
181 rewriter
182 .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
183 }
156 Some(()) 184 Some(())
157} 185}
158 186
159fn update_variant( 187fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> {
160 builder: &mut AssistBuilder, 188 let name = variant.name()?;
161 variant_name: &str, 189 let tuple_field = make::tuple_field(None, make::ty(name.text()));
162 file_id: FileId, 190 let replacement = make::variant(
163 list_range: TextRange, 191 name,
164) -> Option<()> { 192 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
165 let inside_variant_range = TextRange::new(
166 list_range.start().checked_add(TextSize::from(1))?,
167 list_range.end().checked_sub(TextSize::from(1))?,
168 ); 193 );
169 builder.edit_file(file_id); 194 rewriter.replace(variant.syntax(), replacement.syntax());
170 builder.replace(inside_variant_range, variant_name);
171 Some(()) 195 Some(())
172} 196}
173 197
174fn update_reference( 198fn update_reference(
175 ctx: &AssistContext, 199 ctx: &AssistContext,
176 builder: &mut AssistBuilder, 200 rewriter: &mut SyntaxRewriter,
177 reference: Reference, 201 reference: Reference,
178 source_file: &SourceFile, 202 source_file: &SourceFile,
179 _enum_module_def: &ModuleDef, 203 enum_module_def: &ModuleDef,
180 _variant_hir_name: &Name, 204 variant_hir_name: &Name,
181 _visited_modules_set: &mut FxHashSet<Module>, 205 visited_modules_set: &mut FxHashSet<Module>,
182) -> Option<()> { 206) -> Option<()> {
183 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>( 207 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
184 source_file.syntax(), 208 source_file.syntax(),
@@ -187,35 +211,21 @@ fn update_reference(
187 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; 211 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
188 let list = call.arg_list()?; 212 let list = call.arg_list()?;
189 let segment = path_expr.path()?.segment()?; 213 let segment = path_expr.path()?.segment()?;
190 let _module = ctx.sema.scope(&path_expr.syntax()).module()?; 214 let module = ctx.sema.scope(&path_expr.syntax()).module()?;
191 let list_range = list.syntax().text_range();
192 let inside_list_range = TextRange::new(
193 list_range.start().checked_add(TextSize::from(1))?,
194 list_range.end().checked_sub(TextSize::from(1))?,
195 );
196 builder.edit_file(reference.file_range.file_id);
197 /* FIXME: this most likely requires AST-based editing, see `insert_import`
198 if !visited_modules_set.contains(&module) { 215 if !visited_modules_set.contains(&module) {
199 if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name) 216 if insert_import(ctx, rewriter, &path_expr, &module, enum_module_def, variant_hir_name)
200 .is_some() 217 .is_some()
201 { 218 {
202 visited_modules_set.insert(module); 219 visited_modules_set.insert(module);
203 } 220 }
204 } 221 }
205 */
206 builder.replace(inside_list_range, format!("{}{}", segment, list));
207 Some(())
208}
209 222
210fn list_with_visibility(list: &str) -> String { 223 let lparen = syntax::SyntaxElement::from(list.l_paren_token()?);
211 list.split(',') 224 let rparen = syntax::SyntaxElement::from(list.r_paren_token()?);
212 .map(|part| { 225 rewriter.insert_after(&lparen, segment.syntax());
213 let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 }; 226 rewriter.insert_after(&lparen, &lparen);
214 let mut mod_part = part.trim().to_string(); 227 rewriter.insert_before(&rparen, &rparen);
215 mod_part.insert_str(index, "pub "); 228 Some(())
216 mod_part
217 })
218 .join(", ")
219} 229}
220 230
221#[cfg(test)] 231#[cfg(test)]
@@ -228,7 +238,7 @@ mod tests {
228 use super::*; 238 use super::*;
229 239
230 #[test] 240 #[test]
231 fn test_extract_struct_several_fields() { 241 fn test_extract_struct_several_fields_tuple() {
232 check_assist( 242 check_assist(
233 extract_struct_from_enum_variant, 243 extract_struct_from_enum_variant,
234 "enum A { <|>One(u32, u32) }", 244 "enum A { <|>One(u32, u32) }",
@@ -239,6 +249,41 @@ enum A { One(One) }"#,
239 } 249 }
240 250
241 #[test] 251 #[test]
252 fn test_extract_struct_several_fields_named() {
253 check_assist(
254 extract_struct_from_enum_variant,
255 "enum A { <|>One { foo: u32, bar: u32 } }",
256 r#"struct One{ pub foo: u32, pub bar: u32 }
257
258enum A { One(One) }"#,
259 );
260 }
261
262 #[test]
263 fn test_extract_struct_one_field_named() {
264 check_assist(
265 extract_struct_from_enum_variant,
266 "enum A { <|>One { foo: u32 } }",
267 r#"struct One{ pub foo: u32 }
268
269enum A { One(One) }"#,
270 );
271 }
272
273 #[test]
274 fn test_extract_enum_variant_name_value_namespace() {
275 check_assist(
276 extract_struct_from_enum_variant,
277 r#"const One: () = ();
278enum A { <|>One(u32, u32) }"#,
279 r#"const One: () = ();
280struct One(pub u32, pub u32);
281
282enum A { One(One) }"#,
283 );
284 }
285
286 #[test]
242 fn test_extract_struct_pub_visibility() { 287 fn test_extract_struct_pub_visibility() {
243 check_assist( 288 check_assist(
244 extract_struct_from_enum_variant, 289 extract_struct_from_enum_variant,
@@ -250,7 +295,6 @@ pub enum A { One(One) }"#,
250 } 295 }
251 296
252 #[test] 297 #[test]
253 #[ignore] // FIXME: this currently panics if `insert_import` is used
254 fn test_extract_struct_with_complex_imports() { 298 fn test_extract_struct_with_complex_imports() {
255 check_assist( 299 check_assist(
256 extract_struct_from_enum_variant, 300 extract_struct_from_enum_variant,
@@ -316,7 +360,7 @@ fn another_fn() {
316 fn test_extract_enum_not_applicable_if_struct_exists() { 360 fn test_extract_enum_not_applicable_if_struct_exists() {
317 check_not_applicable( 361 check_not_applicable(
318 r#"struct One; 362 r#"struct One;
319 enum A { <|>One(u8) }"#, 363 enum A { <|>One(u8, u32) }"#,
320 ); 364 );
321 } 365 }
322 366
@@ -324,4 +368,14 @@ fn another_fn() {
324 fn test_extract_not_applicable_one_field() { 368 fn test_extract_not_applicable_one_field() {
325 check_not_applicable(r"enum A { <|>One(u32) }"); 369 check_not_applicable(r"enum A { <|>One(u32) }");
326 } 370 }
371
372 #[test]
373 fn test_extract_not_applicable_no_field_tuple() {
374 check_not_applicable(r"enum A { <|>None() }");
375 }
376
377 #[test]
378 fn test_extract_not_applicable_no_field_named() {
379 check_not_applicable(r"enum A { <|>None {} }");
380 }
327} 381}
diff --git a/crates/assists/src/handlers/flip_comma.rs b/crates/assists/src/handlers/flip_comma.rs
index 5c69db53e..64b4b1a76 100644
--- a/crates/assists/src/handlers/flip_comma.rs
+++ b/crates/assists/src/handlers/flip_comma.rs
@@ -18,7 +18,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
18// } 18// }
19// ``` 19// ```
20pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 20pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let comma = ctx.find_token_at_offset(T![,])?; 21 let comma = ctx.find_token_syntax_at_offset(T![,])?;
22 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; 22 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
23 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; 23 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
24 24
diff --git a/crates/assists/src/handlers/flip_trait_bound.rs b/crates/assists/src/handlers/flip_trait_bound.rs
index 347e79b1d..92ee42181 100644
--- a/crates/assists/src/handlers/flip_trait_bound.rs
+++ b/crates/assists/src/handlers/flip_trait_bound.rs
@@ -20,7 +20,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
20pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 20pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 // We want to replicate the behavior of `flip_binexpr` by only suggesting 21 // We want to replicate the behavior of `flip_binexpr` by only suggesting
22 // the assist when the cursor is on a `+` 22 // the assist when the cursor is on a `+`
23 let plus = ctx.find_token_at_offset(T![+])?; 23 let plus = ctx.find_token_syntax_at_offset(T![+])?;
24 24
25 // Make sure we're in a `TypeBoundList` 25 // Make sure we're in a `TypeBoundList`
26 if ast::TypeBoundList::cast(plus.parent()).is_none() { 26 if ast::TypeBoundList::cast(plus.parent()).is_none() {
diff --git a/crates/assists/src/handlers/infer_function_return_type.rs b/crates/assists/src/handlers/infer_function_return_type.rs
new file mode 100644
index 000000000..520d07ae0
--- /dev/null
+++ b/crates/assists/src/handlers/infer_function_return_type.rs
@@ -0,0 +1,337 @@
1use hir::HirDisplay;
2use syntax::{ast, AstNode, TextRange, TextSize};
3use test_utils::mark;
4
5use crate::{AssistContext, AssistId, AssistKind, Assists};
6
7// Assist: infer_function_return_type
8//
9// Adds the return type to a function or closure inferred from its tail expression if it doesn't have a return
10// type specified. This assists is useable in a functions or closures tail expression or return type position.
11//
12// ```
13// fn foo() { 4<|>2i32 }
14// ```
15// ->
16// ```
17// fn foo() -> i32 { 42i32 }
18// ```
19pub(crate) fn infer_function_return_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
20 let (tail_expr, builder_edit_pos, wrap_expr) = extract_tail(ctx)?;
21 let module = ctx.sema.scope(tail_expr.syntax()).module()?;
22 let ty = ctx.sema.type_of_expr(&tail_expr)?;
23 if ty.is_unit() {
24 return None;
25 }
26 let ty = ty.display_source_code(ctx.db(), module.into()).ok()?;
27
28 acc.add(
29 AssistId("infer_function_return_type", AssistKind::RefactorRewrite),
30 "Add this function's return type",
31 tail_expr.syntax().text_range(),
32 |builder| {
33 match builder_edit_pos {
34 InsertOrReplace::Insert(insert_pos) => {
35 builder.insert(insert_pos, &format!("-> {} ", ty))
36 }
37 InsertOrReplace::Replace(text_range) => {
38 builder.replace(text_range, &format!("-> {}", ty))
39 }
40 }
41 if wrap_expr {
42 mark::hit!(wrap_closure_non_block_expr);
43 // `|x| x` becomes `|x| -> T x` which is invalid, so wrap it in a block
44 builder.replace(tail_expr.syntax().text_range(), &format!("{{{}}}", tail_expr));
45 }
46 },
47 )
48}
49
50enum InsertOrReplace {
51 Insert(TextSize),
52 Replace(TextRange),
53}
54
55/// Check the potentially already specified return type and reject it or turn it into a builder command
56/// if allowed.
57fn ret_ty_to_action(ret_ty: Option<ast::RetType>, insert_pos: TextSize) -> Option<InsertOrReplace> {
58 match ret_ty {
59 Some(ret_ty) => match ret_ty.ty() {
60 Some(ast::Type::InferType(_)) | None => {
61 mark::hit!(existing_infer_ret_type);
62 mark::hit!(existing_infer_ret_type_closure);
63 Some(InsertOrReplace::Replace(ret_ty.syntax().text_range()))
64 }
65 _ => {
66 mark::hit!(existing_ret_type);
67 mark::hit!(existing_ret_type_closure);
68 None
69 }
70 },
71 None => Some(InsertOrReplace::Insert(insert_pos + TextSize::from(1))),
72 }
73}
74
75fn extract_tail(ctx: &AssistContext) -> Option<(ast::Expr, InsertOrReplace, bool)> {
76 let (tail_expr, return_type_range, action, wrap_expr) =
77 if let Some(closure) = ctx.find_node_at_offset::<ast::ClosureExpr>() {
78 let rpipe_pos = closure.param_list()?.syntax().last_token()?.text_range().end();
79 let action = ret_ty_to_action(closure.ret_type(), rpipe_pos)?;
80
81 let body = closure.body()?;
82 let body_start = body.syntax().first_token()?.text_range().start();
83 let (tail_expr, wrap_expr) = match body {
84 ast::Expr::BlockExpr(block) => (block.expr()?, false),
85 body => (body, true),
86 };
87
88 let ret_range = TextRange::new(rpipe_pos, body_start);
89 (tail_expr, ret_range, action, wrap_expr)
90 } else {
91 let func = ctx.find_node_at_offset::<ast::Fn>()?;
92 let rparen_pos = func.param_list()?.r_paren_token()?.text_range().end();
93 let action = ret_ty_to_action(func.ret_type(), rparen_pos)?;
94
95 let body = func.body()?;
96 let tail_expr = body.expr()?;
97
98 let ret_range_end = body.l_curly_token()?.text_range().start();
99 let ret_range = TextRange::new(rparen_pos, ret_range_end);
100 (tail_expr, ret_range, action, false)
101 };
102 let frange = ctx.frange.range;
103 if return_type_range.contains_range(frange) {
104 mark::hit!(cursor_in_ret_position);
105 mark::hit!(cursor_in_ret_position_closure);
106 } else if tail_expr.syntax().text_range().contains_range(frange) {
107 mark::hit!(cursor_on_tail);
108 mark::hit!(cursor_on_tail_closure);
109 } else {
110 return None;
111 }
112 Some((tail_expr, action, wrap_expr))
113}
114
115#[cfg(test)]
116mod tests {
117 use crate::tests::{check_assist, check_assist_not_applicable};
118
119 use super::*;
120
121 #[test]
122 fn infer_return_type_specified_inferred() {
123 mark::check!(existing_infer_ret_type);
124 check_assist(
125 infer_function_return_type,
126 r#"fn foo() -> <|>_ {
127 45
128}"#,
129 r#"fn foo() -> i32 {
130 45
131}"#,
132 );
133 }
134
135 #[test]
136 fn infer_return_type_specified_inferred_closure() {
137 mark::check!(existing_infer_ret_type_closure);
138 check_assist(
139 infer_function_return_type,
140 r#"fn foo() {
141 || -> _ {<|>45};
142}"#,
143 r#"fn foo() {
144 || -> i32 {45};
145}"#,
146 );
147 }
148
149 #[test]
150 fn infer_return_type_cursor_at_return_type_pos() {
151 mark::check!(cursor_in_ret_position);
152 check_assist(
153 infer_function_return_type,
154 r#"fn foo() <|>{
155 45
156}"#,
157 r#"fn foo() -> i32 {
158 45
159}"#,
160 );
161 }
162
163 #[test]
164 fn infer_return_type_cursor_at_return_type_pos_closure() {
165 mark::check!(cursor_in_ret_position_closure);
166 check_assist(
167 infer_function_return_type,
168 r#"fn foo() {
169 || <|>45
170}"#,
171 r#"fn foo() {
172 || -> i32 {45}
173}"#,
174 );
175 }
176
177 #[test]
178 fn infer_return_type() {
179 mark::check!(cursor_on_tail);
180 check_assist(
181 infer_function_return_type,
182 r#"fn foo() {
183 45<|>
184}"#,
185 r#"fn foo() -> i32 {
186 45
187}"#,
188 );
189 }
190
191 #[test]
192 fn infer_return_type_nested() {
193 check_assist(
194 infer_function_return_type,
195 r#"fn foo() {
196 if true {
197 3<|>
198 } else {
199 5
200 }
201}"#,
202 r#"fn foo() -> i32 {
203 if true {
204 3
205 } else {
206 5
207 }
208}"#,
209 );
210 }
211
212 #[test]
213 fn not_applicable_ret_type_specified() {
214 mark::check!(existing_ret_type);
215 check_assist_not_applicable(
216 infer_function_return_type,
217 r#"fn foo() -> i32 {
218 ( 45<|> + 32 ) * 123
219}"#,
220 );
221 }
222
223 #[test]
224 fn not_applicable_non_tail_expr() {
225 check_assist_not_applicable(
226 infer_function_return_type,
227 r#"fn foo() {
228 let x = <|>3;
229 ( 45 + 32 ) * 123
230}"#,
231 );
232 }
233
234 #[test]
235 fn not_applicable_unit_return_type() {
236 check_assist_not_applicable(
237 infer_function_return_type,
238 r#"fn foo() {
239 (<|>)
240}"#,
241 );
242 }
243
244 #[test]
245 fn infer_return_type_closure_block() {
246 mark::check!(cursor_on_tail_closure);
247 check_assist(
248 infer_function_return_type,
249 r#"fn foo() {
250 |x: i32| {
251 x<|>
252 };
253}"#,
254 r#"fn foo() {
255 |x: i32| -> i32 {
256 x
257 };
258}"#,
259 );
260 }
261
262 #[test]
263 fn infer_return_type_closure() {
264 check_assist(
265 infer_function_return_type,
266 r#"fn foo() {
267 |x: i32| { x<|> };
268}"#,
269 r#"fn foo() {
270 |x: i32| -> i32 { x };
271}"#,
272 );
273 }
274
275 #[test]
276 fn infer_return_type_closure_wrap() {
277 mark::check!(wrap_closure_non_block_expr);
278 check_assist(
279 infer_function_return_type,
280 r#"fn foo() {
281 |x: i32| x<|>;
282}"#,
283 r#"fn foo() {
284 |x: i32| -> i32 {x};
285}"#,
286 );
287 }
288
289 #[test]
290 fn infer_return_type_nested_closure() {
291 check_assist(
292 infer_function_return_type,
293 r#"fn foo() {
294 || {
295 if true {
296 3<|>
297 } else {
298 5
299 }
300 }
301}"#,
302 r#"fn foo() {
303 || -> i32 {
304 if true {
305 3
306 } else {
307 5
308 }
309 }
310}"#,
311 );
312 }
313
314 #[test]
315 fn not_applicable_ret_type_specified_closure() {
316 mark::check!(existing_ret_type_closure);
317 check_assist_not_applicable(
318 infer_function_return_type,
319 r#"fn foo() {
320 || -> i32 { 3<|> }
321}"#,
322 );
323 }
324
325 #[test]
326 fn not_applicable_non_tail_expr_closure() {
327 check_assist_not_applicable(
328 infer_function_return_type,
329 r#"fn foo() {
330 || -> i32 {
331 let x = 3<|>;
332 6
333 }
334}"#,
335 );
336 }
337}
diff --git a/crates/assists/src/handlers/introduce_named_lifetime.rs b/crates/assists/src/handlers/introduce_named_lifetime.rs
index 5f623e5f7..4cc8dae65 100644
--- a/crates/assists/src/handlers/introduce_named_lifetime.rs
+++ b/crates/assists/src/handlers/introduce_named_lifetime.rs
@@ -36,7 +36,7 @@ static ASSIST_LABEL: &str = "Introduce named lifetime";
36// FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo 36// FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo
37pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 37pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 let lifetime_token = ctx 38 let lifetime_token = ctx
39 .find_token_at_offset(SyntaxKind::LIFETIME) 39 .find_token_syntax_at_offset(SyntaxKind::LIFETIME)
40 .filter(|lifetime| lifetime.text() == "'_")?; 40 .filter(|lifetime| lifetime.text() == "'_")?;
41 if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::Fn::cast) { 41 if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::Fn::cast) {
42 generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range()) 42 generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range())
diff --git a/crates/assists/src/handlers/invert_if.rs b/crates/assists/src/handlers/invert_if.rs
index 461fcf862..ea722b91b 100644
--- a/crates/assists/src/handlers/invert_if.rs
+++ b/crates/assists/src/handlers/invert_if.rs
@@ -29,7 +29,7 @@ use crate::{
29// ``` 29// ```
30 30
31pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 31pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let if_keyword = ctx.find_token_at_offset(T![if])?; 32 let if_keyword = ctx.find_token_syntax_at_offset(T![if])?;
33 let expr = ast::IfExpr::cast(if_keyword.parent())?; 33 let expr = ast::IfExpr::cast(if_keyword.parent())?;
34 let if_range = if_keyword.text_range(); 34 let if_range = if_keyword.text_range();
35 let cursor_in_range = if_range.contains_range(ctx.frange.range); 35 let cursor_in_range = if_range.contains_range(ctx.frange.range);
diff --git a/crates/assists/src/handlers/raw_string.rs b/crates/assists/src/handlers/raw_string.rs
index 9ddd116e0..4c759cc25 100644
--- a/crates/assists/src/handlers/raw_string.rs
+++ b/crates/assists/src/handlers/raw_string.rs
@@ -1,11 +1,6 @@
1use std::borrow::Cow; 1use std::borrow::Cow;
2 2
3use syntax::{ 3use syntax::{ast, AstToken, TextRange, TextSize};
4 ast::{self, HasQuotes, HasStringValue},
5 AstToken,
6 SyntaxKind::{RAW_STRING, STRING},
7 TextRange, TextSize,
8};
9use test_utils::mark; 4use test_utils::mark;
10 5
11use crate::{AssistContext, AssistId, AssistKind, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -26,7 +21,10 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
26// } 21// }
27// ``` 22// ```
28pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 23pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; 24 let token = ctx.find_token_at_offset::<ast::String>()?;
25 if token.is_raw() {
26 return None;
27 }
30 let value = token.value()?; 28 let value = token.value()?;
31 let target = token.syntax().text_range(); 29 let target = token.syntax().text_range();
32 acc.add( 30 acc.add(
@@ -65,7 +63,10 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<
65// } 63// }
66// ``` 64// ```
67pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 65pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
68 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; 66 let token = ctx.find_token_at_offset::<ast::String>()?;
67 if !token.is_raw() {
68 return None;
69 }
69 let value = token.value()?; 70 let value = token.value()?;
70 let target = token.syntax().text_range(); 71 let target = token.syntax().text_range();
71 acc.add( 72 acc.add(
@@ -104,11 +105,15 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio
104// } 105// }
105// ``` 106// ```
106pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 107pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
107 let token = ctx.find_token_at_offset(RAW_STRING)?; 108 let token = ctx.find_token_at_offset::<ast::String>()?;
108 let target = token.text_range(); 109 if !token.is_raw() {
110 return None;
111 }
112 let text_range = token.syntax().text_range();
113 let target = text_range;
109 acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| { 114 acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| {
110 edit.insert(token.text_range().start() + TextSize::of('r'), "#"); 115 edit.insert(text_range.start() + TextSize::of('r'), "#");
111 edit.insert(token.text_range().end(), "#"); 116 edit.insert(text_range.end(), "#");
112 }) 117 })
113} 118}
114 119
@@ -128,7 +133,10 @@ pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
128// } 133// }
129// ``` 134// ```
130pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 135pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
131 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; 136 let token = ctx.find_token_at_offset::<ast::String>()?;
137 if !token.is_raw() {
138 return None;
139 }
132 140
133 let text = token.text().as_str(); 141 let text = token.text().as_str();
134 if !text.starts_with("r#") && text.ends_with('#') { 142 if !text.starts_with("r#") && text.ends_with('#') {
diff --git a/crates/assists/src/handlers/remove_mut.rs b/crates/assists/src/handlers/remove_mut.rs
index 44f41daa9..575b271f7 100644
--- a/crates/assists/src/handlers/remove_mut.rs
+++ b/crates/assists/src/handlers/remove_mut.rs
@@ -18,7 +18,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
18// } 18// }
19// ``` 19// ```
20pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 20pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let mut_token = ctx.find_token_at_offset(T![mut])?; 21 let mut_token = ctx.find_token_syntax_at_offset(T![mut])?;
22 let delete_from = mut_token.text_range().start(); 22 let delete_from = mut_token.text_range().start();
23 let delete_to = match mut_token.next_token() { 23 let delete_to = match mut_token.next_token() {
24 Some(it) if it.kind() == SyntaxKind::WHITESPACE => it.text_range().end(), 24 Some(it) if it.kind() == SyntaxKind::WHITESPACE => it.text_range().end(),
diff --git a/crates/assists/src/handlers/replace_let_with_if_let.rs b/crates/assists/src/handlers/replace_let_with_if_let.rs
index a5bcbda24..69d3b08d3 100644
--- a/crates/assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/assists/src/handlers/replace_let_with_if_let.rs
@@ -37,7 +37,7 @@ use ide_db::ty_filter::TryEnum;
37// fn compute() -> Option<i32> { None } 37// fn compute() -> Option<i32> { None }
38// ``` 38// ```
39pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 39pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let let_kw = ctx.find_token_at_offset(T![let])?; 40 let let_kw = ctx.find_token_syntax_at_offset(T![let])?;
41 let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?; 41 let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?;
42 let init = let_stmt.initializer()?; 42 let init = let_stmt.initializer()?;
43 let original_pat = let_stmt.pat()?; 43 let original_pat = let_stmt.pat()?;
diff --git a/crates/assists/src/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
index c50bc7604..d7e1d9580 100644
--- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
@@ -45,10 +45,9 @@ pub(crate) fn replace_qualified_name_with_use(
45 // affected (that is, all paths inside the node we added the `use` to). 45 // affected (that is, all paths inside the node we added the `use` to).
46 let mut rewriter = SyntaxRewriter::default(); 46 let mut rewriter = SyntaxRewriter::default();
47 shorten_paths(&mut rewriter, syntax.clone(), &path); 47 shorten_paths(&mut rewriter, syntax.clone(), &path);
48 let rewritten_syntax = rewriter.rewrite(&syntax); 48 if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
49 if let Some(ref import_scope) = ImportScope::from(rewritten_syntax) { 49 rewriter += insert_use(import_scope, path, ctx.config.insert_use.merge);
50 let new_syntax = insert_use(import_scope, path, ctx.config.insert_use.merge); 50 builder.rewrite(rewriter);
51 builder.replace(syntax.text_range(), new_syntax.to_string())
52 } 51 }
53 }, 52 },
54 ) 53 )
diff --git a/crates/assists/src/handlers/replace_string_with_char.rs b/crates/assists/src/handlers/replace_string_with_char.rs
index 4ca87a8ec..b4b898846 100644
--- a/crates/assists/src/handlers/replace_string_with_char.rs
+++ b/crates/assists/src/handlers/replace_string_with_char.rs
@@ -1,8 +1,4 @@
1use syntax::{ 1use syntax::{ast, AstToken, SyntaxKind::STRING};
2 ast::{self, HasStringValue},
3 AstToken,
4 SyntaxKind::STRING,
5};
6 2
7use crate::{AssistContext, AssistId, AssistKind, Assists}; 3use crate::{AssistContext, AssistId, AssistKind, Assists};
8 4
@@ -22,7 +18,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
22// } 18// }
23// ``` 19// ```
24pub(crate) fn replace_string_with_char(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 20pub(crate) fn replace_string_with_char(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; 21 let token = ctx.find_token_syntax_at_offset(STRING).and_then(ast::String::cast)?;
26 let value = token.value()?; 22 let value = token.value()?;
27 let target = token.syntax().text_range(); 23 let target = token.syntax().text_range();
28 24
diff --git a/crates/assists/src/handlers/split_import.rs b/crates/assists/src/handlers/split_import.rs
index 15e67eaa1..ef1f6b8a1 100644
--- a/crates/assists/src/handlers/split_import.rs
+++ b/crates/assists/src/handlers/split_import.rs
@@ -16,7 +16,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
16// use std::{collections::HashMap}; 16// use std::{collections::HashMap};
17// ``` 17// ```
18pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 18pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
19 let colon_colon = ctx.find_token_at_offset(T![::])?; 19 let colon_colon = ctx.find_token_syntax_at_offset(T![::])?;
20 let path = ast::Path::cast(colon_colon.parent())?.qualifier()?; 20 let path = ast::Path::cast(colon_colon.parent())?.qualifier()?;
21 let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?; 21 let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?;
22 22
diff --git a/crates/assists/src/handlers/unwrap_block.rs b/crates/assists/src/handlers/unwrap_block.rs
index 3851aeb3e..36ef871b9 100644
--- a/crates/assists/src/handlers/unwrap_block.rs
+++ b/crates/assists/src/handlers/unwrap_block.rs
@@ -29,7 +29,7 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
29 let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite); 29 let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite);
30 let assist_label = "Unwrap block"; 30 let assist_label = "Unwrap block";
31 31
32 let l_curly_token = ctx.find_token_at_offset(T!['{'])?; 32 let l_curly_token = ctx.find_token_syntax_at_offset(T!['{'])?;
33 let mut block = ast::BlockExpr::cast(l_curly_token.parent())?; 33 let mut block = ast::BlockExpr::cast(l_curly_token.parent())?;
34 let mut parent = block.syntax().parent()?; 34 let mut parent = block.syntax().parent()?;
35 if ast::MatchArm::can_cast(parent.kind()) { 35 if ast::MatchArm::can_cast(parent.kind()) {
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index b804e495d..af88b3437 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -143,6 +143,7 @@ mod handlers {
143 mod generate_function; 143 mod generate_function;
144 mod generate_impl; 144 mod generate_impl;
145 mod generate_new; 145 mod generate_new;
146 mod infer_function_return_type;
146 mod inline_local_variable; 147 mod inline_local_variable;
147 mod introduce_named_lifetime; 148 mod introduce_named_lifetime;
148 mod invert_if; 149 mod invert_if;
@@ -190,6 +191,7 @@ mod handlers {
190 generate_function::generate_function, 191 generate_function::generate_function,
191 generate_impl::generate_impl, 192 generate_impl::generate_impl,
192 generate_new::generate_new, 193 generate_new::generate_new,
194 infer_function_return_type::infer_function_return_type,
193 inline_local_variable::inline_local_variable, 195 inline_local_variable::inline_local_variable,
194 introduce_named_lifetime::introduce_named_lifetime, 196 introduce_named_lifetime::introduce_named_lifetime,
195 invert_if::invert_if, 197 invert_if::invert_if,
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index acbf5b652..168e1626a 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -506,6 +506,19 @@ impl<T: Clone> Ctx<T> {
506} 506}
507 507
508#[test] 508#[test]
509fn doctest_infer_function_return_type() {
510 check_doc_test(
511 "infer_function_return_type",
512 r#####"
513fn foo() { 4<|>2i32 }
514"#####,
515 r#####"
516fn foo() -> i32 { 42i32 }
517"#####,
518 )
519}
520
521#[test]
509fn doctest_inline_local_variable() { 522fn doctest_inline_local_variable() {
510 check_doc_test( 523 check_doc_test(
511 "inline_local_variable", 524 "inline_local_variable",
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index a76bd5ebf..84a0dffdd 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -1,12 +1,9 @@
1//! Handle syntactic aspects of inserting a new `use`. 1//! Handle syntactic aspects of inserting a new `use`.
2use std::{ 2use std::{cmp::Ordering, iter::successors};
3 cmp::Ordering,
4 iter::{self, successors},
5};
6 3
7use itertools::{EitherOrBoth, Itertools}; 4use itertools::{EitherOrBoth, Itertools};
8use syntax::{ 5use syntax::{
9 algo, 6 algo::SyntaxRewriter,
10 ast::{ 7 ast::{
11 self, 8 self,
12 edit::{AstNodeEdit, IndentLevel}, 9 edit::{AstNodeEdit, IndentLevel},
@@ -88,20 +85,19 @@ impl ImportScope {
88} 85}
89 86
90/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. 87/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
91pub(crate) fn insert_use( 88pub(crate) fn insert_use<'a>(
92 scope: &ImportScope, 89 scope: &ImportScope,
93 path: ast::Path, 90 path: ast::Path,
94 merge: Option<MergeBehaviour>, 91 merge: Option<MergeBehaviour>,
95) -> SyntaxNode { 92) -> SyntaxRewriter<'a> {
93 let mut rewriter = SyntaxRewriter::default();
96 let use_item = make::use_(make::use_tree(path.clone(), None, None, false)); 94 let use_item = make::use_(make::use_tree(path.clone(), None, None, false));
97 // merge into existing imports if possible 95 // merge into existing imports if possible
98 if let Some(mb) = merge { 96 if let Some(mb) = merge {
99 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { 97 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
100 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { 98 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
101 let to_delete: SyntaxElement = existing_use.syntax().clone().into(); 99 rewriter.replace(existing_use.syntax(), merged.syntax());
102 let to_delete = to_delete.clone()..=to_delete; 100 return rewriter;
103 let to_insert = iter::once(merged.syntax().clone().into());
104 return algo::replace_children(scope.as_syntax_node(), to_delete, to_insert);
105 } 101 }
106 } 102 }
107 } 103 }
@@ -157,7 +153,15 @@ pub(crate) fn insert_use(
157 buf 153 buf
158 }; 154 };
159 155
160 algo::insert_children(scope.as_syntax_node(), insert_position, to_insert) 156 match insert_position {
157 InsertPosition::First => {
158 rewriter.insert_many_as_first_children(scope.as_syntax_node(), to_insert)
159 }
160 InsertPosition::Last => return rewriter, // actually unreachable
161 InsertPosition::Before(anchor) => rewriter.insert_many_before(&anchor, to_insert),
162 InsertPosition::After(anchor) => rewriter.insert_many_after(&anchor, to_insert),
163 }
164 rewriter
161} 165}
162 166
163fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool { 167fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
@@ -1101,7 +1105,8 @@ use foo::bar::baz::Qux;",
1101 .find_map(ast::Path::cast) 1105 .find_map(ast::Path::cast)
1102 .unwrap(); 1106 .unwrap();
1103 1107
1104 let result = insert_use(&file, path, mb).to_string(); 1108 let rewriter = insert_use(&file, path, mb);
1109 let result = rewriter.rewrite(file.as_syntax_node()).to_string();
1105 assert_eq_text!(&result, ra_fixture_after); 1110 assert_eq_text!(&result, ra_fixture_after);
1106 } 1111 }
1107 1112