aboutsummaryrefslogtreecommitdiff
path: root/crates/assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists')
-rw-r--r--crates/assists/src/assist_context.rs13
-rw-r--r--crates/assists/src/handlers/add_custom_impl.rs271
-rw-r--r--crates/assists/src/handlers/add_missing_impl_members.rs92
-rw-r--r--crates/assists/src/handlers/add_turbo_fish.rs2
-rw-r--r--crates/assists/src/handlers/change_return_type_to_result.rs121
-rw-r--r--crates/assists/src/handlers/convert_integer_literal.rs395
-rw-r--r--crates/assists/src/handlers/expand_glob_import.rs78
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs148
-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/reorder_fields.rs4
-rw-r--r--crates/assists/src/handlers/replace_let_with_if_let.rs2
-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.rs92
23 files changed, 999 insertions, 627 deletions
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs
index a1c0550e4..a17e592b0 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 }
@@ -279,12 +282,6 @@ impl AssistBuilder {
279 algo::diff(&node, &new).into_text_edit(&mut self.edit); 282 algo::diff(&node, &new).into_text_edit(&mut self.edit);
280 } 283 }
281 284
282 // FIXME: kill this API
283 /// Get access to the raw `TextEditBuilder`.
284 pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
285 &mut self.edit
286 }
287
288 fn finish(mut self) -> SourceChange { 285 fn finish(mut self) -> SourceChange {
289 self.commit(); 286 self.commit();
290 SourceChange { 287 SourceChange {
diff --git a/crates/assists/src/handlers/add_custom_impl.rs b/crates/assists/src/handlers/add_custom_impl.rs
index 8757fa33f..c13493fd8 100644
--- a/crates/assists/src/handlers/add_custom_impl.rs
+++ b/crates/assists/src/handlers/add_custom_impl.rs
@@ -1,13 +1,18 @@
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 TextSize,
7}; 8};
8 9
9use crate::{ 10use crate::{
10 assist_context::{AssistContext, Assists}, 11 assist_context::{AssistBuilder, AssistContext, Assists},
12 utils::{
13 add_trait_assoc_items_to_impl, filter_assoc_items, mod_path_to_ast, render_snippet, Cursor,
14 DefaultMethods,
15 },
11 AssistId, AssistKind, 16 AssistId, AssistKind,
12}; 17};
13 18
@@ -30,72 +35,154 @@ use crate::{
30// ``` 35// ```
31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 36pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let attr = ctx.find_node_at_offset::<ast::Attr>()?; 37 let attr = ctx.find_node_at_offset::<ast::Attr>()?;
33 let input = attr.token_tree()?;
34 38
35 let attr_name = attr 39 let attr_name = attr
36 .syntax() 40 .syntax()
37 .descendants_with_tokens() 41 .descendants_with_tokens()
38 .filter(|t| t.kind() == IDENT) 42 .filter(|t| t.kind() == IDENT)
39 .find_map(|i| i.into_token()) 43 .find_map(syntax::NodeOrToken::into_token)
40 .filter(|t| *t.text() == "derive")? 44 .filter(|t| t.text() == "derive")?
41 .text() 45 .text()
42 .clone(); 46 .clone();
43 47
44 let trait_token = 48 let trait_token =
45 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?; 49 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
50 let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text())));
51
52 let annotated_name = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
53 let insert_pos = annotated_name.syntax().parent()?.text_range().end();
54
55 let current_module = ctx.sema.scope(annotated_name.syntax()).module()?;
56 let current_crate = current_module.krate();
46 57
47 let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?; 58 let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text())
48 let annotated_name = annotated.syntax().text().to_string(); 59 .into_iter()
49 let start_offset = annotated.syntax().parent()?.text_range().end(); 60 .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
61 either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
62 _ => None,
63 })
64 .flat_map(|trait_| {
65 current_module
66 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
67 .as_ref()
68 .map(mod_path_to_ast)
69 .zip(Some(trait_))
70 });
50 71
51 let label = 72 let mut no_traits_found = true;
52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name); 73 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) {
74 add_assist(acc, ctx, &attr, &trait_path, Some(trait_), &annotated_name, insert_pos)?;
75 }
76 if no_traits_found {
77 add_assist(acc, ctx, &attr, &trait_path, None, &annotated_name, insert_pos)?;
78 }
79 Some(())
80}
53 81
82fn add_assist(
83 acc: &mut Assists,
84 ctx: &AssistContext,
85 attr: &ast::Attr,
86 trait_path: &ast::Path,
87 trait_: Option<hir::Trait>,
88 annotated_name: &ast::Name,
89 insert_pos: TextSize,
90) -> Option<()> {
54 let target = attr.syntax().text_range(); 91 let target = attr.syntax().text_range();
92 let input = attr.token_tree()?;
93 let label = format!("Add custom impl `{}` for `{}`", trait_path, annotated_name);
94 let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?;
95
55 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| { 96 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
56 let new_attr_input = input 97 let impl_def_with_items =
57 .syntax() 98 impl_def_from_trait(&ctx.sema, annotated_name, trait_, trait_path);
58 .descendants_with_tokens() 99 update_attribute(builder, &input, &trait_name, &attr);
59 .filter(|t| t.kind() == IDENT) 100 match (ctx.config.snippet_cap, impl_def_with_items) {
60 .filter_map(|t| t.into_token().map(|t| t.text().clone())) 101 (None, _) => builder.insert(
61 .filter(|t| t != trait_token.text()) 102 insert_pos,
62 .collect::<Vec<SmolStr>>(); 103 format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name),
63 let has_more_derives = !new_attr_input.is_empty(); 104 ),
64 105 (Some(cap), None) => builder.insert_snippet(
65 if has_more_derives { 106 cap,
66 let new_attr_input = format!("({})", new_attr_input.iter().format(", ")); 107 insert_pos,
67 builder.replace(input.syntax().text_range(), new_attr_input); 108 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name),
68 } else { 109 ),
69 let attr_range = attr.syntax().text_range(); 110 (Some(cap), Some((impl_def, first_assoc_item))) => {
70 builder.delete(attr_range); 111 let mut cursor = Cursor::Before(first_assoc_item.syntax());
71 112 let placeholder;
72 let line_break_range = attr 113 if let ast::AssocItem::Fn(ref func) = first_assoc_item {
73 .syntax() 114 if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
74 .next_sibling_or_token() 115 if m.syntax().text() == "todo!()" {
75 .filter(|t| t.kind() == WHITESPACE) 116 placeholder = m;
76 .map(|t| t.text_range()) 117 cursor = Cursor::Replace(placeholder.syntax());
77 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0))); 118 }
78 builder.delete(line_break_range); 119 }
79 } 120 }
80 121
81 match ctx.config.snippet_cap {
82 Some(cap) => {
83 builder.insert_snippet( 122 builder.insert_snippet(
84 cap, 123 cap,
85 start_offset, 124 insert_pos,
86 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name), 125 format!("\n\n{}", render_snippet(cap, impl_def.syntax(), cursor)),
87 ); 126 )
88 }
89 None => {
90 builder.insert(
91 start_offset,
92 format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name),
93 );
94 } 127 }
95 } 128 };
96 }) 129 })
97} 130}
98 131
132fn impl_def_from_trait(
133 sema: &hir::Semantics<ide_db::RootDatabase>,
134 annotated_name: &ast::Name,
135 trait_: Option<hir::Trait>,
136 trait_path: &ast::Path,
137) -> Option<(ast::Impl, ast::AssocItem)> {
138 let trait_ = trait_?;
139 let target_scope = sema.scope(annotated_name.syntax());
140 let trait_items = filter_assoc_items(sema.db, &trait_.items(sema.db), DefaultMethods::No);
141 if trait_items.is_empty() {
142 return None;
143 }
144 let impl_def = make::impl_trait(
145 trait_path.clone(),
146 make::path_unqualified(make::path_segment(make::name_ref(annotated_name.text()))),
147 );
148 let (impl_def, first_assoc_item) =
149 add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope);
150 Some((impl_def, first_assoc_item))
151}
152
153fn update_attribute(
154 builder: &mut AssistBuilder,
155 input: &ast::TokenTree,
156 trait_name: &ast::NameRef,
157 attr: &ast::Attr,
158) {
159 let new_attr_input = input
160 .syntax()
161 .descendants_with_tokens()
162 .filter(|t| t.kind() == IDENT)
163 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
164 .filter(|t| t != trait_name.text())
165 .collect::<Vec<SmolStr>>();
166 let has_more_derives = !new_attr_input.is_empty();
167
168 if has_more_derives {
169 let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
170 builder.replace(input.syntax().text_range(), new_attr_input);
171 } else {
172 let attr_range = attr.syntax().text_range();
173 builder.delete(attr_range);
174
175 if let Some(line_break_range) = attr
176 .syntax()
177 .next_sibling_or_token()
178 .filter(|t| t.kind() == WHITESPACE)
179 .map(|t| t.text_range())
180 {
181 builder.delete(line_break_range);
182 }
183 }
184}
185
99#[cfg(test)] 186#[cfg(test)]
100mod tests { 187mod tests {
101 use crate::tests::{check_assist, check_assist_not_applicable}; 188 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -103,6 +190,96 @@ mod tests {
103 use super::*; 190 use super::*;
104 191
105 #[test] 192 #[test]
193 fn add_custom_impl_debug() {
194 check_assist(
195 add_custom_impl,
196 "
197mod fmt {
198 pub struct Error;
199 pub type Result = Result<(), Error>;
200 pub struct Formatter<'a>;
201 pub trait Debug {
202 fn fmt(&self, f: &mut Formatter<'_>) -> Result;
203 }
204}
205
206#[derive(Debu<|>g)]
207struct Foo {
208 bar: String,
209}
210",
211 "
212mod fmt {
213 pub struct Error;
214 pub type Result = Result<(), Error>;
215 pub struct Formatter<'a>;
216 pub trait Debug {
217 fn fmt(&self, f: &mut Formatter<'_>) -> Result;
218 }
219}
220
221struct Foo {
222 bar: String,
223}
224
225impl fmt::Debug for Foo {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 ${0:todo!()}
228 }
229}
230",
231 )
232 }
233 #[test]
234 fn add_custom_impl_all() {
235 check_assist(
236 add_custom_impl,
237 "
238mod foo {
239 pub trait Bar {
240 type Qux;
241 const Baz: usize = 42;
242 const Fez: usize;
243 fn foo();
244 fn bar() {}
245 }
246}
247
248#[derive(<|>Bar)]
249struct Foo {
250 bar: String,
251}
252",
253 "
254mod foo {
255 pub trait Bar {
256 type Qux;
257 const Baz: usize = 42;
258 const Fez: usize;
259 fn foo();
260 fn bar() {}
261 }
262}
263
264struct Foo {
265 bar: String,
266}
267
268impl foo::Bar for Foo {
269 $0type Qux;
270
271 const Baz: usize = 42;
272
273 const Fez: usize;
274
275 fn foo() {
276 todo!()
277 }
278}
279",
280 )
281 }
282 #[test]
106 fn add_custom_impl_for_unique_input() { 283 fn add_custom_impl_for_unique_input() {
107 check_assist( 284 check_assist(
108 add_custom_impl, 285 add_custom_impl,
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs
index b82fb30ad..bbb71e261 100644
--- a/crates/assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/assists/src/handlers/add_missing_impl_members.rs
@@ -1,27 +1,14 @@
1use hir::HasSource; 1use ide_db::traits::resolve_target_trait;
2use ide_db::traits::{get_missing_assoc_items, resolve_target_trait}; 2use syntax::ast::{self, AstNode};
3use syntax::{
4 ast::{
5 self,
6 edit::{self, AstNodeEdit, IndentLevel},
7 make, AstNode, NameOwner,
8 },
9 SmolStr,
10};
11 3
12use crate::{ 4use crate::{
13 assist_context::{AssistContext, Assists}, 5 assist_context::{AssistContext, Assists},
14 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, 6 utils::add_trait_assoc_items_to_impl,
15 utils::{render_snippet, Cursor}, 7 utils::DefaultMethods,
8 utils::{filter_assoc_items, render_snippet, Cursor},
16 AssistId, AssistKind, 9 AssistId, AssistKind,
17}; 10};
18 11
19#[derive(PartialEq)]
20enum AddMissingImplMembersMode {
21 DefaultMethodsOnly,
22 NoDefaultMethods,
23}
24
25// Assist: add_impl_missing_members 12// Assist: add_impl_missing_members
26// 13//
27// Adds scaffold for required impl members. 14// Adds scaffold for required impl members.
@@ -55,7 +42,7 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -
55 add_missing_impl_members_inner( 42 add_missing_impl_members_inner(
56 acc, 43 acc,
57 ctx, 44 ctx,
58 AddMissingImplMembersMode::NoDefaultMethods, 45 DefaultMethods::No,
59 "add_impl_missing_members", 46 "add_impl_missing_members",
60 "Implement missing members", 47 "Implement missing members",
61 ) 48 )
@@ -97,7 +84,7 @@ pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext
97 add_missing_impl_members_inner( 84 add_missing_impl_members_inner(
98 acc, 85 acc,
99 ctx, 86 ctx,
100 AddMissingImplMembersMode::DefaultMethodsOnly, 87 DefaultMethods::Only,
101 "add_impl_default_members", 88 "add_impl_default_members",
102 "Implement default members", 89 "Implement default members",
103 ) 90 )
@@ -106,7 +93,7 @@ pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext
106fn add_missing_impl_members_inner( 93fn add_missing_impl_members_inner(
107 acc: &mut Assists, 94 acc: &mut Assists,
108 ctx: &AssistContext, 95 ctx: &AssistContext,
109 mode: AddMissingImplMembersMode, 96 mode: DefaultMethods,
110 assist_id: &'static str, 97 assist_id: &'static str,
111 label: &'static str, 98 label: &'static str,
112) -> Option<()> { 99) -> Option<()> {
@@ -114,32 +101,11 @@ fn add_missing_impl_members_inner(
114 let impl_def = ctx.find_node_at_offset::<ast::Impl>()?; 101 let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
115 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; 102 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
116 103
117 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> { 104 let missing_items = filter_assoc_items(
118 match item { 105 ctx.db(),
119 ast::AssocItem::Fn(def) => def.name(), 106 &ide_db::traits::get_missing_assoc_items(&ctx.sema, &impl_def),
120 ast::AssocItem::TypeAlias(def) => def.name(), 107 mode,
121 ast::AssocItem::Const(def) => def.name(), 108 );
122 ast::AssocItem::MacroCall(_) => None,
123 }
124 .map(|it| it.text().clone())
125 };
126
127 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
128 .iter()
129 .map(|i| match i {
130 hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(ctx.db()).value),
131 hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(ctx.db()).value),
132 hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(ctx.db()).value),
133 })
134 .filter(|t| def_name(&t).is_some())
135 .filter(|t| match t {
136 ast::AssocItem::Fn(def) => match mode {
137 AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
138 AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
139 },
140 _ => mode == AddMissingImplMembersMode::NoDefaultMethods,
141 })
142 .collect::<Vec<_>>();
143 109
144 if missing_items.is_empty() { 110 if missing_items.is_empty() {
145 return None; 111 return None;
@@ -147,29 +113,9 @@ fn add_missing_impl_members_inner(
147 113
148 let target = impl_def.syntax().text_range(); 114 let target = impl_def.syntax().text_range();
149 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| { 115 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
150 let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list);
151
152 let n_existing_items = impl_item_list.assoc_items().count();
153 let source_scope = ctx.sema.scope_for_def(trait_);
154 let target_scope = ctx.sema.scope(impl_def.syntax()); 116 let target_scope = ctx.sema.scope(impl_def.syntax());
155 let ast_transform = QualifyPaths::new(&target_scope, &source_scope) 117 let (new_impl_def, first_new_item) =
156 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone())); 118 add_trait_assoc_items_to_impl(&ctx.sema, missing_items, trait_, impl_def, target_scope);
157
158 let items = missing_items
159 .into_iter()
160 .map(|it| ast_transform::apply(&*ast_transform, it))
161 .map(|it| match it {
162 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
163 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
164 _ => it,
165 })
166 .map(|it| edit::remove_attrs_and_docs(&it));
167
168 let new_impl_item_list = impl_item_list.append_items(items);
169 let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list);
170 let first_new_item =
171 new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap();
172
173 match ctx.config.snippet_cap { 119 match ctx.config.snippet_cap {
174 None => builder.replace(target, new_impl_def.to_string()), 120 None => builder.replace(target, new_impl_def.to_string()),
175 Some(cap) => { 121 Some(cap) => {
@@ -193,14 +139,6 @@ fn add_missing_impl_members_inner(
193 }) 139 })
194} 140}
195 141
196fn add_body(fn_def: ast::Fn) -> ast::Fn {
197 if fn_def.body().is_some() {
198 return fn_def;
199 }
200 let body = make::block_expr(None, Some(make::expr_todo())).indent(IndentLevel(1));
201 fn_def.with_body(body)
202}
203
204#[cfg(test)] 142#[cfg(test)]
205mod tests { 143mod tests {
206 use crate::tests::{check_assist, check_assist_not_applicable}; 144 use crate::tests::{check_assist, check_assist_not_applicable};
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/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 c8af80701..667115382 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, ast::Radix, AstNode}; 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,14 +15,16 @@ 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 (radix, value) = literal.int_value()?; 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();
19 25
20 let range = literal.syntax().text_range(); 26 let range = literal.syntax().text_range();
21 let group_id = GroupLabel("Convert integer base".into()); 27 let group_id = GroupLabel("Convert integer base".into());
22 let suffix = match literal.kind() {
23 ast::LiteralKind::IntNumber { suffix } => suffix,
24 _ => return None,
25 };
26 28
27 for &target_radix in Radix::ALL { 29 for &target_radix in Radix::ALL {
28 if target_radix == radix { 30 if target_radix == radix {
@@ -36,16 +38,11 @@ pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) ->
36 Radix::Hexadecimal => format!("0x{:X}", value), 38 Radix::Hexadecimal => format!("0x{:X}", value),
37 }; 39 };
38 40
39 let label = format!( 41 let label = format!("Convert {} to {}{}", literal, converted, suffix.unwrap_or_default());
40 "Convert {} to {}{}",
41 literal,
42 converted,
43 suffix.as_deref().unwrap_or_default()
44 );
45 42
46 // 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.
47 if let Some(suffix) = &suffix { 44 if let Some(suffix) = suffix {
48 converted.push_str(&suffix); 45 converted.push_str(suffix);
49 } 46 }
50 47
51 acc.add_group( 48 acc.add_group(
@@ -132,34 +129,6 @@ mod tests {
132 ); 129 );
133 } 130 }
134 131
135 // Decimal numbers under 3 digits have a special case where they return early because we can't fit a
136 // other base's prefix, so we have a separate test for that.
137 #[test]
138 fn convert_small_decimal_integer() {
139 let before = "const _: i32 = 10<|>;";
140
141 check_assist_by_label(
142 convert_integer_literal,
143 before,
144 "const _: i32 = 0b1010;",
145 "Convert 10 to 0b1010",
146 );
147
148 check_assist_by_label(
149 convert_integer_literal,
150 before,
151 "const _: i32 = 0o12;",
152 "Convert 10 to 0o12",
153 );
154
155 check_assist_by_label(
156 convert_integer_literal,
157 before,
158 "const _: i32 = 0xA;",
159 "Convert 10 to 0xA",
160 );
161 }
162
163 #[test] 132 #[test]
164 fn convert_hexadecimal_integer() { 133 fn convert_hexadecimal_integer() {
165 let before = "const _: i32 = 0xFF<|>;"; 134 let before = "const _: i32 = 0xFF<|>;";
@@ -239,7 +208,7 @@ mod tests {
239 } 208 }
240 209
241 #[test] 210 #[test]
242 fn convert_decimal_integer_with_underscores() { 211 fn convert_integer_with_underscores() {
243 let before = "const _: i32 = 1_00_0<|>;"; 212 let before = "const _: i32 = 1_00_0<|>;";
244 213
245 check_assist_by_label( 214 check_assist_by_label(
@@ -265,111 +234,7 @@ mod tests {
265 } 234 }
266 235
267 #[test] 236 #[test]
268 fn convert_small_decimal_integer_with_underscores() { 237 fn convert_integer_with_suffix() {
269 let before = "const _: i32 = 1_0<|>;";
270
271 check_assist_by_label(
272 convert_integer_literal,
273 before,
274 "const _: i32 = 0b1010;",
275 "Convert 1_0 to 0b1010",
276 );
277
278 check_assist_by_label(
279 convert_integer_literal,
280 before,
281 "const _: i32 = 0o12;",
282 "Convert 1_0 to 0o12",
283 );
284
285 check_assist_by_label(
286 convert_integer_literal,
287 before,
288 "const _: i32 = 0xA;",
289 "Convert 1_0 to 0xA",
290 );
291 }
292
293 #[test]
294 fn convert_hexadecimal_integer_with_underscores() {
295 let before = "const _: i32 = 0x_F_F<|>;";
296
297 check_assist_by_label(
298 convert_integer_literal,
299 before,
300 "const _: i32 = 0b11111111;",
301 "Convert 0x_F_F to 0b11111111",
302 );
303
304 check_assist_by_label(
305 convert_integer_literal,
306 before,
307 "const _: i32 = 0o377;",
308 "Convert 0x_F_F to 0o377",
309 );
310
311 check_assist_by_label(
312 convert_integer_literal,
313 before,
314 "const _: i32 = 255;",
315 "Convert 0x_F_F to 255",
316 );
317 }
318
319 #[test]
320 fn convert_binary_integer_with_underscores() {
321 let before = "const _: i32 = 0b1111_1111<|>;";
322
323 check_assist_by_label(
324 convert_integer_literal,
325 before,
326 "const _: i32 = 0o377;",
327 "Convert 0b1111_1111 to 0o377",
328 );
329
330 check_assist_by_label(
331 convert_integer_literal,
332 before,
333 "const _: i32 = 255;",
334 "Convert 0b1111_1111 to 255",
335 );
336
337 check_assist_by_label(
338 convert_integer_literal,
339 before,
340 "const _: i32 = 0xFF;",
341 "Convert 0b1111_1111 to 0xFF",
342 );
343 }
344
345 #[test]
346 fn convert_octal_integer_with_underscores() {
347 let before = "const _: i32 = 0o3_77<|>;";
348
349 check_assist_by_label(
350 convert_integer_literal,
351 before,
352 "const _: i32 = 0b11111111;",
353 "Convert 0o3_77 to 0b11111111",
354 );
355
356 check_assist_by_label(
357 convert_integer_literal,
358 before,
359 "const _: i32 = 255;",
360 "Convert 0o3_77 to 255",
361 );
362
363 check_assist_by_label(
364 convert_integer_literal,
365 before,
366 "const _: i32 = 0xFF;",
367 "Convert 0o3_77 to 0xFF",
368 );
369 }
370
371 #[test]
372 fn convert_decimal_integer_with_suffix() {
373 let before = "const _: i32 = 1000i32<|>;"; 238 let before = "const _: i32 = 1000i32<|>;";
374 239
375 check_assist_by_label( 240 check_assist_by_label(
@@ -395,240 +260,6 @@ mod tests {
395 } 260 }
396 261
397 #[test] 262 #[test]
398 fn convert_small_decimal_integer_with_suffix() {
399 let before = "const _: i32 = 10i32<|>;";
400
401 check_assist_by_label(
402 convert_integer_literal,
403 before,
404 "const _: i32 = 0b1010i32;",
405 "Convert 10i32 to 0b1010i32",
406 );
407
408 check_assist_by_label(
409 convert_integer_literal,
410 before,
411 "const _: i32 = 0o12i32;",
412 "Convert 10i32 to 0o12i32",
413 );
414
415 check_assist_by_label(
416 convert_integer_literal,
417 before,
418 "const _: i32 = 0xAi32;",
419 "Convert 10i32 to 0xAi32",
420 );
421 }
422
423 #[test]
424 fn convert_hexadecimal_integer_with_suffix() {
425 let before = "const _: i32 = 0xFFi32<|>;";
426
427 check_assist_by_label(
428 convert_integer_literal,
429 before,
430 "const _: i32 = 0b11111111i32;",
431 "Convert 0xFFi32 to 0b11111111i32",
432 );
433
434 check_assist_by_label(
435 convert_integer_literal,
436 before,
437 "const _: i32 = 0o377i32;",
438 "Convert 0xFFi32 to 0o377i32",
439 );
440
441 check_assist_by_label(
442 convert_integer_literal,
443 before,
444 "const _: i32 = 255i32;",
445 "Convert 0xFFi32 to 255i32",
446 );
447 }
448
449 #[test]
450 fn convert_binary_integer_with_suffix() {
451 let before = "const _: i32 = 0b11111111i32<|>;";
452
453 check_assist_by_label(
454 convert_integer_literal,
455 before,
456 "const _: i32 = 0o377i32;",
457 "Convert 0b11111111i32 to 0o377i32",
458 );
459
460 check_assist_by_label(
461 convert_integer_literal,
462 before,
463 "const _: i32 = 255i32;",
464 "Convert 0b11111111i32 to 255i32",
465 );
466
467 check_assist_by_label(
468 convert_integer_literal,
469 before,
470 "const _: i32 = 0xFFi32;",
471 "Convert 0b11111111i32 to 0xFFi32",
472 );
473 }
474
475 #[test]
476 fn convert_octal_integer_with_suffix() {
477 let before = "const _: i32 = 0o377i32<|>;";
478
479 check_assist_by_label(
480 convert_integer_literal,
481 before,
482 "const _: i32 = 0b11111111i32;",
483 "Convert 0o377i32 to 0b11111111i32",
484 );
485
486 check_assist_by_label(
487 convert_integer_literal,
488 before,
489 "const _: i32 = 255i32;",
490 "Convert 0o377i32 to 255i32",
491 );
492
493 check_assist_by_label(
494 convert_integer_literal,
495 before,
496 "const _: i32 = 0xFFi32;",
497 "Convert 0o377i32 to 0xFFi32",
498 );
499 }
500
501 #[test]
502 fn convert_decimal_integer_with_underscores_and_suffix() {
503 let before = "const _: i32 = 1_00_0i32<|>;";
504
505 check_assist_by_label(
506 convert_integer_literal,
507 before,
508 "const _: i32 = 0b1111101000i32;",
509 "Convert 1_00_0i32 to 0b1111101000i32",
510 );
511
512 check_assist_by_label(
513 convert_integer_literal,
514 before,
515 "const _: i32 = 0o1750i32;",
516 "Convert 1_00_0i32 to 0o1750i32",
517 );
518
519 check_assist_by_label(
520 convert_integer_literal,
521 before,
522 "const _: i32 = 0x3E8i32;",
523 "Convert 1_00_0i32 to 0x3E8i32",
524 );
525 }
526
527 #[test]
528 fn convert_small_decimal_integer_with_underscores_and_suffix() {
529 let before = "const _: i32 = 1_0i32<|>;";
530
531 check_assist_by_label(
532 convert_integer_literal,
533 before,
534 "const _: i32 = 0b1010i32;",
535 "Convert 1_0i32 to 0b1010i32",
536 );
537
538 check_assist_by_label(
539 convert_integer_literal,
540 before,
541 "const _: i32 = 0o12i32;",
542 "Convert 1_0i32 to 0o12i32",
543 );
544
545 check_assist_by_label(
546 convert_integer_literal,
547 before,
548 "const _: i32 = 0xAi32;",
549 "Convert 1_0i32 to 0xAi32",
550 );
551 }
552
553 #[test]
554 fn convert_hexadecimal_integer_with_underscores_and_suffix() {
555 let before = "const _: i32 = 0x_F_Fi32<|>;";
556
557 check_assist_by_label(
558 convert_integer_literal,
559 before,
560 "const _: i32 = 0b11111111i32;",
561 "Convert 0x_F_Fi32 to 0b11111111i32",
562 );
563
564 check_assist_by_label(
565 convert_integer_literal,
566 before,
567 "const _: i32 = 0o377i32;",
568 "Convert 0x_F_Fi32 to 0o377i32",
569 );
570
571 check_assist_by_label(
572 convert_integer_literal,
573 before,
574 "const _: i32 = 255i32;",
575 "Convert 0x_F_Fi32 to 255i32",
576 );
577 }
578
579 #[test]
580 fn convert_binary_integer_with_underscores_and_suffix() {
581 let before = "const _: i32 = 0b1111_1111i32<|>;";
582
583 check_assist_by_label(
584 convert_integer_literal,
585 before,
586 "const _: i32 = 0o377i32;",
587 "Convert 0b1111_1111i32 to 0o377i32",
588 );
589
590 check_assist_by_label(
591 convert_integer_literal,
592 before,
593 "const _: i32 = 255i32;",
594 "Convert 0b1111_1111i32 to 255i32",
595 );
596
597 check_assist_by_label(
598 convert_integer_literal,
599 before,
600 "const _: i32 = 0xFFi32;",
601 "Convert 0b1111_1111i32 to 0xFFi32",
602 );
603 }
604
605 #[test]
606 fn convert_octal_integer_with_underscores_and_suffix() {
607 let before = "const _: i32 = 0o3_77i32<|>;";
608
609 check_assist_by_label(
610 convert_integer_literal,
611 before,
612 "const _: i32 = 0b11111111i32;",
613 "Convert 0o3_77i32 to 0b11111111i32",
614 );
615
616 check_assist_by_label(
617 convert_integer_literal,
618 before,
619 "const _: i32 = 255i32;",
620 "Convert 0o3_77i32 to 255i32",
621 );
622
623 check_assist_by_label(
624 convert_integer_literal,
625 before,
626 "const _: i32 = 0xFFi32;",
627 "Convert 0o3_77i32 to 0xFFi32",
628 );
629 }
630
631 #[test]
632 fn convert_overflowing_literal() { 263 fn convert_overflowing_literal() {
633 let before = "const _: i32 = 264 let before = "const _: i32 =
634 111111111111111111111111111111111111111111111111111111111111111111111111<|>;"; 265 111111111111111111111111111111111111111111111111111111111111111111111111<|>;";
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs
index 316a58d88..f51a9a4ad 100644
--- a/crates/assists/src/handlers/expand_glob_import.rs
+++ b/crates/assists/src/handlers/expand_glob_import.rs
@@ -5,13 +5,13 @@ use ide_db::{
5 search::SearchScope, 5 search::SearchScope,
6}; 6};
7use syntax::{ 7use syntax::{
8 algo, 8 algo::SyntaxRewriter,
9 ast::{self, make}, 9 ast::{self, make},
10 AstNode, Direction, SyntaxNode, SyntaxToken, T, 10 AstNode, Direction, SyntaxNode, SyntaxToken, T,
11}; 11};
12 12
13use crate::{ 13use crate::{
14 assist_context::{AssistBuilder, AssistContext, Assists}, 14 assist_context::{AssistContext, Assists},
15 AssistId, AssistKind, 15 AssistId, AssistKind,
16}; 16};
17 17
@@ -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,
@@ -61,7 +61,9 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Opti
61 "Expand glob import", 61 "Expand glob import",
62 target.text_range(), 62 target.text_range(),
63 |builder| { 63 |builder| {
64 replace_ast(builder, parent, mod_path, names_to_import); 64 let mut rewriter = SyntaxRewriter::default();
65 replace_ast(&mut rewriter, parent, mod_path, names_to_import);
66 builder.rewrite(rewriter);
65 }, 67 },
66 ) 68 )
67} 69}
@@ -236,7 +238,7 @@ fn find_names_to_import(
236} 238}
237 239
238fn replace_ast( 240fn replace_ast(
239 builder: &mut AssistBuilder, 241 rewriter: &mut SyntaxRewriter,
240 parent: Either<ast::UseTree, ast::UseTreeList>, 242 parent: Either<ast::UseTree, ast::UseTreeList>,
241 path: ast::Path, 243 path: ast::Path,
242 names_to_import: Vec<Name>, 244 names_to_import: Vec<Name>,
@@ -264,32 +266,21 @@ fn replace_ast(
264 match use_trees.as_slice() { 266 match use_trees.as_slice() {
265 [name] => { 267 [name] => {
266 if let Some(end_path) = name.path() { 268 if let Some(end_path) = name.path() {
267 let replacement = 269 rewriter.replace_ast(
268 make::use_tree(make::path_concat(path, end_path), None, None, false); 270 &parent.left_or_else(|tl| tl.parent_use_tree()),
269 271 &make::use_tree(make::path_concat(path, end_path), None, None, false),
270 algo::diff( 272 );
271 &parent.either(|n| n.syntax().clone(), |n| n.syntax().clone()),
272 replacement.syntax(),
273 )
274 .into_text_edit(builder.text_edit_builder());
275 } 273 }
276 } 274 }
277 names => { 275 names => match &parent {
278 let replacement = match parent { 276 Either::Left(parent) => rewriter.replace_ast(
279 Either::Left(_) => { 277 parent,
280 make::use_tree(path, Some(make::use_tree_list(names.to_owned())), None, false) 278 &make::use_tree(path, Some(make::use_tree_list(names.to_owned())), None, false),
281 .syntax() 279 ),
282 .clone() 280 Either::Right(parent) => {
283 } 281 rewriter.replace_ast(parent, &make::use_tree_list(names.to_owned()))
284 Either::Right(_) => make::use_tree_list(names.to_owned()).syntax().clone(), 282 }
285 }; 283 },
286
287 algo::diff(
288 &parent.either(|n| n.syntax().clone(), |n| n.syntax().clone()),
289 &replacement,
290 )
291 .into_text_edit(builder.text_edit_builder());
292 }
293 }; 284 };
294} 285}
295 286
@@ -884,4 +875,33 @@ fn qux(baz: Baz) {}
884 ", 875 ",
885 ) 876 )
886 } 877 }
878
879 #[test]
880 fn expanding_glob_import_single_nested_glob_only() {
881 check_assist(
882 expand_glob_import,
883 r"
884mod foo {
885 pub struct Bar;
886}
887
888use foo::{*<|>};
889
890struct Baz {
891 bar: Bar
892}
893",
894 r"
895mod foo {
896 pub struct Bar;
897}
898
899use foo::Bar;
900
901struct Baz {
902 bar: Bar
903}
904",
905 );
906 }
887} 907}
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 dddab255e..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,3 +1,6 @@
1use std::iter;
2
3use either::Either;
1use hir::{AsName, EnumVariant, Module, ModuleDef, Name}; 4use hir::{AsName, EnumVariant, Module, ModuleDef, Name};
2use ide_db::{defs::Definition, search::Reference, RootDatabase}; 5use ide_db::{defs::Definition, search::Reference, RootDatabase};
3use rustc_hash::{FxHashMap, FxHashSet}; 6use rustc_hash::{FxHashMap, FxHashSet};
@@ -31,40 +34,32 @@ pub(crate) fn extract_struct_from_enum_variant(
31 ctx: &AssistContext, 34 ctx: &AssistContext,
32) -> Option<()> { 35) -> Option<()> {
33 let variant = ctx.find_node_at_offset::<ast::Variant>()?; 36 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
34 let field_list = match variant.kind() { 37 let field_list = extract_field_list_if_applicable(&variant)?;
35 ast::StructKind::Tuple(field_list) => field_list,
36 _ => return None,
37 };
38
39 // skip 1-tuple variants
40 if field_list.fields().count() == 1 {
41 return None;
42 }
43 38
44 let variant_name = variant.name()?; 39 let variant_name = variant.name()?;
45 let variant_hir = ctx.sema.to_def(&variant)?; 40 let variant_hir = ctx.sema.to_def(&variant)?;
46 if existing_struct_def(ctx.db(), &variant_name, &variant_hir) { 41 if existing_definition(ctx.db(), &variant_name, &variant_hir) {
47 return None; 42 return None;
48 } 43 }
44
49 let enum_ast = variant.parent_enum(); 45 let enum_ast = variant.parent_enum();
50 let visibility = enum_ast.visibility();
51 let enum_hir = ctx.sema.to_def(&enum_ast)?; 46 let enum_hir = ctx.sema.to_def(&enum_ast)?;
52 let variant_hir_name = variant_hir.name(ctx.db());
53 let enum_module_def = ModuleDef::from(enum_hir);
54 let current_module = enum_hir.module(ctx.db());
55 let target = variant.syntax().text_range(); 47 let target = variant.syntax().text_range();
56 acc.add( 48 acc.add(
57 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite), 49 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
58 "Extract struct from enum variant", 50 "Extract struct from enum variant",
59 target, 51 target,
60 |builder| { 52 |builder| {
61 let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir)); 53 let variant_hir_name = variant_hir.name(ctx.db());
62 let res = definition.usages(&ctx.sema).all(); 54 let enum_module_def = ModuleDef::from(enum_hir);
55 let usages =
56 Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir)).usages(&ctx.sema).all();
63 57
64 let mut visited_modules_set = FxHashSet::default(); 58 let mut visited_modules_set = FxHashSet::default();
59 let current_module = enum_hir.module(ctx.db());
65 visited_modules_set.insert(current_module); 60 visited_modules_set.insert(current_module);
66 let mut rewriters = FxHashMap::default(); 61 let mut rewriters = FxHashMap::default();
67 for reference in res { 62 for reference in usages {
68 let rewriter = rewriters 63 let rewriter = rewriters
69 .entry(reference.file_range.file_id) 64 .entry(reference.file_range.file_id)
70 .or_insert_with(SyntaxRewriter::default); 65 .or_insert_with(SyntaxRewriter::default);
@@ -86,26 +81,49 @@ pub(crate) fn extract_struct_from_enum_variant(
86 builder.rewrite(rewriter); 81 builder.rewrite(rewriter);
87 } 82 }
88 builder.edit_file(ctx.frange.file_id); 83 builder.edit_file(ctx.frange.file_id);
89 update_variant(&mut rewriter, &variant_name, &field_list); 84 update_variant(&mut rewriter, &variant);
90 extract_struct_def( 85 extract_struct_def(
91 &mut rewriter, 86 &mut rewriter,
92 &enum_ast, 87 &enum_ast,
93 variant_name.clone(), 88 variant_name.clone(),
94 &field_list, 89 &field_list,
95 &variant.parent_enum().syntax().clone().into(), 90 &variant.parent_enum().syntax().clone().into(),
96 visibility, 91 enum_ast.visibility(),
97 ); 92 );
98 builder.rewrite(rewriter); 93 builder.rewrite(rewriter);
99 }, 94 },
100 ) 95 )
101} 96}
102 97
103fn existing_struct_def(db: &RootDatabase, variant_name: &ast::Name, 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 {
104 variant 113 variant
105 .parent_enum(db) 114 .parent_enum(db)
106 .module(db) 115 .module(db)
107 .scope(db, None) 116 .scope(db, None)
108 .into_iter() 117 .into_iter()
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 })
109 .any(|(name, _)| name == variant_name.as_name()) 127 .any(|(name, _)| name == variant_name.as_name())
110} 128}
111 129
@@ -133,19 +151,29 @@ fn extract_struct_def(
133 rewriter: &mut SyntaxRewriter, 151 rewriter: &mut SyntaxRewriter,
134 enum_: &ast::Enum, 152 enum_: &ast::Enum,
135 variant_name: ast::Name, 153 variant_name: ast::Name,
136 variant_list: &ast::TupleFieldList, 154 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
137 start_offset: &SyntaxElement, 155 start_offset: &SyntaxElement,
138 visibility: Option<ast::Visibility>, 156 visibility: Option<ast::Visibility>,
139) -> Option<()> { 157) -> Option<()> {
140 let variant_list = make::tuple_field_list( 158 let pub_vis = Some(make::visibility_pub());
141 variant_list 159 let field_list = match field_list {
142 .fields() 160 Either::Left(field_list) => {
143 .flat_map(|field| Some(make::tuple_field(Some(make::visibility_pub()), field.ty()?))), 161 make::record_field_list(field_list.fields().flat_map(|field| {
144 ); 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(),
172 };
145 173
146 rewriter.insert_before( 174 rewriter.insert_before(
147 start_offset, 175 start_offset,
148 make::struct_(visibility, variant_name, None, variant_list.into()).syntax(), 176 make::struct_(visibility, variant_name, None, field_list).syntax(),
149 ); 177 );
150 rewriter.insert_before(start_offset, &make::tokens::blank_line()); 178 rewriter.insert_before(start_offset, &make::tokens::blank_line());
151 179
@@ -156,15 +184,14 @@ fn extract_struct_def(
156 Some(()) 184 Some(())
157} 185}
158 186
159fn update_variant( 187fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> {
160 rewriter: &mut SyntaxRewriter, 188 let name = variant.name()?;
161 variant_name: &ast::Name, 189 let tuple_field = make::tuple_field(None, make::ty(name.text()));
162 field_list: &ast::TupleFieldList, 190 let replacement = make::variant(
163) -> Option<()> { 191 name,
164 let (l, r): (SyntaxElement, SyntaxElement) = 192 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
165 (field_list.l_paren_token()?.into(), field_list.r_paren_token()?.into()); 193 );
166 let replacement = vec![l, variant_name.syntax().clone().into(), r]; 194 rewriter.replace(variant.syntax(), replacement.syntax());
167 rewriter.replace_with_many(field_list.syntax(), replacement);
168 Some(()) 195 Some(())
169} 196}
170 197
@@ -211,7 +238,7 @@ mod tests {
211 use super::*; 238 use super::*;
212 239
213 #[test] 240 #[test]
214 fn test_extract_struct_several_fields() { 241 fn test_extract_struct_several_fields_tuple() {
215 check_assist( 242 check_assist(
216 extract_struct_from_enum_variant, 243 extract_struct_from_enum_variant,
217 "enum A { <|>One(u32, u32) }", 244 "enum A { <|>One(u32, u32) }",
@@ -222,6 +249,41 @@ enum A { One(One) }"#,
222 } 249 }
223 250
224 #[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]
225 fn test_extract_struct_pub_visibility() { 287 fn test_extract_struct_pub_visibility() {
226 check_assist( 288 check_assist(
227 extract_struct_from_enum_variant, 289 extract_struct_from_enum_variant,
@@ -298,7 +360,7 @@ fn another_fn() {
298 fn test_extract_enum_not_applicable_if_struct_exists() { 360 fn test_extract_enum_not_applicable_if_struct_exists() {
299 check_not_applicable( 361 check_not_applicable(
300 r#"struct One; 362 r#"struct One;
301 enum A { <|>One(u8) }"#, 363 enum A { <|>One(u8, u32) }"#,
302 ); 364 );
303 } 365 }
304 366
@@ -306,4 +368,14 @@ fn another_fn() {
306 fn test_extract_not_applicable_one_field() { 368 fn test_extract_not_applicable_one_field() {
307 check_not_applicable(r"enum A { <|>One(u32) }"); 369 check_not_applicable(r"enum A { <|>One(u32) }");
308 } 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 }
309} 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/reorder_fields.rs b/crates/assists/src/handlers/reorder_fields.rs
index 527f457a7..7c0f0f44e 100644
--- a/crates/assists/src/handlers/reorder_fields.rs
+++ b/crates/assists/src/handlers/reorder_fields.rs
@@ -47,9 +47,11 @@ fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
47 "Reorder record fields", 47 "Reorder record fields",
48 target, 48 target,
49 |edit| { 49 |edit| {
50 let mut rewriter = algo::SyntaxRewriter::default();
50 for (old, new) in fields.iter().zip(&sorted_fields) { 51 for (old, new) in fields.iter().zip(&sorted_fields) {
51 algo::diff(old, new).into_text_edit(edit.text_edit_builder()); 52 rewriter.replace(old, new);
52 } 53 }
54 edit.rewrite(rewriter);
53 }, 55 },
54 ) 56 )
55} 57}
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_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.rs b/crates/assists/src/utils.rs
index 56f925ee6..7071fe96b 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -4,17 +4,22 @@ pub(crate) mod import_assets;
4 4
5use std::ops; 5use std::ops;
6 6
7use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait}; 7use hir::{Crate, Enum, HasSource, Module, ScopeDef, Semantics, Trait};
8use ide_db::RootDatabase; 8use ide_db::RootDatabase;
9use itertools::Itertools; 9use itertools::Itertools;
10use syntax::{ 10use syntax::{
11 ast::{self, make, ArgListOwner}, 11 ast::edit::AstNodeEdit,
12 ast::NameOwner,
13 ast::{self, edit, make, ArgListOwner},
12 AstNode, Direction, 14 AstNode, Direction,
13 SyntaxKind::*, 15 SyntaxKind::*,
14 SyntaxNode, TextSize, T, 16 SyntaxNode, TextSize, T,
15}; 17};
16 18
17use crate::assist_config::SnippetCap; 19use crate::{
20 assist_config::SnippetCap,
21 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
22};
18 23
19pub use insert_use::MergeBehaviour; 24pub use insert_use::MergeBehaviour;
20pub(crate) use insert_use::{insert_use, ImportScope}; 25pub(crate) use insert_use::{insert_use, ImportScope};
@@ -77,6 +82,87 @@ pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option<ast::Expr> {
77 None 82 None
78} 83}
79 84
85#[derive(Copy, Clone, PartialEq)]
86pub enum DefaultMethods {
87 Only,
88 No,
89}
90
91pub fn filter_assoc_items(
92 db: &RootDatabase,
93 items: &[hir::AssocItem],
94 default_methods: DefaultMethods,
95) -> Vec<ast::AssocItem> {
96 fn has_def_name(item: &ast::AssocItem) -> bool {
97 match item {
98 ast::AssocItem::Fn(def) => def.name(),
99 ast::AssocItem::TypeAlias(def) => def.name(),
100 ast::AssocItem::Const(def) => def.name(),
101 ast::AssocItem::MacroCall(_) => None,
102 }
103 .is_some()
104 };
105
106 items
107 .iter()
108 .map(|i| match i {
109 hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(db).value),
110 hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(db).value),
111 hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(db).value),
112 })
113 .filter(has_def_name)
114 .filter(|it| match it {
115 ast::AssocItem::Fn(def) => matches!(
116 (default_methods, def.body()),
117 (DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None)
118 ),
119 _ => default_methods == DefaultMethods::No,
120 })
121 .collect::<Vec<_>>()
122}
123
124pub fn add_trait_assoc_items_to_impl(
125 sema: &hir::Semantics<ide_db::RootDatabase>,
126 items: Vec<ast::AssocItem>,
127 trait_: hir::Trait,
128 impl_def: ast::Impl,
129 target_scope: hir::SemanticsScope,
130) -> (ast::Impl, ast::AssocItem) {
131 let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list);
132
133 let n_existing_items = impl_item_list.assoc_items().count();
134 let source_scope = sema.scope_for_def(trait_);
135 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
136 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone()));
137
138 let items = items
139 .into_iter()
140 .map(|it| ast_transform::apply(&*ast_transform, it))
141 .map(|it| match it {
142 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
143 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
144 _ => it,
145 })
146 .map(|it| edit::remove_attrs_and_docs(&it));
147
148 let new_impl_item_list = impl_item_list.append_items(items);
149 let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list);
150 let first_new_item =
151 new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap();
152 return (new_impl_def, first_new_item);
153
154 fn add_body(fn_def: ast::Fn) -> ast::Fn {
155 match fn_def.body() {
156 Some(_) => fn_def,
157 None => {
158 let body =
159 make::block_expr(None, Some(make::expr_todo())).indent(edit::IndentLevel(1));
160 fn_def.with_body(body)
161 }
162 }
163 }
164}
165
80#[derive(Clone, Copy, Debug)] 166#[derive(Clone, Copy, Debug)]
81pub(crate) enum Cursor<'a> { 167pub(crate) enum Cursor<'a> {
82 Replace(&'a SyntaxNode), 168 Replace(&'a SyntaxNode),