diff options
Diffstat (limited to 'crates/ide_assists')
-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_type_alias.rs | 149 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/extract_variable.rs | 73 | ||||
-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/reorder_fields.rs | 89 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 4 | ||||
-rw-r--r-- | crates/ide_assists/src/tests/generated.rs | 51 | ||||
-rw-r--r-- | crates/ide_assists/src/utils.rs | 4 |
11 files changed, 674 insertions, 62 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_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/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/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..3e2c82dac 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -117,10 +117,12 @@ mod handlers { | |||
117 | mod convert_integer_literal; | 117 | mod convert_integer_literal; |
118 | mod convert_comment_block; | 118 | mod convert_comment_block; |
119 | mod convert_iter_for_each_to_for; | 119 | mod convert_iter_for_each_to_for; |
120 | mod convert_into_to_from; | ||
120 | mod early_return; | 121 | mod early_return; |
121 | mod expand_glob_import; | 122 | mod expand_glob_import; |
122 | mod extract_function; | 123 | mod extract_function; |
123 | mod extract_struct_from_enum_variant; | 124 | mod extract_struct_from_enum_variant; |
125 | mod extract_type_alias; | ||
124 | mod extract_variable; | 126 | mod extract_variable; |
125 | mod fill_match_arms; | 127 | mod fill_match_arms; |
126 | mod fix_visibility; | 128 | mod fix_visibility; |
@@ -184,9 +186,11 @@ mod handlers { | |||
184 | convert_integer_literal::convert_integer_literal, | 186 | convert_integer_literal::convert_integer_literal, |
185 | convert_comment_block::convert_comment_block, | 187 | convert_comment_block::convert_comment_block, |
186 | convert_iter_for_each_to_for::convert_iter_for_each_to_for, | 188 | convert_iter_for_each_to_for::convert_iter_for_each_to_for, |
189 | convert_into_to_from::convert_into_to_from, | ||
187 | early_return::convert_to_guarded_return, | 190 | early_return::convert_to_guarded_return, |
188 | expand_glob_import::expand_glob_import, | 191 | expand_glob_import::expand_glob_import, |
189 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, | 192 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, |
193 | extract_type_alias::extract_type_alias, | ||
190 | fill_match_arms::fill_match_arms, | 194 | fill_match_arms::fill_match_arms, |
191 | fix_visibility::fix_visibility, | 195 | fix_visibility::fix_visibility, |
192 | flip_binexpr::flip_binexpr, | 196 | flip_binexpr::flip_binexpr, |
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 |