diff options
Diffstat (limited to 'crates/ide_assists/src')
-rw-r--r-- | crates/ide_assists/src/assist_context.rs | 5 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/auto_import.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/convert_into_to_from.rs | 355 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/extract_function.rs | 176 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/extract_type_alias.rs | 149 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/extract_variable.rs | 73 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/fill_match_arms.rs | 22 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/flip_comma.rs | 18 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/generate_default_from_new.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/generate_is_empty_from_len.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/remove_dbg.rs | 105 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/reorder_fields.rs | 89 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 6 | ||||
-rw-r--r-- | crates/ide_assists/src/tests.rs | 6 | ||||
-rw-r--r-- | crates/ide_assists/src/tests/generated.rs | 51 | ||||
-rw-r--r-- | crates/ide_assists/src/utils.rs | 4 |
16 files changed, 943 insertions, 122 deletions
diff --git a/crates/ide_assists/src/assist_context.rs b/crates/ide_assists/src/assist_context.rs index 1482d37f8..8714e4978 100644 --- a/crates/ide_assists/src/assist_context.rs +++ b/crates/ide_assists/src/assist_context.rs | |||
@@ -13,7 +13,7 @@ use ide_db::{ | |||
13 | RootDatabase, | 13 | RootDatabase, |
14 | }; | 14 | }; |
15 | use syntax::{ | 15 | use syntax::{ |
16 | algo::{self, find_node_at_offset, SyntaxRewriter}, | 16 | algo::{self, find_node_at_offset, find_node_at_range, SyntaxRewriter}, |
17 | AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxNodePtr, | 17 | AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxNodePtr, |
18 | SyntaxToken, TextRange, TextSize, TokenAtOffset, | 18 | SyntaxToken, TextRange, TextSize, TokenAtOffset, |
19 | }; | 19 | }; |
@@ -89,6 +89,9 @@ impl<'a> AssistContext<'a> { | |||
89 | pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> { | 89 | pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> { |
90 | find_node_at_offset(self.source_file.syntax(), self.offset()) | 90 | find_node_at_offset(self.source_file.syntax(), self.offset()) |
91 | } | 91 | } |
92 | pub(crate) fn find_node_at_range<N: AstNode>(&self) -> Option<N> { | ||
93 | find_node_at_range(self.source_file.syntax(), self.frange.range) | ||
94 | } | ||
92 | pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> { | 95 | pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> { |
93 | self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset()) | 96 | self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset()) |
94 | } | 97 | } |
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs index 7019039b9..5ccd7f7a2 100644 --- a/crates/ide_assists/src/handlers/auto_import.rs +++ b/crates/ide_assists/src/handlers/auto_import.rs | |||
@@ -61,6 +61,8 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; | |||
61 | // - `plain`: This setting does not impose any restrictions in imports. | 61 | // - `plain`: This setting does not impose any restrictions in imports. |
62 | // | 62 | // |
63 | // In `VS Code` the configuration for this is `rust-analyzer.assist.importPrefix`. | 63 | // In `VS Code` the configuration for this is `rust-analyzer.assist.importPrefix`. |
64 | // | ||
65 | // image::https://user-images.githubusercontent.com/48062697/113020673-b85be580-917a-11eb-9022-59585f35d4f8.gif[] | ||
64 | 66 | ||
65 | // Assist: auto_import | 67 | // Assist: auto_import |
66 | // | 68 | // |
diff --git a/crates/ide_assists/src/handlers/convert_into_to_from.rs b/crates/ide_assists/src/handlers/convert_into_to_from.rs new file mode 100644 index 000000000..199e1ad5c --- /dev/null +++ b/crates/ide_assists/src/handlers/convert_into_to_from.rs | |||
@@ -0,0 +1,355 @@ | |||
1 | use ide_db::{ | ||
2 | helpers::{mod_path_to_ast, FamousDefs}, | ||
3 | traits::resolve_target_trait, | ||
4 | }; | ||
5 | use syntax::ast::{self, AstNode, NameOwner}; | ||
6 | |||
7 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | ||
8 | |||
9 | // Assist: convert_into_to_from | ||
10 | // | ||
11 | // Converts an Into impl to an equivalent From impl. | ||
12 | // | ||
13 | // ``` | ||
14 | // # //- /lib.rs crate:core | ||
15 | // # pub mod convert { pub trait Into<T> { pub fn into(self) -> T; } } | ||
16 | // # //- /lib.rs crate:main deps:core | ||
17 | // # use core::convert::Into; | ||
18 | // impl $0Into<Thing> for usize { | ||
19 | // fn into(self) -> Thing { | ||
20 | // Thing { | ||
21 | // b: self.to_string(), | ||
22 | // a: self | ||
23 | // } | ||
24 | // } | ||
25 | // } | ||
26 | // ``` | ||
27 | // -> | ||
28 | // ``` | ||
29 | // # use core::convert::Into; | ||
30 | // impl From<usize> for Thing { | ||
31 | // fn from(val: usize) -> Self { | ||
32 | // Thing { | ||
33 | // b: val.to_string(), | ||
34 | // a: val | ||
35 | // } | ||
36 | // } | ||
37 | // } | ||
38 | // ``` | ||
39 | pub(crate) fn convert_into_to_from(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
40 | let impl_ = ctx.find_node_at_offset::<ast::Impl>()?; | ||
41 | let src_type = impl_.self_ty()?; | ||
42 | let ast_trait = impl_.trait_()?; | ||
43 | |||
44 | let module = ctx.sema.scope(impl_.syntax()).module()?; | ||
45 | |||
46 | let trait_ = resolve_target_trait(&ctx.sema, &impl_)?; | ||
47 | if trait_ != FamousDefs(&ctx.sema, Some(module.krate())).core_convert_Into()? { | ||
48 | return None; | ||
49 | } | ||
50 | |||
51 | let src_type_path = { | ||
52 | let src_type_path = src_type.syntax().descendants().find_map(ast::Path::cast)?; | ||
53 | let src_type_def = match ctx.sema.resolve_path(&src_type_path) { | ||
54 | Some(hir::PathResolution::Def(module_def)) => module_def, | ||
55 | _ => return None, | ||
56 | }; | ||
57 | |||
58 | mod_path_to_ast(&module.find_use_path(ctx.db(), src_type_def)?) | ||
59 | }; | ||
60 | |||
61 | let dest_type = match &ast_trait { | ||
62 | ast::Type::PathType(path) => { | ||
63 | path.path()?.segment()?.generic_arg_list()?.generic_args().next()? | ||
64 | } | ||
65 | _ => return None, | ||
66 | }; | ||
67 | |||
68 | let into_fn = impl_.assoc_item_list()?.assoc_items().find_map(|item| { | ||
69 | if let ast::AssocItem::Fn(f) = item { | ||
70 | if f.name()?.text() == "into" { | ||
71 | return Some(f); | ||
72 | } | ||
73 | }; | ||
74 | None | ||
75 | })?; | ||
76 | |||
77 | let into_fn_name = into_fn.name()?; | ||
78 | let into_fn_params = into_fn.param_list()?; | ||
79 | let into_fn_return = into_fn.ret_type()?; | ||
80 | |||
81 | let selfs = into_fn | ||
82 | .body()? | ||
83 | .syntax() | ||
84 | .descendants() | ||
85 | .filter_map(ast::NameRef::cast) | ||
86 | .filter(|name| name.text() == "self" || name.text() == "Self"); | ||
87 | |||
88 | acc.add( | ||
89 | AssistId("convert_into_to_from", AssistKind::RefactorRewrite), | ||
90 | "Convert Into to From", | ||
91 | impl_.syntax().text_range(), | ||
92 | |builder| { | ||
93 | builder.replace(src_type.syntax().text_range(), dest_type.to_string()); | ||
94 | builder.replace(ast_trait.syntax().text_range(), format!("From<{}>", src_type)); | ||
95 | builder.replace(into_fn_return.syntax().text_range(), "-> Self"); | ||
96 | builder.replace( | ||
97 | into_fn_params.syntax().text_range(), | ||
98 | format!("(val: {})", src_type.to_string()), | ||
99 | ); | ||
100 | builder.replace(into_fn_name.syntax().text_range(), "from"); | ||
101 | |||
102 | for s in selfs { | ||
103 | match s.text().as_ref() { | ||
104 | "self" => builder.replace(s.syntax().text_range(), "val"), | ||
105 | "Self" => builder.replace(s.syntax().text_range(), src_type_path.to_string()), | ||
106 | _ => {} | ||
107 | } | ||
108 | } | ||
109 | }, | ||
110 | ) | ||
111 | } | ||
112 | |||
113 | #[cfg(test)] | ||
114 | mod tests { | ||
115 | use super::*; | ||
116 | |||
117 | use crate::tests::check_assist; | ||
118 | |||
119 | #[test] | ||
120 | fn convert_into_to_from_converts_a_struct() { | ||
121 | check_convert_into_to_from( | ||
122 | r#" | ||
123 | struct Thing { | ||
124 | a: String, | ||
125 | b: usize | ||
126 | } | ||
127 | |||
128 | impl $0core::convert::Into<Thing> for usize { | ||
129 | fn into(self) -> Thing { | ||
130 | Thing { | ||
131 | b: self.to_string(), | ||
132 | a: self | ||
133 | } | ||
134 | } | ||
135 | } | ||
136 | "#, | ||
137 | r#" | ||
138 | struct Thing { | ||
139 | a: String, | ||
140 | b: usize | ||
141 | } | ||
142 | |||
143 | impl From<usize> for Thing { | ||
144 | fn from(val: usize) -> Self { | ||
145 | Thing { | ||
146 | b: val.to_string(), | ||
147 | a: val | ||
148 | } | ||
149 | } | ||
150 | } | ||
151 | "#, | ||
152 | ) | ||
153 | } | ||
154 | |||
155 | #[test] | ||
156 | fn convert_into_to_from_converts_enums() { | ||
157 | check_convert_into_to_from( | ||
158 | r#" | ||
159 | enum Thing { | ||
160 | Foo(String), | ||
161 | Bar(String) | ||
162 | } | ||
163 | |||
164 | impl $0core::convert::Into<String> for Thing { | ||
165 | fn into(self) -> String { | ||
166 | match self { | ||
167 | Self::Foo(s) => s, | ||
168 | Self::Bar(s) => s | ||
169 | } | ||
170 | } | ||
171 | } | ||
172 | "#, | ||
173 | r#" | ||
174 | enum Thing { | ||
175 | Foo(String), | ||
176 | Bar(String) | ||
177 | } | ||
178 | |||
179 | impl From<Thing> for String { | ||
180 | fn from(val: Thing) -> Self { | ||
181 | match val { | ||
182 | Thing::Foo(s) => s, | ||
183 | Thing::Bar(s) => s | ||
184 | } | ||
185 | } | ||
186 | } | ||
187 | "#, | ||
188 | ) | ||
189 | } | ||
190 | |||
191 | #[test] | ||
192 | fn convert_into_to_from_on_enum_with_lifetimes() { | ||
193 | check_convert_into_to_from( | ||
194 | r#" | ||
195 | enum Thing<'a> { | ||
196 | Foo(&'a str), | ||
197 | Bar(&'a str) | ||
198 | } | ||
199 | |||
200 | impl<'a> $0core::convert::Into<&'a str> for Thing<'a> { | ||
201 | fn into(self) -> &'a str { | ||
202 | match self { | ||
203 | Self::Foo(s) => s, | ||
204 | Self::Bar(s) => s | ||
205 | } | ||
206 | } | ||
207 | } | ||
208 | "#, | ||
209 | r#" | ||
210 | enum Thing<'a> { | ||
211 | Foo(&'a str), | ||
212 | Bar(&'a str) | ||
213 | } | ||
214 | |||
215 | impl<'a> From<Thing<'a>> for &'a str { | ||
216 | fn from(val: Thing<'a>) -> Self { | ||
217 | match val { | ||
218 | Thing::Foo(s) => s, | ||
219 | Thing::Bar(s) => s | ||
220 | } | ||
221 | } | ||
222 | } | ||
223 | "#, | ||
224 | ) | ||
225 | } | ||
226 | |||
227 | #[test] | ||
228 | fn convert_into_to_from_works_on_references() { | ||
229 | check_convert_into_to_from( | ||
230 | r#" | ||
231 | struct Thing(String); | ||
232 | |||
233 | impl $0core::convert::Into<String> for &Thing { | ||
234 | fn into(self) -> Thing { | ||
235 | self.0.clone() | ||
236 | } | ||
237 | } | ||
238 | "#, | ||
239 | r#" | ||
240 | struct Thing(String); | ||
241 | |||
242 | impl From<&Thing> for String { | ||
243 | fn from(val: &Thing) -> Self { | ||
244 | val.0.clone() | ||
245 | } | ||
246 | } | ||
247 | "#, | ||
248 | ) | ||
249 | } | ||
250 | |||
251 | #[test] | ||
252 | fn convert_into_to_from_works_on_qualified_structs() { | ||
253 | check_convert_into_to_from( | ||
254 | r#" | ||
255 | mod things { | ||
256 | pub struct Thing(String); | ||
257 | pub struct BetterThing(String); | ||
258 | } | ||
259 | |||
260 | impl $0core::convert::Into<things::BetterThing> for &things::Thing { | ||
261 | fn into(self) -> Thing { | ||
262 | things::BetterThing(self.0.clone()) | ||
263 | } | ||
264 | } | ||
265 | "#, | ||
266 | r#" | ||
267 | mod things { | ||
268 | pub struct Thing(String); | ||
269 | pub struct BetterThing(String); | ||
270 | } | ||
271 | |||
272 | impl From<&things::Thing> for things::BetterThing { | ||
273 | fn from(val: &things::Thing) -> Self { | ||
274 | things::BetterThing(val.0.clone()) | ||
275 | } | ||
276 | } | ||
277 | "#, | ||
278 | ) | ||
279 | } | ||
280 | |||
281 | #[test] | ||
282 | fn convert_into_to_from_works_on_qualified_enums() { | ||
283 | check_convert_into_to_from( | ||
284 | r#" | ||
285 | mod things { | ||
286 | pub enum Thing { | ||
287 | A(String) | ||
288 | } | ||
289 | pub struct BetterThing { | ||
290 | B(String) | ||
291 | } | ||
292 | } | ||
293 | |||
294 | impl $0core::convert::Into<things::BetterThing> for &things::Thing { | ||
295 | fn into(self) -> Thing { | ||
296 | match self { | ||
297 | Self::A(s) => things::BetterThing::B(s) | ||
298 | } | ||
299 | } | ||
300 | } | ||
301 | "#, | ||
302 | r#" | ||
303 | mod things { | ||
304 | pub enum Thing { | ||
305 | A(String) | ||
306 | } | ||
307 | pub struct BetterThing { | ||
308 | B(String) | ||
309 | } | ||
310 | } | ||
311 | |||
312 | impl From<&things::Thing> for things::BetterThing { | ||
313 | fn from(val: &things::Thing) -> Self { | ||
314 | match val { | ||
315 | things::Thing::A(s) => things::BetterThing::B(s) | ||
316 | } | ||
317 | } | ||
318 | } | ||
319 | "#, | ||
320 | ) | ||
321 | } | ||
322 | |||
323 | #[test] | ||
324 | fn convert_into_to_from_not_applicable_on_any_trait_named_into() { | ||
325 | check_assist_not_applicable( | ||
326 | r#" | ||
327 | pub trait Into<T> {{ | ||
328 | pub fn into(self) -> T; | ||
329 | }} | ||
330 | |||
331 | struct Thing { | ||
332 | a: String, | ||
333 | } | ||
334 | |||
335 | impl $0Into<Thing> for String { | ||
336 | fn into(self) -> Thing { | ||
337 | Thing { | ||
338 | a: self | ||
339 | } | ||
340 | } | ||
341 | } | ||
342 | "#, | ||
343 | ); | ||
344 | } | ||
345 | |||
346 | fn check_convert_into_to_from(before: &str, after: &str) { | ||
347 | let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE); | ||
348 | check_assist(convert_into_to_from, before, after); | ||
349 | } | ||
350 | |||
351 | fn check_assist_not_applicable(before: &str) { | ||
352 | let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE); | ||
353 | crate::tests::check_assist_not_applicable(convert_into_to_from, before); | ||
354 | } | ||
355 | } | ||
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs index 5fdc8bf38..059414274 100644 --- a/crates/ide_assists/src/handlers/extract_function.rs +++ b/crates/ide_assists/src/handlers/extract_function.rs | |||
@@ -75,7 +75,8 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
75 | let insert_after = scope_for_fn_insertion(&body, anchor)?; | 75 | let insert_after = scope_for_fn_insertion(&body, anchor)?; |
76 | let module = ctx.sema.scope(&insert_after).module()?; | 76 | let module = ctx.sema.scope(&insert_after).module()?; |
77 | 77 | ||
78 | let vars_defined_in_body_and_outlive = vars_defined_in_body_and_outlive(ctx, &body); | 78 | let vars_defined_in_body_and_outlive = |
79 | vars_defined_in_body_and_outlive(ctx, &body, &node.parent().as_ref().unwrap_or(&node)); | ||
79 | let ret_ty = body_return_ty(ctx, &body)?; | 80 | let ret_ty = body_return_ty(ctx, &body)?; |
80 | 81 | ||
81 | // FIXME: we compute variables that outlive here just to check `never!` condition | 82 | // FIXME: we compute variables that outlive here just to check `never!` condition |
@@ -257,7 +258,7 @@ struct Function { | |||
257 | control_flow: ControlFlow, | 258 | control_flow: ControlFlow, |
258 | ret_ty: RetType, | 259 | ret_ty: RetType, |
259 | body: FunctionBody, | 260 | body: FunctionBody, |
260 | vars_defined_in_body_and_outlive: Vec<Local>, | 261 | vars_defined_in_body_and_outlive: Vec<OutlivedLocal>, |
261 | } | 262 | } |
262 | 263 | ||
263 | #[derive(Debug)] | 264 | #[derive(Debug)] |
@@ -296,9 +297,9 @@ impl Function { | |||
296 | RetType::Expr(ty) => FunType::Single(ty.clone()), | 297 | RetType::Expr(ty) => FunType::Single(ty.clone()), |
297 | RetType::Stmt => match self.vars_defined_in_body_and_outlive.as_slice() { | 298 | RetType::Stmt => match self.vars_defined_in_body_and_outlive.as_slice() { |
298 | [] => FunType::Unit, | 299 | [] => FunType::Unit, |
299 | [var] => FunType::Single(var.ty(ctx.db())), | 300 | [var] => FunType::Single(var.local.ty(ctx.db())), |
300 | vars => { | 301 | vars => { |
301 | let types = vars.iter().map(|v| v.ty(ctx.db())).collect(); | 302 | let types = vars.iter().map(|v| v.local.ty(ctx.db())).collect(); |
302 | FunType::Tuple(types) | 303 | FunType::Tuple(types) |
303 | } | 304 | } |
304 | }, | 305 | }, |
@@ -562,6 +563,12 @@ impl HasTokenAtOffset for FunctionBody { | |||
562 | } | 563 | } |
563 | } | 564 | } |
564 | 565 | ||
566 | #[derive(Debug)] | ||
567 | struct OutlivedLocal { | ||
568 | local: Local, | ||
569 | mut_usage_outside_body: bool, | ||
570 | } | ||
571 | |||
565 | /// Try to guess what user wants to extract | 572 | /// Try to guess what user wants to extract |
566 | /// | 573 | /// |
567 | /// We have basically have two cases: | 574 | /// We have basically have two cases: |
@@ -707,10 +714,10 @@ fn has_exclusive_usages(ctx: &AssistContext, usages: &LocalUsages, body: &Functi | |||
707 | .any(|reference| reference_is_exclusive(reference, body, ctx)) | 714 | .any(|reference| reference_is_exclusive(reference, body, ctx)) |
708 | } | 715 | } |
709 | 716 | ||
710 | /// checks if this reference requires `&mut` access inside body | 717 | /// checks if this reference requires `&mut` access inside node |
711 | fn reference_is_exclusive( | 718 | fn reference_is_exclusive( |
712 | reference: &FileReference, | 719 | reference: &FileReference, |
713 | body: &FunctionBody, | 720 | node: &dyn HasTokenAtOffset, |
714 | ctx: &AssistContext, | 721 | ctx: &AssistContext, |
715 | ) -> bool { | 722 | ) -> bool { |
716 | // we directly modify variable with set: `n = 0`, `n += 1` | 723 | // we directly modify variable with set: `n = 0`, `n += 1` |
@@ -719,7 +726,7 @@ fn reference_is_exclusive( | |||
719 | } | 726 | } |
720 | 727 | ||
721 | // we take `&mut` reference to variable: `&mut v` | 728 | // we take `&mut` reference to variable: `&mut v` |
722 | let path = match path_element_of_reference(body, reference) { | 729 | let path = match path_element_of_reference(node, reference) { |
723 | Some(path) => path, | 730 | Some(path) => path, |
724 | None => return false, | 731 | None => return false, |
725 | }; | 732 | }; |
@@ -729,6 +736,14 @@ fn reference_is_exclusive( | |||
729 | 736 | ||
730 | /// checks if this expr requires `&mut` access, recurses on field access | 737 | /// checks if this expr requires `&mut` access, recurses on field access |
731 | fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> { | 738 | fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> { |
739 | match expr { | ||
740 | ast::Expr::MacroCall(_) => { | ||
741 | // FIXME: expand macro and check output for mutable usages of the variable? | ||
742 | return None; | ||
743 | } | ||
744 | _ => (), | ||
745 | } | ||
746 | |||
732 | let parent = expr.syntax().parent()?; | 747 | let parent = expr.syntax().parent()?; |
733 | 748 | ||
734 | if let Some(bin_expr) = ast::BinExpr::cast(parent.clone()) { | 749 | if let Some(bin_expr) = ast::BinExpr::cast(parent.clone()) { |
@@ -787,7 +802,7 @@ impl HasTokenAtOffset for SyntaxNode { | |||
787 | } | 802 | } |
788 | } | 803 | } |
789 | 804 | ||
790 | /// find relevant `ast::PathExpr` for reference | 805 | /// find relevant `ast::Expr` for reference |
791 | /// | 806 | /// |
792 | /// # Preconditions | 807 | /// # Preconditions |
793 | /// | 808 | /// |
@@ -804,7 +819,11 @@ fn path_element_of_reference( | |||
804 | stdx::never!(false, "cannot find path parent of variable usage: {:?}", token); | 819 | stdx::never!(false, "cannot find path parent of variable usage: {:?}", token); |
805 | None | 820 | None |
806 | })?; | 821 | })?; |
807 | stdx::always!(matches!(path, ast::Expr::PathExpr(_))); | 822 | stdx::always!( |
823 | matches!(path, ast::Expr::PathExpr(_) | ast::Expr::MacroCall(_)), | ||
824 | "unexpected expression type for variable usage: {:?}", | ||
825 | path | ||
826 | ); | ||
808 | Some(path) | 827 | Some(path) |
809 | } | 828 | } |
810 | 829 | ||
@@ -820,10 +839,16 @@ fn vars_defined_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> | |||
820 | } | 839 | } |
821 | 840 | ||
822 | /// list local variables defined inside `body` that should be returned from extracted function | 841 | /// list local variables defined inside `body` that should be returned from extracted function |
823 | fn vars_defined_in_body_and_outlive(ctx: &AssistContext, body: &FunctionBody) -> Vec<Local> { | 842 | fn vars_defined_in_body_and_outlive( |
824 | let mut vars_defined_in_body = vars_defined_in_body(&body, ctx); | 843 | ctx: &AssistContext, |
825 | vars_defined_in_body.retain(|var| var_outlives_body(ctx, body, var)); | 844 | body: &FunctionBody, |
845 | parent: &SyntaxNode, | ||
846 | ) -> Vec<OutlivedLocal> { | ||
847 | let vars_defined_in_body = vars_defined_in_body(&body, ctx); | ||
826 | vars_defined_in_body | 848 | vars_defined_in_body |
849 | .into_iter() | ||
850 | .filter_map(|var| var_outlives_body(ctx, body, var, parent)) | ||
851 | .collect() | ||
827 | } | 852 | } |
828 | 853 | ||
829 | /// checks if the relevant local was defined before(outside of) body | 854 | /// checks if the relevant local was defined before(outside of) body |
@@ -843,11 +868,23 @@ fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode { | |||
843 | } | 868 | } |
844 | } | 869 | } |
845 | 870 | ||
846 | /// checks if local variable is used after(outside of) body | 871 | /// returns usage details if local variable is used after(outside of) body |
847 | fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool { | 872 | fn var_outlives_body( |
848 | let usages = LocalUsages::find(ctx, *var); | 873 | ctx: &AssistContext, |
874 | body: &FunctionBody, | ||
875 | var: Local, | ||
876 | parent: &SyntaxNode, | ||
877 | ) -> Option<OutlivedLocal> { | ||
878 | let usages = LocalUsages::find(ctx, var); | ||
849 | let has_usages = usages.iter().any(|reference| body.preceedes_range(reference.range)); | 879 | let has_usages = usages.iter().any(|reference| body.preceedes_range(reference.range)); |
850 | has_usages | 880 | if !has_usages { |
881 | return None; | ||
882 | } | ||
883 | let has_mut_usages = usages | ||
884 | .iter() | ||
885 | .filter(|reference| body.preceedes_range(reference.range)) | ||
886 | .any(|reference| reference_is_exclusive(reference, parent, ctx)); | ||
887 | Some(OutlivedLocal { local: var, mut_usage_outside_body: has_mut_usages }) | ||
851 | } | 888 | } |
852 | 889 | ||
853 | fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> { | 890 | fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> { |
@@ -927,16 +964,25 @@ fn format_replacement(ctx: &AssistContext, fun: &Function, indent: IndentLevel) | |||
927 | let mut buf = String::new(); | 964 | let mut buf = String::new(); |
928 | match fun.vars_defined_in_body_and_outlive.as_slice() { | 965 | match fun.vars_defined_in_body_and_outlive.as_slice() { |
929 | [] => {} | 966 | [] => {} |
930 | [var] => format_to!(buf, "let {} = ", var.name(ctx.db()).unwrap()), | 967 | [var] => { |
968 | format_to!(buf, "let {}{} = ", mut_modifier(var), var.local.name(ctx.db()).unwrap()) | ||
969 | } | ||
931 | [v0, vs @ ..] => { | 970 | [v0, vs @ ..] => { |
932 | buf.push_str("let ("); | 971 | buf.push_str("let ("); |
933 | format_to!(buf, "{}", v0.name(ctx.db()).unwrap()); | 972 | format_to!(buf, "{}{}", mut_modifier(v0), v0.local.name(ctx.db()).unwrap()); |
934 | for var in vs { | 973 | for var in vs { |
935 | format_to!(buf, ", {}", var.name(ctx.db()).unwrap()); | 974 | format_to!(buf, ", {}{}", mut_modifier(var), var.local.name(ctx.db()).unwrap()); |
936 | } | 975 | } |
937 | buf.push_str(") = "); | 976 | buf.push_str(") = "); |
938 | } | 977 | } |
939 | } | 978 | } |
979 | fn mut_modifier(var: &OutlivedLocal) -> &'static str { | ||
980 | if var.mut_usage_outside_body { | ||
981 | "mut " | ||
982 | } else { | ||
983 | "" | ||
984 | } | ||
985 | } | ||
940 | format_to!(buf, "{}", expr); | 986 | format_to!(buf, "{}", expr); |
941 | if fun.ret_ty.is_unit() | 987 | if fun.ret_ty.is_unit() |
942 | && (!fun.vars_defined_in_body_and_outlive.is_empty() || !expr.is_block_like()) | 988 | && (!fun.vars_defined_in_body_and_outlive.is_empty() || !expr.is_block_like()) |
@@ -1199,10 +1245,10 @@ fn make_body( | |||
1199 | match fun.vars_defined_in_body_and_outlive.as_slice() { | 1245 | match fun.vars_defined_in_body_and_outlive.as_slice() { |
1200 | [] => {} | 1246 | [] => {} |
1201 | [var] => { | 1247 | [var] => { |
1202 | tail_expr = Some(path_expr_from_local(ctx, *var)); | 1248 | tail_expr = Some(path_expr_from_local(ctx, var.local)); |
1203 | } | 1249 | } |
1204 | vars => { | 1250 | vars => { |
1205 | let exprs = vars.iter().map(|var| path_expr_from_local(ctx, *var)); | 1251 | let exprs = vars.iter().map(|var| path_expr_from_local(ctx, var.local)); |
1206 | let expr = make::expr_tuple(exprs); | 1252 | let expr = make::expr_tuple(exprs); |
1207 | tail_expr = Some(expr); | 1253 | tail_expr = Some(expr); |
1208 | } | 1254 | } |
@@ -2111,6 +2157,30 @@ fn $0fun_name(n: i32) -> i32 { | |||
2111 | } | 2157 | } |
2112 | 2158 | ||
2113 | #[test] | 2159 | #[test] |
2160 | fn variable_defined_inside_and_used_after_mutably_no_ret() { | ||
2161 | check_assist( | ||
2162 | extract_function, | ||
2163 | r" | ||
2164 | fn foo() { | ||
2165 | let n = 1; | ||
2166 | $0let mut k = n * n;$0 | ||
2167 | k += 1; | ||
2168 | }", | ||
2169 | r" | ||
2170 | fn foo() { | ||
2171 | let n = 1; | ||
2172 | let mut k = fun_name(n); | ||
2173 | k += 1; | ||
2174 | } | ||
2175 | |||
2176 | fn $0fun_name(n: i32) -> i32 { | ||
2177 | let mut k = n * n; | ||
2178 | k | ||
2179 | }", | ||
2180 | ); | ||
2181 | } | ||
2182 | |||
2183 | #[test] | ||
2114 | fn two_variables_defined_inside_and_used_after_no_ret() { | 2184 | fn two_variables_defined_inside_and_used_after_no_ret() { |
2115 | check_assist( | 2185 | check_assist( |
2116 | extract_function, | 2186 | extract_function, |
@@ -2137,6 +2207,38 @@ fn $0fun_name(n: i32) -> (i32, i32) { | |||
2137 | } | 2207 | } |
2138 | 2208 | ||
2139 | #[test] | 2209 | #[test] |
2210 | fn multi_variables_defined_inside_and_used_after_mutably_no_ret() { | ||
2211 | check_assist( | ||
2212 | extract_function, | ||
2213 | r" | ||
2214 | fn foo() { | ||
2215 | let n = 1; | ||
2216 | $0let mut k = n * n; | ||
2217 | let mut m = k + 2; | ||
2218 | let mut o = m + 3; | ||
2219 | o += 1;$0 | ||
2220 | k += o; | ||
2221 | m = 1; | ||
2222 | }", | ||
2223 | r" | ||
2224 | fn foo() { | ||
2225 | let n = 1; | ||
2226 | let (mut k, mut m, o) = fun_name(n); | ||
2227 | k += o; | ||
2228 | m = 1; | ||
2229 | } | ||
2230 | |||
2231 | fn $0fun_name(n: i32) -> (i32, i32, i32) { | ||
2232 | let mut k = n * n; | ||
2233 | let mut m = k + 2; | ||
2234 | let mut o = m + 3; | ||
2235 | o += 1; | ||
2236 | (k, m, o) | ||
2237 | }", | ||
2238 | ); | ||
2239 | } | ||
2240 | |||
2241 | #[test] | ||
2140 | fn nontrivial_patterns_define_variables() { | 2242 | fn nontrivial_patterns_define_variables() { |
2141 | check_assist( | 2243 | check_assist( |
2142 | extract_function, | 2244 | extract_function, |
@@ -3372,4 +3474,36 @@ fn foo() -> Result<(), i64> { | |||
3372 | }"##, | 3474 | }"##, |
3373 | ); | 3475 | ); |
3374 | } | 3476 | } |
3477 | |||
3478 | #[test] | ||
3479 | fn param_usage_in_macro() { | ||
3480 | check_assist( | ||
3481 | extract_function, | ||
3482 | r" | ||
3483 | macro_rules! m { | ||
3484 | ($val:expr) => { $val }; | ||
3485 | } | ||
3486 | |||
3487 | fn foo() { | ||
3488 | let n = 1; | ||
3489 | $0let k = n * m!(n);$0 | ||
3490 | let m = k + 1; | ||
3491 | }", | ||
3492 | r" | ||
3493 | macro_rules! m { | ||
3494 | ($val:expr) => { $val }; | ||
3495 | } | ||
3496 | |||
3497 | fn foo() { | ||
3498 | let n = 1; | ||
3499 | let k = fun_name(n); | ||
3500 | let m = k + 1; | ||
3501 | } | ||
3502 | |||
3503 | fn $0fun_name(n: i32) -> i32 { | ||
3504 | let k = n * m!(n); | ||
3505 | k | ||
3506 | }", | ||
3507 | ); | ||
3508 | } | ||
3375 | } | 3509 | } |
diff --git a/crates/ide_assists/src/handlers/extract_type_alias.rs b/crates/ide_assists/src/handlers/extract_type_alias.rs new file mode 100644 index 000000000..442a209b9 --- /dev/null +++ b/crates/ide_assists/src/handlers/extract_type_alias.rs | |||
@@ -0,0 +1,149 @@ | |||
1 | use syntax::ast::{self, AstNode}; | ||
2 | |||
3 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | ||
4 | |||
5 | // Assist: extract_type_alias | ||
6 | // | ||
7 | // Extracts the selected type as a type alias. | ||
8 | // | ||
9 | // ``` | ||
10 | // struct S { | ||
11 | // field: $0(u8, u8, u8)$0, | ||
12 | // } | ||
13 | // ``` | ||
14 | // -> | ||
15 | // ``` | ||
16 | // type $0Type = (u8, u8, u8); | ||
17 | // | ||
18 | // struct S { | ||
19 | // field: Type, | ||
20 | // } | ||
21 | // ``` | ||
22 | pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
23 | if ctx.frange.range.is_empty() { | ||
24 | return None; | ||
25 | } | ||
26 | |||
27 | let node = ctx.find_node_at_range::<ast::Type>()?; | ||
28 | let insert = ctx.find_node_at_offset::<ast::Item>()?.syntax().text_range().start(); | ||
29 | let target = node.syntax().text_range(); | ||
30 | |||
31 | acc.add( | ||
32 | AssistId("extract_type_alias", AssistKind::RefactorExtract), | ||
33 | "Extract type as type alias", | ||
34 | target, | ||
35 | |builder| { | ||
36 | builder.edit_file(ctx.frange.file_id); | ||
37 | builder.replace(target, "Type"); | ||
38 | match ctx.config.snippet_cap { | ||
39 | Some(cap) => { | ||
40 | builder.insert_snippet(cap, insert, format!("type $0Type = {};\n\n", node)); | ||
41 | } | ||
42 | None => { | ||
43 | builder.insert(insert, format!("type Type = {};\n\n", node)); | ||
44 | } | ||
45 | } | ||
46 | }, | ||
47 | ) | ||
48 | } | ||
49 | |||
50 | #[cfg(test)] | ||
51 | mod tests { | ||
52 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
53 | |||
54 | use super::*; | ||
55 | |||
56 | #[test] | ||
57 | fn test_not_applicable_without_selection() { | ||
58 | check_assist_not_applicable( | ||
59 | extract_type_alias, | ||
60 | r" | ||
61 | struct S { | ||
62 | field: $0(u8, u8, u8), | ||
63 | } | ||
64 | ", | ||
65 | ); | ||
66 | } | ||
67 | |||
68 | #[test] | ||
69 | fn test_simple_types() { | ||
70 | check_assist( | ||
71 | extract_type_alias, | ||
72 | r" | ||
73 | struct S { | ||
74 | field: $0u8$0, | ||
75 | } | ||
76 | ", | ||
77 | r#" | ||
78 | type $0Type = u8; | ||
79 | |||
80 | struct S { | ||
81 | field: Type, | ||
82 | } | ||
83 | "#, | ||
84 | ); | ||
85 | } | ||
86 | |||
87 | #[test] | ||
88 | fn test_generic_type_arg() { | ||
89 | check_assist( | ||
90 | extract_type_alias, | ||
91 | r" | ||
92 | fn generic<T>() {} | ||
93 | |||
94 | fn f() { | ||
95 | generic::<$0()$0>(); | ||
96 | } | ||
97 | ", | ||
98 | r#" | ||
99 | fn generic<T>() {} | ||
100 | |||
101 | type $0Type = (); | ||
102 | |||
103 | fn f() { | ||
104 | generic::<Type>(); | ||
105 | } | ||
106 | "#, | ||
107 | ); | ||
108 | } | ||
109 | |||
110 | #[test] | ||
111 | fn test_inner_type_arg() { | ||
112 | check_assist( | ||
113 | extract_type_alias, | ||
114 | r" | ||
115 | struct Vec<T> {} | ||
116 | struct S { | ||
117 | v: Vec<Vec<$0Vec<u8>$0>>, | ||
118 | } | ||
119 | ", | ||
120 | r#" | ||
121 | struct Vec<T> {} | ||
122 | type $0Type = Vec<u8>; | ||
123 | |||
124 | struct S { | ||
125 | v: Vec<Vec<Type>>, | ||
126 | } | ||
127 | "#, | ||
128 | ); | ||
129 | } | ||
130 | |||
131 | #[test] | ||
132 | fn test_extract_inner_type() { | ||
133 | check_assist( | ||
134 | extract_type_alias, | ||
135 | r" | ||
136 | struct S { | ||
137 | field: ($0u8$0,), | ||
138 | } | ||
139 | ", | ||
140 | r#" | ||
141 | type $0Type = u8; | ||
142 | |||
143 | struct S { | ||
144 | field: (Type,), | ||
145 | } | ||
146 | "#, | ||
147 | ); | ||
148 | } | ||
149 | } | ||
diff --git a/crates/ide_assists/src/handlers/extract_variable.rs b/crates/ide_assists/src/handlers/extract_variable.rs index 7a32483dc..136b9a55b 100644 --- a/crates/ide_assists/src/handlers/extract_variable.rs +++ b/crates/ide_assists/src/handlers/extract_variable.rs | |||
@@ -2,7 +2,8 @@ use stdx::format_to; | |||
2 | use syntax::{ | 2 | use syntax::{ |
3 | ast::{self, AstNode}, | 3 | ast::{self, AstNode}, |
4 | SyntaxKind::{ | 4 | SyntaxKind::{ |
5 | BLOCK_EXPR, BREAK_EXPR, CLOSURE_EXPR, COMMENT, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, | 5 | BLOCK_EXPR, BREAK_EXPR, CLOSURE_EXPR, COMMENT, LOOP_EXPR, MATCH_ARM, MATCH_GUARD, |
6 | PATH_EXPR, RETURN_EXPR, | ||
6 | }, | 7 | }, |
7 | SyntaxNode, | 8 | SyntaxNode, |
8 | }; | 9 | }; |
@@ -147,9 +148,18 @@ impl Anchor { | |||
147 | } | 148 | } |
148 | 149 | ||
149 | if let Some(parent) = node.parent() { | 150 | if let Some(parent) = node.parent() { |
150 | if parent.kind() == MATCH_ARM || parent.kind() == CLOSURE_EXPR { | 151 | if parent.kind() == CLOSURE_EXPR { |
152 | cov_mark::hit!(test_extract_var_in_closure_no_block); | ||
151 | return Some(Anchor::WrapInBlock(node)); | 153 | return Some(Anchor::WrapInBlock(node)); |
152 | } | 154 | } |
155 | if parent.kind() == MATCH_ARM { | ||
156 | if node.kind() == MATCH_GUARD { | ||
157 | cov_mark::hit!(test_extract_var_in_match_guard); | ||
158 | } else { | ||
159 | cov_mark::hit!(test_extract_var_in_match_arm_no_block); | ||
160 | return Some(Anchor::WrapInBlock(node)); | ||
161 | } | ||
162 | } | ||
153 | } | 163 | } |
154 | 164 | ||
155 | if let Some(stmt) = ast::Stmt::cast(node.clone()) { | 165 | if let Some(stmt) = ast::Stmt::cast(node.clone()) { |
@@ -280,9 +290,10 @@ fn foo() { | |||
280 | 290 | ||
281 | #[test] | 291 | #[test] |
282 | fn test_extract_var_in_match_arm_no_block() { | 292 | fn test_extract_var_in_match_arm_no_block() { |
293 | cov_mark::check!(test_extract_var_in_match_arm_no_block); | ||
283 | check_assist( | 294 | check_assist( |
284 | extract_variable, | 295 | extract_variable, |
285 | " | 296 | r#" |
286 | fn main() { | 297 | fn main() { |
287 | let x = true; | 298 | let x = true; |
288 | let tuple = match x { | 299 | let tuple = match x { |
@@ -290,8 +301,8 @@ fn main() { | |||
290 | _ => (0, false) | 301 | _ => (0, false) |
291 | }; | 302 | }; |
292 | } | 303 | } |
293 | ", | 304 | "#, |
294 | " | 305 | r#" |
295 | fn main() { | 306 | fn main() { |
296 | let x = true; | 307 | let x = true; |
297 | let tuple = match x { | 308 | let tuple = match x { |
@@ -299,7 +310,7 @@ fn main() { | |||
299 | _ => (0, false) | 310 | _ => (0, false) |
300 | }; | 311 | }; |
301 | } | 312 | } |
302 | ", | 313 | "#, |
303 | ); | 314 | ); |
304 | } | 315 | } |
305 | 316 | ||
@@ -307,7 +318,7 @@ fn main() { | |||
307 | fn test_extract_var_in_match_arm_with_block() { | 318 | fn test_extract_var_in_match_arm_with_block() { |
308 | check_assist( | 319 | check_assist( |
309 | extract_variable, | 320 | extract_variable, |
310 | " | 321 | r#" |
311 | fn main() { | 322 | fn main() { |
312 | let x = true; | 323 | let x = true; |
313 | let tuple = match x { | 324 | let tuple = match x { |
@@ -318,8 +329,8 @@ fn main() { | |||
318 | _ => (0, false) | 329 | _ => (0, false) |
319 | }; | 330 | }; |
320 | } | 331 | } |
321 | ", | 332 | "#, |
322 | " | 333 | r#" |
323 | fn main() { | 334 | fn main() { |
324 | let x = true; | 335 | let x = true; |
325 | let tuple = match x { | 336 | let tuple = match x { |
@@ -331,24 +342,50 @@ fn main() { | |||
331 | _ => (0, false) | 342 | _ => (0, false) |
332 | }; | 343 | }; |
333 | } | 344 | } |
334 | ", | 345 | "#, |
346 | ); | ||
347 | } | ||
348 | |||
349 | #[test] | ||
350 | fn test_extract_var_in_match_guard() { | ||
351 | cov_mark::check!(test_extract_var_in_match_guard); | ||
352 | check_assist( | ||
353 | extract_variable, | ||
354 | r#" | ||
355 | fn main() { | ||
356 | match () { | ||
357 | () if $010 > 0$0 => 1 | ||
358 | _ => 2 | ||
359 | }; | ||
360 | } | ||
361 | "#, | ||
362 | r#" | ||
363 | fn main() { | ||
364 | let $0var_name = 10 > 0; | ||
365 | match () { | ||
366 | () if var_name => 1 | ||
367 | _ => 2 | ||
368 | }; | ||
369 | } | ||
370 | "#, | ||
335 | ); | 371 | ); |
336 | } | 372 | } |
337 | 373 | ||
338 | #[test] | 374 | #[test] |
339 | fn test_extract_var_in_closure_no_block() { | 375 | fn test_extract_var_in_closure_no_block() { |
376 | cov_mark::check!(test_extract_var_in_closure_no_block); | ||
340 | check_assist( | 377 | check_assist( |
341 | extract_variable, | 378 | extract_variable, |
342 | " | 379 | r#" |
343 | fn main() { | 380 | fn main() { |
344 | let lambda = |x: u32| $0x * 2$0; | 381 | let lambda = |x: u32| $0x * 2$0; |
345 | } | 382 | } |
346 | ", | 383 | "#, |
347 | " | 384 | r#" |
348 | fn main() { | 385 | fn main() { |
349 | let lambda = |x: u32| { let $0var_name = x * 2; var_name }; | 386 | let lambda = |x: u32| { let $0var_name = x * 2; var_name }; |
350 | } | 387 | } |
351 | ", | 388 | "#, |
352 | ); | 389 | ); |
353 | } | 390 | } |
354 | 391 | ||
@@ -356,16 +393,16 @@ fn main() { | |||
356 | fn test_extract_var_in_closure_with_block() { | 393 | fn test_extract_var_in_closure_with_block() { |
357 | check_assist( | 394 | check_assist( |
358 | extract_variable, | 395 | extract_variable, |
359 | " | 396 | r#" |
360 | fn main() { | 397 | fn main() { |
361 | let lambda = |x: u32| { $0x * 2$0 }; | 398 | let lambda = |x: u32| { $0x * 2$0 }; |
362 | } | 399 | } |
363 | ", | 400 | "#, |
364 | " | 401 | r#" |
365 | fn main() { | 402 | fn main() { |
366 | let lambda = |x: u32| { let $0var_name = x * 2; var_name }; | 403 | let lambda = |x: u32| { let $0var_name = x * 2; var_name }; |
367 | } | 404 | } |
368 | ", | 405 | "#, |
369 | ); | 406 | ); |
370 | } | 407 | } |
371 | 408 | ||
diff --git a/crates/ide_assists/src/handlers/fill_match_arms.rs b/crates/ide_assists/src/handlers/fill_match_arms.rs index 878b3a3fa..80bd1b7e8 100644 --- a/crates/ide_assists/src/handlers/fill_match_arms.rs +++ b/crates/ide_assists/src/handlers/fill_match_arms.rs | |||
@@ -71,12 +71,6 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
71 | return None; | 71 | return None; |
72 | } | 72 | } |
73 | 73 | ||
74 | // We do not currently support filling match arms for a tuple | ||
75 | // containing a single enum. | ||
76 | if enum_defs.len() < 2 { | ||
77 | return None; | ||
78 | } | ||
79 | |||
80 | // When calculating the match arms for a tuple of enums, we want | 74 | // When calculating the match arms for a tuple of enums, we want |
81 | // to create a match arm for each possible combination of enum | 75 | // to create a match arm for each possible combination of enum |
82 | // values. The `multi_cartesian_product` method transforms | 76 | // values. The `multi_cartesian_product` method transforms |
@@ -514,10 +508,7 @@ fn main() { | |||
514 | 508 | ||
515 | #[test] | 509 | #[test] |
516 | fn fill_match_arms_single_element_tuple_of_enum() { | 510 | fn fill_match_arms_single_element_tuple_of_enum() { |
517 | // For now we don't hande the case of a single element tuple, but | 511 | check_assist( |
518 | // we could handle this in the future if `make::tuple_pat` allowed | ||
519 | // creating a tuple with a single pattern. | ||
520 | check_assist_not_applicable( | ||
521 | fill_match_arms, | 512 | fill_match_arms, |
522 | r#" | 513 | r#" |
523 | enum A { One, Two } | 514 | enum A { One, Two } |
@@ -528,6 +519,17 @@ fn main() { | |||
528 | } | 519 | } |
529 | } | 520 | } |
530 | "#, | 521 | "#, |
522 | r#" | ||
523 | enum A { One, Two } | ||
524 | |||
525 | fn main() { | ||
526 | let a = A::One; | ||
527 | match (a, ) { | ||
528 | $0(A::One,) => {} | ||
529 | (A::Two,) => {} | ||
530 | } | ||
531 | } | ||
532 | "#, | ||
531 | ); | 533 | ); |
532 | } | 534 | } |
533 | 535 | ||
diff --git a/crates/ide_assists/src/handlers/flip_comma.rs b/crates/ide_assists/src/handlers/flip_comma.rs index 7f5e75d34..99be5e090 100644 --- a/crates/ide_assists/src/handlers/flip_comma.rs +++ b/crates/ide_assists/src/handlers/flip_comma.rs | |||
@@ -66,26 +66,12 @@ mod tests { | |||
66 | } | 66 | } |
67 | 67 | ||
68 | #[test] | 68 | #[test] |
69 | #[should_panic] | ||
70 | fn flip_comma_before_punct() { | 69 | fn flip_comma_before_punct() { |
71 | // See https://github.com/rust-analyzer/rust-analyzer/issues/1619 | 70 | // See https://github.com/rust-analyzer/rust-analyzer/issues/1619 |
72 | // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct | 71 | // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct |
73 | // declaration body. | 72 | // declaration body. |
74 | check_assist_target( | 73 | check_assist_not_applicable(flip_comma, "pub enum Test { A,$0 }"); |
75 | flip_comma, | 74 | check_assist_not_applicable(flip_comma, "pub struct Test { foo: usize,$0 }"); |
76 | "pub enum Test { \ | ||
77 | A,$0 \ | ||
78 | }", | ||
79 | ",", | ||
80 | ); | ||
81 | |||
82 | check_assist_target( | ||
83 | flip_comma, | ||
84 | "pub struct Test { \ | ||
85 | foo: usize,$0 \ | ||
86 | }", | ||
87 | ",", | ||
88 | ); | ||
89 | } | 75 | } |
90 | 76 | ||
91 | #[test] | 77 | #[test] |
diff --git a/crates/ide_assists/src/handlers/generate_default_from_new.rs b/crates/ide_assists/src/handlers/generate_default_from_new.rs index 81c54ba3e..dc14552d6 100644 --- a/crates/ide_assists/src/handlers/generate_default_from_new.rs +++ b/crates/ide_assists/src/handlers/generate_default_from_new.rs | |||
@@ -92,7 +92,7 @@ fn is_default_implemented(ctx: &AssistContext, impl_: &Impl) -> bool { | |||
92 | None => return false, | 92 | None => return false, |
93 | }; | 93 | }; |
94 | 94 | ||
95 | let ty = impl_def.target_ty(db); | 95 | let ty = impl_def.self_ty(db); |
96 | let krate = impl_def.module(db).krate(); | 96 | let krate = impl_def.module(db).krate(); |
97 | let default = FamousDefs(&ctx.sema, Some(krate)).core_default_Default(); | 97 | let default = FamousDefs(&ctx.sema, Some(krate)).core_default_Default(); |
98 | let default_trait = match default { | 98 | let default_trait = match default { |
diff --git a/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs b/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs index b8834d283..910010a04 100644 --- a/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs +++ b/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs | |||
@@ -91,7 +91,7 @@ fn get_impl_method( | |||
91 | 91 | ||
92 | let scope = ctx.sema.scope(impl_.syntax()); | 92 | let scope = ctx.sema.scope(impl_.syntax()); |
93 | let krate = impl_def.module(db).krate(); | 93 | let krate = impl_def.module(db).krate(); |
94 | let ty = impl_def.target_ty(db); | 94 | let ty = impl_def.self_ty(db); |
95 | let traits_in_scope = scope.traits_in_scope(); | 95 | let traits_in_scope = scope.traits_in_scope(); |
96 | ty.iterate_method_candidates(db, krate, &traits_in_scope, Some(fn_name), |_, func| Some(func)) | 96 | ty.iterate_method_candidates(db, krate, &traits_in_scope, Some(fn_name), |_, func| Some(func)) |
97 | } | 97 | } |
diff --git a/crates/ide_assists/src/handlers/remove_dbg.rs b/crates/ide_assists/src/handlers/remove_dbg.rs index 6114091f2..c8226550f 100644 --- a/crates/ide_assists/src/handlers/remove_dbg.rs +++ b/crates/ide_assists/src/handlers/remove_dbg.rs | |||
@@ -1,5 +1,5 @@ | |||
1 | use syntax::{ | 1 | use syntax::{ |
2 | ast::{self, AstNode}, | 2 | ast::{self, AstNode, AstToken}, |
3 | match_ast, SyntaxElement, TextRange, TextSize, T, | 3 | match_ast, SyntaxElement, TextRange, TextSize, T, |
4 | }; | 4 | }; |
5 | 5 | ||
@@ -24,7 +24,39 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
24 | let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; | 24 | let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; |
25 | let new_contents = adjusted_macro_contents(¯o_call)?; | 25 | let new_contents = adjusted_macro_contents(¯o_call)?; |
26 | 26 | ||
27 | let macro_text_range = macro_call.syntax().text_range(); | 27 | let parent = macro_call.syntax().parent(); |
28 | |||
29 | let macro_text_range = if let Some(it) = parent.as_ref() { | ||
30 | if new_contents.is_empty() { | ||
31 | match_ast! { | ||
32 | match it { | ||
33 | ast::BlockExpr(_it) => { | ||
34 | macro_call.syntax() | ||
35 | .prev_sibling_or_token() | ||
36 | .and_then(whitespace_start) | ||
37 | .map(|start| TextRange::new(start, macro_call.syntax().text_range().end())) | ||
38 | .unwrap_or(macro_call.syntax().text_range()) | ||
39 | }, | ||
40 | ast::ExprStmt(it) => { | ||
41 | let start = it | ||
42 | .syntax() | ||
43 | .prev_sibling_or_token() | ||
44 | .and_then(whitespace_start) | ||
45 | .unwrap_or(it.syntax().text_range().start()); | ||
46 | let end = it.syntax().text_range().end(); | ||
47 | |||
48 | TextRange::new(start, end) | ||
49 | }, | ||
50 | _ => macro_call.syntax().text_range() | ||
51 | } | ||
52 | } | ||
53 | } else { | ||
54 | macro_call.syntax().text_range() | ||
55 | } | ||
56 | } else { | ||
57 | macro_call.syntax().text_range() | ||
58 | }; | ||
59 | |||
28 | let macro_end = if macro_call.semicolon_token().is_some() { | 60 | let macro_end = if macro_call.semicolon_token().is_some() { |
29 | macro_text_range.end() - TextSize::of(';') | 61 | macro_text_range.end() - TextSize::of(';') |
30 | } else { | 62 | } else { |
@@ -36,11 +68,22 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
36 | "Remove dbg!()", | 68 | "Remove dbg!()", |
37 | macro_text_range, | 69 | macro_text_range, |
38 | |builder| { | 70 | |builder| { |
39 | builder.replace(TextRange::new(macro_text_range.start(), macro_end), new_contents); | 71 | builder.replace( |
72 | TextRange::new(macro_text_range.start(), macro_end), | ||
73 | if new_contents.is_empty() && parent.and_then(ast::LetStmt::cast).is_some() { | ||
74 | ast::make::expr_unit().to_string() | ||
75 | } else { | ||
76 | new_contents | ||
77 | }, | ||
78 | ); | ||
40 | }, | 79 | }, |
41 | ) | 80 | ) |
42 | } | 81 | } |
43 | 82 | ||
83 | fn whitespace_start(it: SyntaxElement) -> Option<TextSize> { | ||
84 | Some(it.into_token().and_then(ast::Whitespace::cast)?.syntax().text_range().start()) | ||
85 | } | ||
86 | |||
44 | fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> { | 87 | fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> { |
45 | let contents = get_valid_macrocall_contents(¯o_call, "dbg")?; | 88 | let contents = get_valid_macrocall_contents(¯o_call, "dbg")?; |
46 | let macro_text_with_brackets = macro_call.token_tree()?.syntax().text(); | 89 | let macro_text_with_brackets = macro_call.token_tree()?.syntax().text(); |
@@ -94,15 +137,11 @@ fn get_valid_macrocall_contents( | |||
94 | let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>(); | 137 | let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>(); |
95 | let last_child = contents_between_brackets.pop()?; | 138 | let last_child = contents_between_brackets.pop()?; |
96 | 139 | ||
97 | if contents_between_brackets.is_empty() { | 140 | match (first_child.kind(), last_child.kind()) { |
98 | None | 141 | (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => { |
99 | } else { | 142 | Some(contents_between_brackets) |
100 | match (first_child.kind(), last_child.kind()) { | ||
101 | (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => { | ||
102 | Some(contents_between_brackets) | ||
103 | } | ||
104 | _ => None, | ||
105 | } | 143 | } |
144 | _ => None, | ||
106 | } | 145 | } |
107 | } | 146 | } |
108 | 147 | ||
@@ -418,4 +457,48 @@ fn main() { | |||
418 | }"#, | 457 | }"#, |
419 | ); | 458 | ); |
420 | } | 459 | } |
460 | |||
461 | #[test] | ||
462 | fn test_remove_empty_dbg() { | ||
463 | check_assist(remove_dbg, r#"fn foo() { $0dbg!(); }"#, r#"fn foo() { }"#); | ||
464 | check_assist( | ||
465 | remove_dbg, | ||
466 | r#" | ||
467 | fn foo() { | ||
468 | $0dbg!(); | ||
469 | } | ||
470 | "#, | ||
471 | r#" | ||
472 | fn foo() { | ||
473 | } | ||
474 | "#, | ||
475 | ); | ||
476 | check_assist( | ||
477 | remove_dbg, | ||
478 | r#" | ||
479 | fn foo() { | ||
480 | let test = $0dbg!(); | ||
481 | }"#, | ||
482 | r#" | ||
483 | fn foo() { | ||
484 | let test = (); | ||
485 | }"#, | ||
486 | ); | ||
487 | check_assist( | ||
488 | remove_dbg, | ||
489 | r#" | ||
490 | fn foo() { | ||
491 | let t = { | ||
492 | println!("Hello, world"); | ||
493 | $0dbg!() | ||
494 | }; | ||
495 | }"#, | ||
496 | r#" | ||
497 | fn foo() { | ||
498 | let t = { | ||
499 | println!("Hello, world"); | ||
500 | }; | ||
501 | }"#, | ||
502 | ); | ||
503 | } | ||
421 | } | 504 | } |
diff --git a/crates/ide_assists/src/handlers/reorder_fields.rs b/crates/ide_assists/src/handlers/reorder_fields.rs index 383ca6c47..1a95135ca 100644 --- a/crates/ide_assists/src/handlers/reorder_fields.rs +++ b/crates/ide_assists/src/handlers/reorder_fields.rs | |||
@@ -1,6 +1,8 @@ | |||
1 | use either::Either; | ||
2 | use itertools::Itertools; | ||
1 | use rustc_hash::FxHashMap; | 3 | use rustc_hash::FxHashMap; |
2 | 4 | ||
3 | use syntax::{algo, ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode}; | 5 | use syntax::{ast, ted, AstNode}; |
4 | 6 | ||
5 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 7 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
6 | 8 | ||
@@ -22,60 +24,70 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; | |||
22 | pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 24 | pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
23 | let record = ctx | 25 | let record = ctx |
24 | .find_node_at_offset::<ast::RecordExpr>() | 26 | .find_node_at_offset::<ast::RecordExpr>() |
25 | .map(|it| it.syntax().clone()) | 27 | .map(Either::Left) |
26 | .or_else(|| ctx.find_node_at_offset::<ast::RecordPat>().map(|it| it.syntax().clone()))?; | 28 | .or_else(|| ctx.find_node_at_offset::<ast::RecordPat>().map(Either::Right))?; |
27 | |||
28 | let path = record.children().find_map(ast::Path::cast)?; | ||
29 | 29 | ||
30 | let path = record.as_ref().either(|it| it.path(), |it| it.path())?; | ||
30 | let ranks = compute_fields_ranks(&path, &ctx)?; | 31 | let ranks = compute_fields_ranks(&path, &ctx)?; |
32 | let get_rank_of_field = | ||
33 | |of: Option<_>| *ranks.get(&of.unwrap_or_default()).unwrap_or(&usize::MAX); | ||
31 | 34 | ||
32 | let fields: Vec<SyntaxNode> = { | 35 | let field_list = match &record { |
33 | let field_kind = match record.kind() { | 36 | Either::Left(it) => Either::Left(it.record_expr_field_list()?), |
34 | RECORD_EXPR => RECORD_EXPR_FIELD, | 37 | Either::Right(it) => Either::Right(it.record_pat_field_list()?), |
35 | RECORD_PAT => RECORD_PAT_FIELD, | ||
36 | _ => { | ||
37 | stdx::never!(); | ||
38 | return None; | ||
39 | } | ||
40 | }; | ||
41 | record.children().flat_map(|n| n.children()).filter(|n| n.kind() == field_kind).collect() | ||
42 | }; | 38 | }; |
43 | 39 | let fields = match field_list { | |
44 | let sorted_fields = { | 40 | Either::Left(it) => Either::Left(( |
45 | let mut fields = fields.clone(); | 41 | it.fields() |
46 | fields.sort_by_key(|node| *ranks.get(&get_field_name(node)).unwrap_or(&usize::max_value())); | 42 | .sorted_unstable_by_key(|field| { |
47 | fields | 43 | get_rank_of_field(field.field_name().map(|it| it.to_string())) |
44 | }) | ||
45 | .collect::<Vec<_>>(), | ||
46 | it, | ||
47 | )), | ||
48 | Either::Right(it) => Either::Right(( | ||
49 | it.fields() | ||
50 | .sorted_unstable_by_key(|field| { | ||
51 | get_rank_of_field(field.field_name().map(|it| it.to_string())) | ||
52 | }) | ||
53 | .collect::<Vec<_>>(), | ||
54 | it, | ||
55 | )), | ||
48 | }; | 56 | }; |
49 | 57 | ||
50 | if sorted_fields == fields { | 58 | let is_sorted = fields.as_ref().either( |
59 | |(sorted, field_list)| field_list.fields().zip(sorted).all(|(a, b)| a == *b), | ||
60 | |(sorted, field_list)| field_list.fields().zip(sorted).all(|(a, b)| a == *b), | ||
61 | ); | ||
62 | if is_sorted { | ||
51 | cov_mark::hit!(reorder_sorted_fields); | 63 | cov_mark::hit!(reorder_sorted_fields); |
52 | return None; | 64 | return None; |
53 | } | 65 | } |
54 | 66 | let target = record.as_ref().either(AstNode::syntax, AstNode::syntax).text_range(); | |
55 | let target = record.text_range(); | ||
56 | acc.add( | 67 | acc.add( |
57 | AssistId("reorder_fields", AssistKind::RefactorRewrite), | 68 | AssistId("reorder_fields", AssistKind::RefactorRewrite), |
58 | "Reorder record fields", | 69 | "Reorder record fields", |
59 | target, | 70 | target, |
60 | |edit| { | 71 | |builder| match fields { |
61 | let mut rewriter = algo::SyntaxRewriter::default(); | 72 | Either::Left((sorted, field_list)) => { |
62 | for (old, new) in fields.iter().zip(&sorted_fields) { | 73 | replace(builder.make_ast_mut(field_list).fields(), sorted) |
63 | rewriter.replace(old, new); | 74 | } |
75 | Either::Right((sorted, field_list)) => { | ||
76 | replace(builder.make_ast_mut(field_list).fields(), sorted) | ||
64 | } | 77 | } |
65 | edit.rewrite(rewriter); | ||
66 | }, | 78 | }, |
67 | ) | 79 | ) |
68 | } | 80 | } |
69 | 81 | ||
70 | fn get_field_name(node: &SyntaxNode) -> String { | 82 | fn replace<T: AstNode + PartialEq>( |
71 | let res = match_ast! { | 83 | fields: impl Iterator<Item = T>, |
72 | match node { | 84 | sorted_fields: impl IntoIterator<Item = T>, |
73 | ast::RecordExprField(field) => field.field_name().map(|it| it.to_string()), | 85 | ) { |
74 | ast::RecordPatField(field) => field.field_name().map(|it| it.to_string()), | 86 | fields.zip(sorted_fields).filter(|(field, sorted)| field != sorted).for_each( |
75 | _ => None, | 87 | |(field, sorted_field)| { |
76 | } | 88 | ted::replace(field.syntax(), sorted_field.syntax().clone_for_update()); |
77 | }; | 89 | }, |
78 | res.unwrap_or_default() | 90 | ); |
79 | } | 91 | } |
80 | 92 | ||
81 | fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { | 93 | fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { |
@@ -86,7 +98,7 @@ fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashM | |||
86 | 98 | ||
87 | let res = strukt | 99 | let res = strukt |
88 | .fields(ctx.db()) | 100 | .fields(ctx.db()) |
89 | .iter() | 101 | .into_iter() |
90 | .enumerate() | 102 | .enumerate() |
91 | .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx)) | 103 | .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx)) |
92 | .collect(); | 104 | .collect(); |
@@ -137,7 +149,6 @@ const test: Foo = Foo { foo: 1, bar: 0 }; | |||
137 | "#, | 149 | "#, |
138 | ) | 150 | ) |
139 | } | 151 | } |
140 | |||
141 | #[test] | 152 | #[test] |
142 | fn reorder_struct_pattern() { | 153 | fn reorder_struct_pattern() { |
143 | check_assist( | 154 | check_assist( |
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 8c068a6c0..3694f468f 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -28,7 +28,9 @@ pub use assist_config::AssistConfig; | |||
28 | 28 | ||
29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
30 | pub enum AssistKind { | 30 | pub enum AssistKind { |
31 | // FIXME: does the None variant make sense? Probably not. | ||
31 | None, | 32 | None, |
33 | |||
32 | QuickFix, | 34 | QuickFix, |
33 | Generate, | 35 | Generate, |
34 | Refactor, | 36 | Refactor, |
@@ -117,10 +119,12 @@ mod handlers { | |||
117 | mod convert_integer_literal; | 119 | mod convert_integer_literal; |
118 | mod convert_comment_block; | 120 | mod convert_comment_block; |
119 | mod convert_iter_for_each_to_for; | 121 | mod convert_iter_for_each_to_for; |
122 | mod convert_into_to_from; | ||
120 | mod early_return; | 123 | mod early_return; |
121 | mod expand_glob_import; | 124 | mod expand_glob_import; |
122 | mod extract_function; | 125 | mod extract_function; |
123 | mod extract_struct_from_enum_variant; | 126 | mod extract_struct_from_enum_variant; |
127 | mod extract_type_alias; | ||
124 | mod extract_variable; | 128 | mod extract_variable; |
125 | mod fill_match_arms; | 129 | mod fill_match_arms; |
126 | mod fix_visibility; | 130 | mod fix_visibility; |
@@ -184,9 +188,11 @@ mod handlers { | |||
184 | convert_integer_literal::convert_integer_literal, | 188 | convert_integer_literal::convert_integer_literal, |
185 | convert_comment_block::convert_comment_block, | 189 | convert_comment_block::convert_comment_block, |
186 | convert_iter_for_each_to_for::convert_iter_for_each_to_for, | 190 | convert_iter_for_each_to_for::convert_iter_for_each_to_for, |
191 | convert_into_to_from::convert_into_to_from, | ||
187 | early_return::convert_to_guarded_return, | 192 | early_return::convert_to_guarded_return, |
188 | expand_glob_import::expand_glob_import, | 193 | expand_glob_import::expand_glob_import, |
189 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, | 194 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, |
195 | extract_type_alias::extract_type_alias, | ||
190 | fill_match_arms::fill_match_arms, | 196 | fill_match_arms::fill_match_arms, |
191 | fix_visibility::fix_visibility, | 197 | fix_visibility::fix_visibility, |
192 | flip_binexpr::flip_binexpr, | 198 | flip_binexpr::flip_binexpr, |
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs index a7a923beb..60e3a1474 100644 --- a/crates/ide_assists/src/tests.rs +++ b/crates/ide_assists/src/tests.rs | |||
@@ -84,7 +84,8 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { | |||
84 | }); | 84 | }); |
85 | 85 | ||
86 | let actual = { | 86 | let actual = { |
87 | let source_change = assist.source_change.unwrap(); | 87 | let source_change = |
88 | assist.source_change.expect("Assist did not contain any source changes"); | ||
88 | let mut actual = before; | 89 | let mut actual = before; |
89 | if let Some(source_file_edit) = source_change.get_source_edit(file_id) { | 90 | if let Some(source_file_edit) = source_change.get_source_edit(file_id) { |
90 | source_file_edit.apply(&mut actual); | 91 | source_file_edit.apply(&mut actual); |
@@ -121,7 +122,8 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: | |||
121 | 122 | ||
122 | match (assist, expected) { | 123 | match (assist, expected) { |
123 | (Some(assist), ExpectedResult::After(after)) => { | 124 | (Some(assist), ExpectedResult::After(after)) => { |
124 | let source_change = assist.source_change.unwrap(); | 125 | let source_change = |
126 | assist.source_change.expect("Assist did not contain any source changes"); | ||
125 | assert!(!source_change.source_file_edits.is_empty()); | 127 | assert!(!source_change.source_file_edits.is_empty()); |
126 | let skip_header = source_change.source_file_edits.len() == 1 | 128 | let skip_header = source_change.source_file_edits.len() == 1 |
127 | && source_change.file_system_edits.len() == 0; | 129 | && source_change.file_system_edits.len() == 0; |
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 736027ff0..27a22ca10 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs | |||
@@ -206,6 +206,38 @@ const _: i32 = 0b1010; | |||
206 | } | 206 | } |
207 | 207 | ||
208 | #[test] | 208 | #[test] |
209 | fn doctest_convert_into_to_from() { | ||
210 | check_doc_test( | ||
211 | "convert_into_to_from", | ||
212 | r#####" | ||
213 | //- /lib.rs crate:core | ||
214 | pub mod convert { pub trait Into<T> { pub fn into(self) -> T; } } | ||
215 | //- /lib.rs crate:main deps:core | ||
216 | use core::convert::Into; | ||
217 | impl $0Into<Thing> for usize { | ||
218 | fn into(self) -> Thing { | ||
219 | Thing { | ||
220 | b: self.to_string(), | ||
221 | a: self | ||
222 | } | ||
223 | } | ||
224 | } | ||
225 | "#####, | ||
226 | r#####" | ||
227 | use core::convert::Into; | ||
228 | impl From<usize> for Thing { | ||
229 | fn from(val: usize) -> Self { | ||
230 | Thing { | ||
231 | b: val.to_string(), | ||
232 | a: val | ||
233 | } | ||
234 | } | ||
235 | } | ||
236 | "#####, | ||
237 | ) | ||
238 | } | ||
239 | |||
240 | #[test] | ||
209 | fn doctest_convert_iter_for_each_to_for() { | 241 | fn doctest_convert_iter_for_each_to_for() { |
210 | check_doc_test( | 242 | check_doc_test( |
211 | "convert_iter_for_each_to_for", | 243 | "convert_iter_for_each_to_for", |
@@ -329,6 +361,25 @@ enum A { One(One) } | |||
329 | } | 361 | } |
330 | 362 | ||
331 | #[test] | 363 | #[test] |
364 | fn doctest_extract_type_alias() { | ||
365 | check_doc_test( | ||
366 | "extract_type_alias", | ||
367 | r#####" | ||
368 | struct S { | ||
369 | field: $0(u8, u8, u8)$0, | ||
370 | } | ||
371 | "#####, | ||
372 | r#####" | ||
373 | type $0Type = (u8, u8, u8); | ||
374 | |||
375 | struct S { | ||
376 | field: Type, | ||
377 | } | ||
378 | "#####, | ||
379 | ) | ||
380 | } | ||
381 | |||
382 | #[test] | ||
332 | fn doctest_extract_variable() { | 383 | fn doctest_extract_variable() { |
333 | check_doc_test( | 384 | check_doc_test( |
334 | "extract_variable", | 385 | "extract_variable", |
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs index 5f630ec75..d67524937 100644 --- a/crates/ide_assists/src/utils.rs +++ b/crates/ide_assists/src/utils.rs | |||
@@ -338,11 +338,11 @@ pub(crate) fn find_struct_impl( | |||
338 | // (we currently use the wrong type parameter) | 338 | // (we currently use the wrong type parameter) |
339 | // also we wouldn't want to use e.g. `impl S<u32>` | 339 | // also we wouldn't want to use e.g. `impl S<u32>` |
340 | 340 | ||
341 | let same_ty = match blk.target_ty(db).as_adt() { | 341 | let same_ty = match blk.self_ty(db).as_adt() { |
342 | Some(def) => def == struct_def, | 342 | Some(def) => def == struct_def, |
343 | None => false, | 343 | None => false, |
344 | }; | 344 | }; |
345 | let not_trait_impl = blk.target_trait(db).is_none(); | 345 | let not_trait_impl = blk.trait_(db).is_none(); |
346 | 346 | ||
347 | if !(same_ty && not_trait_impl) { | 347 | if !(same_ty && not_trait_impl) { |
348 | None | 348 | None |