diff options
Diffstat (limited to 'crates/ide_assists/src/handlers')
12 files changed, 634 insertions, 67 deletions
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_struct_from_enum_variant.rs b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs index 596c536a7..a8d6355bd 100644 --- a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs | |||
@@ -195,7 +195,7 @@ fn extract_struct_def( | |||
195 | 195 | ||
196 | fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> { | 196 | fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> { |
197 | let name = variant.name()?; | 197 | let name = variant.name()?; |
198 | let tuple_field = make::tuple_field(None, make::ty(name.text())); | 198 | let tuple_field = make::tuple_field(None, make::ty(&name.text())); |
199 | let replacement = make::variant( | 199 | let replacement = make::variant( |
200 | name, | 200 | name, |
201 | Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))), | 201 | Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))), |
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_enum_is_method.rs b/crates/ide_assists/src/handlers/generate_enum_is_method.rs index 7e181a480..a9f71a703 100644 --- a/crates/ide_assists/src/handlers/generate_enum_is_method.rs +++ b/crates/ide_assists/src/handlers/generate_enum_is_method.rs | |||
@@ -44,7 +44,7 @@ pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext) -> | |||
44 | }; | 44 | }; |
45 | 45 | ||
46 | let enum_lowercase_name = to_lower_snake_case(&parent_enum.name()?.to_string()); | 46 | let enum_lowercase_name = to_lower_snake_case(&parent_enum.name()?.to_string()); |
47 | let fn_name = format!("is_{}", &to_lower_snake_case(variant_name.text())); | 47 | let fn_name = format!("is_{}", &to_lower_snake_case(&variant_name.text())); |
48 | 48 | ||
49 | // Return early if we've found an existing new fn | 49 | // Return early if we've found an existing new fn |
50 | let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?; | 50 | let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?; |
diff --git a/crates/ide_assists/src/handlers/generate_enum_projection_method.rs b/crates/ide_assists/src/handlers/generate_enum_projection_method.rs index 871bcab50..e2f572ba3 100644 --- a/crates/ide_assists/src/handlers/generate_enum_projection_method.rs +++ b/crates/ide_assists/src/handlers/generate_enum_projection_method.rs | |||
@@ -132,7 +132,8 @@ fn generate_enum_projection_method( | |||
132 | ast::StructKind::Unit => return None, | 132 | ast::StructKind::Unit => return None, |
133 | }; | 133 | }; |
134 | 134 | ||
135 | let fn_name = format!("{}_{}", props.fn_name_prefix, &to_lower_snake_case(variant_name.text())); | 135 | let fn_name = |
136 | format!("{}_{}", props.fn_name_prefix, &to_lower_snake_case(&variant_name.text())); | ||
136 | 137 | ||
137 | // Return early if we've found an existing new fn | 138 | // Return early if we've found an existing new fn |
138 | let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?; | 139 | let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?; |
diff --git a/crates/ide_assists/src/handlers/generate_impl.rs b/crates/ide_assists/src/handlers/generate_impl.rs index a8e3c4fc2..fd2e250bc 100644 --- a/crates/ide_assists/src/handlers/generate_impl.rs +++ b/crates/ide_assists/src/handlers/generate_impl.rs | |||
@@ -72,6 +72,17 @@ mod tests { | |||
72 | check_assist( | 72 | check_assist( |
73 | generate_impl, | 73 | generate_impl, |
74 | r#" | 74 | r#" |
75 | struct MyOwnArray<T, const S: usize> {}$0"#, | ||
76 | r#" | ||
77 | struct MyOwnArray<T, const S: usize> {} | ||
78 | |||
79 | impl<T, const S: usize> MyOwnArray<T, S> { | ||
80 | $0 | ||
81 | }"#, | ||
82 | ); | ||
83 | check_assist( | ||
84 | generate_impl, | ||
85 | r#" | ||
75 | #[cfg(feature = "foo")] | 86 | #[cfg(feature = "foo")] |
76 | struct Foo<'a, T: Foo<'a>> {$0}"#, | 87 | struct Foo<'a, T: Foo<'a>> {$0}"#, |
77 | r#" | 88 | r#" |
@@ -114,11 +125,11 @@ mod tests { | |||
114 | check_assist( | 125 | check_assist( |
115 | generate_impl, | 126 | generate_impl, |
116 | r#" | 127 | r#" |
117 | struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String> {}$0"#, | 128 | struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}$0"#, |
118 | r#" | 129 | r#" |
119 | struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String> {} | 130 | struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {} |
120 | 131 | ||
121 | impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b> Defaulted<'a, 'b, T> { | 132 | impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b, const S: usize> Defaulted<'a, 'b, T, S> { |
122 | $0 | 133 | $0 |
123 | }"#, | 134 | }"#, |
124 | ); | 135 | ); |
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/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs index 4f0ef52ca..f872d20c8 100644 --- a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs | |||
@@ -165,7 +165,7 @@ fn impl_def_from_trait( | |||
165 | } | 165 | } |
166 | let impl_def = make::impl_trait( | 166 | let impl_def = make::impl_trait( |
167 | trait_path.clone(), | 167 | trait_path.clone(), |
168 | make::path_unqualified(make::path_segment(make::name_ref(annotated_name.text()))), | 168 | make::path_unqualified(make::path_segment(make::name_ref(&annotated_name.text()))), |
169 | ); | 169 | ); |
170 | let (impl_def, first_assoc_item) = | 170 | let (impl_def, first_assoc_item) = |
171 | add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope); | 171 | add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope); |
@@ -178,12 +178,13 @@ fn update_attribute( | |||
178 | trait_name: &ast::NameRef, | 178 | trait_name: &ast::NameRef, |
179 | attr: &ast::Attr, | 179 | attr: &ast::Attr, |
180 | ) { | 180 | ) { |
181 | let trait_name = trait_name.text(); | ||
181 | let new_attr_input = input | 182 | let new_attr_input = input |
182 | .syntax() | 183 | .syntax() |
183 | .descendants_with_tokens() | 184 | .descendants_with_tokens() |
184 | .filter(|t| t.kind() == IDENT) | 185 | .filter(|t| t.kind() == IDENT) |
185 | .filter_map(|t| t.into_token().map(|t| t.text().to_string())) | 186 | .filter_map(|t| t.into_token().map(|t| t.text().to_string())) |
186 | .filter(|t| t != trait_name.text()) | 187 | .filter(|t| t != &trait_name) |
187 | .collect::<Vec<_>>(); | 188 | .collect::<Vec<_>>(); |
188 | let has_more_derives = !new_attr_input.is_empty(); | 189 | let has_more_derives = !new_attr_input.is_empty(); |
189 | 190 | ||