aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src')
-rw-r--r--crates/ide_assists/src/assist_context.rs5
-rw-r--r--crates/ide_assists/src/handlers/auto_import.rs2
-rw-r--r--crates/ide_assists/src/handlers/convert_into_to_from.rs355
-rw-r--r--crates/ide_assists/src/handlers/extract_function.rs176
-rw-r--r--crates/ide_assists/src/handlers/extract_type_alias.rs149
-rw-r--r--crates/ide_assists/src/handlers/extract_variable.rs73
-rw-r--r--crates/ide_assists/src/handlers/fill_match_arms.rs22
-rw-r--r--crates/ide_assists/src/handlers/flip_comma.rs18
-rw-r--r--crates/ide_assists/src/handlers/generate_default_from_new.rs2
-rw-r--r--crates/ide_assists/src/handlers/generate_is_empty_from_len.rs2
-rw-r--r--crates/ide_assists/src/handlers/remove_dbg.rs105
-rw-r--r--crates/ide_assists/src/handlers/reorder_fields.rs89
-rw-r--r--crates/ide_assists/src/lib.rs6
-rw-r--r--crates/ide_assists/src/tests.rs6
-rw-r--r--crates/ide_assists/src/tests/generated.rs51
-rw-r--r--crates/ide_assists/src/utils.rs4
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};
15use syntax::{ 15use 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 @@
1use ide_db::{
2 helpers::{mod_path_to_ast, FamousDefs},
3 traits::resolve_target_trait,
4};
5use syntax::ast::{self, AstNode, NameOwner};
6
7use 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// ```
39pub(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)]
114mod 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#"
123struct Thing {
124 a: String,
125 b: usize
126}
127
128impl $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#"
138struct Thing {
139 a: String,
140 b: usize
141}
142
143impl 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#"
159enum Thing {
160 Foo(String),
161 Bar(String)
162}
163
164impl $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#"
174enum Thing {
175 Foo(String),
176 Bar(String)
177}
178
179impl 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#"
195enum Thing<'a> {
196 Foo(&'a str),
197 Bar(&'a str)
198}
199
200impl<'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#"
210enum Thing<'a> {
211 Foo(&'a str),
212 Bar(&'a str)
213}
214
215impl<'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#"
231struct Thing(String);
232
233impl $0core::convert::Into<String> for &Thing {
234 fn into(self) -> Thing {
235 self.0.clone()
236 }
237}
238"#,
239 r#"
240struct Thing(String);
241
242impl 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#"
255mod things {
256 pub struct Thing(String);
257 pub struct BetterThing(String);
258}
259
260impl $0core::convert::Into<things::BetterThing> for &things::Thing {
261 fn into(self) -> Thing {
262 things::BetterThing(self.0.clone())
263 }
264}
265"#,
266 r#"
267mod things {
268 pub struct Thing(String);
269 pub struct BetterThing(String);
270}
271
272impl 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#"
285mod things {
286 pub enum Thing {
287 A(String)
288 }
289 pub struct BetterThing {
290 B(String)
291 }
292}
293
294impl $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#"
303mod things {
304 pub enum Thing {
305 A(String)
306 }
307 pub struct BetterThing {
308 B(String)
309 }
310}
311
312impl 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#"
327pub trait Into<T> {{
328 pub fn into(self) -> T;
329}}
330
331struct Thing {
332 a: String,
333}
334
335impl $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)]
567struct 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
711fn reference_is_exclusive( 718fn 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
731fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> { 738fn 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
823fn vars_defined_in_body_and_outlive(ctx: &AssistContext, body: &FunctionBody) -> Vec<Local> { 842fn 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
847fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool { 872fn 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
853fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> { 890fn 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"
2164fn foo() {
2165 let n = 1;
2166 $0let mut k = n * n;$0
2167 k += 1;
2168}",
2169 r"
2170fn foo() {
2171 let n = 1;
2172 let mut k = fun_name(n);
2173 k += 1;
2174}
2175
2176fn $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"
2214fn 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"
2224fn foo() {
2225 let n = 1;
2226 let (mut k, mut m, o) = fun_name(n);
2227 k += o;
2228 m = 1;
2229}
2230
2231fn $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"
3483macro_rules! m {
3484 ($val:expr) => { $val };
3485}
3486
3487fn foo() {
3488 let n = 1;
3489 $0let k = n * m!(n);$0
3490 let m = k + 1;
3491}",
3492 r"
3493macro_rules! m {
3494 ($val:expr) => { $val };
3495}
3496
3497fn foo() {
3498 let n = 1;
3499 let k = fun_name(n);
3500 let m = k + 1;
3501}
3502
3503fn $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 @@
1use syntax::ast::{self, AstNode};
2
3use 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// ```
22pub(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)]
51mod 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"
61struct 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"
73struct S {
74 field: $0u8$0,
75}
76 ",
77 r#"
78type $0Type = u8;
79
80struct 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"
92fn generic<T>() {}
93
94fn f() {
95 generic::<$0()$0>();
96}
97 ",
98 r#"
99fn generic<T>() {}
100
101type $0Type = ();
102
103fn 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"
115struct Vec<T> {}
116struct S {
117 v: Vec<Vec<$0Vec<u8>$0>>,
118}
119 ",
120 r#"
121struct Vec<T> {}
122type $0Type = Vec<u8>;
123
124struct 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"
136struct S {
137 field: ($0u8$0,),
138}
139 ",
140 r#"
141type $0Type = u8;
142
143struct 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;
2use syntax::{ 2use 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#"
286fn main() { 297fn 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#"
295fn main() { 306fn 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#"
311fn main() { 322fn 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#"
323fn main() { 334fn 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#"
355fn main() {
356 match () {
357 () if $010 > 0$0 => 1
358 _ => 2
359 };
360}
361"#,
362 r#"
363fn 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#"
343fn main() { 380fn main() {
344 let lambda = |x: u32| $0x * 2$0; 381 let lambda = |x: u32| $0x * 2$0;
345} 382}
346", 383"#,
347 " 384 r#"
348fn main() { 385fn 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#"
360fn main() { 397fn main() {
361 let lambda = |x: u32| { $0x * 2$0 }; 398 let lambda = |x: u32| { $0x * 2$0 };
362} 399}
363", 400"#,
364 " 401 r#"
365fn main() { 402fn 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 @@
1use syntax::{ 1use 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(&macro_call)?; 25 let new_contents = adjusted_macro_contents(&macro_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
83fn whitespace_start(it: SyntaxElement) -> Option<TextSize> {
84 Some(it.into_token().and_then(ast::Whitespace::cast)?.syntax().text_range().start())
85}
86
44fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> { 87fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> {
45 let contents = get_valid_macrocall_contents(&macro_call, "dbg")?; 88 let contents = get_valid_macrocall_contents(&macro_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#"
467fn foo() {
468 $0dbg!();
469}
470"#,
471 r#"
472fn foo() {
473}
474"#,
475 );
476 check_assist(
477 remove_dbg,
478 r#"
479fn foo() {
480 let test = $0dbg!();
481}"#,
482 r#"
483fn foo() {
484 let test = ();
485}"#,
486 );
487 check_assist(
488 remove_dbg,
489 r#"
490fn foo() {
491 let t = {
492 println!("Hello, world");
493 $0dbg!()
494 };
495}"#,
496 r#"
497fn 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 @@
1use either::Either;
2use itertools::Itertools;
1use rustc_hash::FxHashMap; 3use rustc_hash::FxHashMap;
2 4
3use syntax::{algo, ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode}; 5use syntax::{ast, ted, AstNode};
4 6
5use crate::{AssistContext, AssistId, AssistKind, Assists}; 7use crate::{AssistContext, AssistId, AssistKind, Assists};
6 8
@@ -22,60 +24,70 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
22pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 24pub(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
70fn get_field_name(node: &SyntaxNode) -> String { 82fn 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
81fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { 93fn 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)]
30pub enum AssistKind { 30pub 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]
209fn doctest_convert_into_to_from() {
210 check_doc_test(
211 "convert_into_to_from",
212 r#####"
213//- /lib.rs crate:core
214pub mod convert { pub trait Into<T> { pub fn into(self) -> T; } }
215//- /lib.rs crate:main deps:core
216use core::convert::Into;
217impl $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#####"
227use core::convert::Into;
228impl 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]
209fn doctest_convert_iter_for_each_to_for() { 241fn 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]
364fn doctest_extract_type_alias() {
365 check_doc_test(
366 "extract_type_alias",
367 r#####"
368struct S {
369 field: $0(u8, u8, u8)$0,
370}
371"#####,
372 r#####"
373type $0Type = (u8, u8, u8);
374
375struct S {
376 field: Type,
377}
378"#####,
379 )
380}
381
382#[test]
332fn doctest_extract_variable() { 383fn 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