diff options
Diffstat (limited to 'crates/ide_assists/src')
-rw-r--r-- | crates/ide_assists/src/handlers/auto_import.rs | 33 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/extract_function.rs | 276 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/extract_variable.rs | 73 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/fill_match_arms.rs | 370 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/flip_comma.rs | 18 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/generate_deref.rs | 227 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/inline_local_variable.rs | 223 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/introduce_named_lifetime.rs | 112 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/remove_dbg.rs | 105 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/reorder_fields.rs | 89 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs | 108 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 4 | ||||
-rw-r--r-- | crates/ide_assists/src/tests.rs | 7 | ||||
-rw-r--r-- | crates/ide_assists/src/tests/generated.rs | 27 |
14 files changed, 1365 insertions, 307 deletions
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs index 5ccd7f7a2..49aa70f74 100644 --- a/crates/ide_assists/src/handlers/auto_import.rs +++ b/crates/ide_assists/src/handlers/auto_import.rs | |||
@@ -934,4 +934,37 @@ fn main() { | |||
934 | ", | 934 | ", |
935 | ); | 935 | ); |
936 | } | 936 | } |
937 | |||
938 | #[test] | ||
939 | fn inner_items() { | ||
940 | check_assist( | ||
941 | auto_import, | ||
942 | r#" | ||
943 | mod baz { | ||
944 | pub struct Foo {} | ||
945 | } | ||
946 | |||
947 | mod bar { | ||
948 | fn bar() { | ||
949 | Foo$0; | ||
950 | println!("Hallo"); | ||
951 | } | ||
952 | } | ||
953 | "#, | ||
954 | r#" | ||
955 | mod baz { | ||
956 | pub struct Foo {} | ||
957 | } | ||
958 | |||
959 | mod bar { | ||
960 | use crate::baz::Foo; | ||
961 | |||
962 | fn bar() { | ||
963 | Foo; | ||
964 | println!("Hallo"); | ||
965 | } | ||
966 | } | ||
967 | "#, | ||
968 | ); | ||
969 | } | ||
937 | } | 970 | } |
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs index 5fdc8bf38..5f80a40c8 100644 --- a/crates/ide_assists/src/handlers/extract_function.rs +++ b/crates/ide_assists/src/handlers/extract_function.rs | |||
@@ -75,7 +75,8 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
75 | let insert_after = scope_for_fn_insertion(&body, anchor)?; | 75 | let insert_after = scope_for_fn_insertion(&body, anchor)?; |
76 | let module = ctx.sema.scope(&insert_after).module()?; | 76 | let module = ctx.sema.scope(&insert_after).module()?; |
77 | 77 | ||
78 | let vars_defined_in_body_and_outlive = vars_defined_in_body_and_outlive(ctx, &body); | 78 | let vars_defined_in_body_and_outlive = |
79 | vars_defined_in_body_and_outlive(ctx, &body, &node.parent().as_ref().unwrap_or(&node)); | ||
79 | let ret_ty = body_return_ty(ctx, &body)?; | 80 | let ret_ty = body_return_ty(ctx, &body)?; |
80 | 81 | ||
81 | // FIXME: we compute variables that outlive here just to check `never!` condition | 82 | // FIXME: we compute variables that outlive here just to check `never!` condition |
@@ -257,7 +258,7 @@ struct Function { | |||
257 | control_flow: ControlFlow, | 258 | control_flow: ControlFlow, |
258 | ret_ty: RetType, | 259 | ret_ty: RetType, |
259 | body: FunctionBody, | 260 | body: FunctionBody, |
260 | vars_defined_in_body_and_outlive: Vec<Local>, | 261 | vars_defined_in_body_and_outlive: Vec<OutlivedLocal>, |
261 | } | 262 | } |
262 | 263 | ||
263 | #[derive(Debug)] | 264 | #[derive(Debug)] |
@@ -296,9 +297,9 @@ impl Function { | |||
296 | RetType::Expr(ty) => FunType::Single(ty.clone()), | 297 | RetType::Expr(ty) => FunType::Single(ty.clone()), |
297 | RetType::Stmt => match self.vars_defined_in_body_and_outlive.as_slice() { | 298 | RetType::Stmt => match self.vars_defined_in_body_and_outlive.as_slice() { |
298 | [] => FunType::Unit, | 299 | [] => FunType::Unit, |
299 | [var] => FunType::Single(var.ty(ctx.db())), | 300 | [var] => FunType::Single(var.local.ty(ctx.db())), |
300 | vars => { | 301 | vars => { |
301 | let types = vars.iter().map(|v| v.ty(ctx.db())).collect(); | 302 | let types = vars.iter().map(|v| v.local.ty(ctx.db())).collect(); |
302 | FunType::Tuple(types) | 303 | FunType::Tuple(types) |
303 | } | 304 | } |
304 | }, | 305 | }, |
@@ -562,6 +563,12 @@ impl HasTokenAtOffset for FunctionBody { | |||
562 | } | 563 | } |
563 | } | 564 | } |
564 | 565 | ||
566 | #[derive(Debug)] | ||
567 | struct OutlivedLocal { | ||
568 | local: Local, | ||
569 | mut_usage_outside_body: bool, | ||
570 | } | ||
571 | |||
565 | /// Try to guess what user wants to extract | 572 | /// Try to guess what user wants to extract |
566 | /// | 573 | /// |
567 | /// We have basically have two cases: | 574 | /// We have basically have two cases: |
@@ -592,7 +599,12 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu | |||
592 | // we have selected a few statements in a block | 599 | // we have selected a few statements in a block |
593 | // so covering_element returns the whole block | 600 | // so covering_element returns the whole block |
594 | if node.kind() == BLOCK_EXPR { | 601 | if node.kind() == BLOCK_EXPR { |
595 | let body = FunctionBody::from_range(node.clone(), selection_range); | 602 | // Extract the full statements. |
603 | let statements_range = node | ||
604 | .children() | ||
605 | .filter(|c| selection_range.intersect(c.text_range()).is_some()) | ||
606 | .fold(selection_range, |acc, c| acc.cover(c.text_range())); | ||
607 | let body = FunctionBody::from_range(node.clone(), statements_range); | ||
596 | if body.is_some() { | 608 | if body.is_some() { |
597 | return body; | 609 | return body; |
598 | } | 610 | } |
@@ -603,7 +615,8 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu | |||
603 | // so we try to expand covering_element to parent and repeat the previous | 615 | // so we try to expand covering_element to parent and repeat the previous |
604 | if let Some(parent) = node.parent() { | 616 | if let Some(parent) = node.parent() { |
605 | if parent.kind() == BLOCK_EXPR { | 617 | if parent.kind() == BLOCK_EXPR { |
606 | let body = FunctionBody::from_range(parent, selection_range); | 618 | // Extract the full statement. |
619 | let body = FunctionBody::from_range(parent, node.text_range()); | ||
607 | if body.is_some() { | 620 | if body.is_some() { |
608 | return body; | 621 | return body; |
609 | } | 622 | } |
@@ -707,10 +720,10 @@ fn has_exclusive_usages(ctx: &AssistContext, usages: &LocalUsages, body: &Functi | |||
707 | .any(|reference| reference_is_exclusive(reference, body, ctx)) | 720 | .any(|reference| reference_is_exclusive(reference, body, ctx)) |
708 | } | 721 | } |
709 | 722 | ||
710 | /// checks if this reference requires `&mut` access inside body | 723 | /// checks if this reference requires `&mut` access inside node |
711 | fn reference_is_exclusive( | 724 | fn reference_is_exclusive( |
712 | reference: &FileReference, | 725 | reference: &FileReference, |
713 | body: &FunctionBody, | 726 | node: &dyn HasTokenAtOffset, |
714 | ctx: &AssistContext, | 727 | ctx: &AssistContext, |
715 | ) -> bool { | 728 | ) -> bool { |
716 | // we directly modify variable with set: `n = 0`, `n += 1` | 729 | // we directly modify variable with set: `n = 0`, `n += 1` |
@@ -719,7 +732,7 @@ fn reference_is_exclusive( | |||
719 | } | 732 | } |
720 | 733 | ||
721 | // we take `&mut` reference to variable: `&mut v` | 734 | // we take `&mut` reference to variable: `&mut v` |
722 | let path = match path_element_of_reference(body, reference) { | 735 | let path = match path_element_of_reference(node, reference) { |
723 | Some(path) => path, | 736 | Some(path) => path, |
724 | None => return false, | 737 | None => return false, |
725 | }; | 738 | }; |
@@ -729,6 +742,14 @@ fn reference_is_exclusive( | |||
729 | 742 | ||
730 | /// checks if this expr requires `&mut` access, recurses on field access | 743 | /// checks if this expr requires `&mut` access, recurses on field access |
731 | fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> { | 744 | fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> { |
745 | match expr { | ||
746 | ast::Expr::MacroCall(_) => { | ||
747 | // FIXME: expand macro and check output for mutable usages of the variable? | ||
748 | return None; | ||
749 | } | ||
750 | _ => (), | ||
751 | } | ||
752 | |||
732 | let parent = expr.syntax().parent()?; | 753 | let parent = expr.syntax().parent()?; |
733 | 754 | ||
734 | if let Some(bin_expr) = ast::BinExpr::cast(parent.clone()) { | 755 | if let Some(bin_expr) = ast::BinExpr::cast(parent.clone()) { |
@@ -787,7 +808,7 @@ impl HasTokenAtOffset for SyntaxNode { | |||
787 | } | 808 | } |
788 | } | 809 | } |
789 | 810 | ||
790 | /// find relevant `ast::PathExpr` for reference | 811 | /// find relevant `ast::Expr` for reference |
791 | /// | 812 | /// |
792 | /// # Preconditions | 813 | /// # Preconditions |
793 | /// | 814 | /// |
@@ -804,7 +825,11 @@ fn path_element_of_reference( | |||
804 | stdx::never!(false, "cannot find path parent of variable usage: {:?}", token); | 825 | stdx::never!(false, "cannot find path parent of variable usage: {:?}", token); |
805 | None | 826 | None |
806 | })?; | 827 | })?; |
807 | stdx::always!(matches!(path, ast::Expr::PathExpr(_))); | 828 | stdx::always!( |
829 | matches!(path, ast::Expr::PathExpr(_) | ast::Expr::MacroCall(_)), | ||
830 | "unexpected expression type for variable usage: {:?}", | ||
831 | path | ||
832 | ); | ||
808 | Some(path) | 833 | Some(path) |
809 | } | 834 | } |
810 | 835 | ||
@@ -820,10 +845,16 @@ fn vars_defined_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> | |||
820 | } | 845 | } |
821 | 846 | ||
822 | /// list local variables defined inside `body` that should be returned from extracted function | 847 | /// list local variables defined inside `body` that should be returned from extracted function |
823 | fn vars_defined_in_body_and_outlive(ctx: &AssistContext, body: &FunctionBody) -> Vec<Local> { | 848 | fn vars_defined_in_body_and_outlive( |
824 | let mut vars_defined_in_body = vars_defined_in_body(&body, ctx); | 849 | ctx: &AssistContext, |
825 | vars_defined_in_body.retain(|var| var_outlives_body(ctx, body, var)); | 850 | body: &FunctionBody, |
851 | parent: &SyntaxNode, | ||
852 | ) -> Vec<OutlivedLocal> { | ||
853 | let vars_defined_in_body = vars_defined_in_body(&body, ctx); | ||
826 | vars_defined_in_body | 854 | vars_defined_in_body |
855 | .into_iter() | ||
856 | .filter_map(|var| var_outlives_body(ctx, body, var, parent)) | ||
857 | .collect() | ||
827 | } | 858 | } |
828 | 859 | ||
829 | /// checks if the relevant local was defined before(outside of) body | 860 | /// checks if the relevant local was defined before(outside of) body |
@@ -843,11 +874,23 @@ fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode { | |||
843 | } | 874 | } |
844 | } | 875 | } |
845 | 876 | ||
846 | /// checks if local variable is used after(outside of) body | 877 | /// returns usage details if local variable is used after(outside of) body |
847 | fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool { | 878 | fn var_outlives_body( |
848 | let usages = LocalUsages::find(ctx, *var); | 879 | ctx: &AssistContext, |
880 | body: &FunctionBody, | ||
881 | var: Local, | ||
882 | parent: &SyntaxNode, | ||
883 | ) -> Option<OutlivedLocal> { | ||
884 | let usages = LocalUsages::find(ctx, var); | ||
849 | let has_usages = usages.iter().any(|reference| body.preceedes_range(reference.range)); | 885 | let has_usages = usages.iter().any(|reference| body.preceedes_range(reference.range)); |
850 | has_usages | 886 | if !has_usages { |
887 | return None; | ||
888 | } | ||
889 | let has_mut_usages = usages | ||
890 | .iter() | ||
891 | .filter(|reference| body.preceedes_range(reference.range)) | ||
892 | .any(|reference| reference_is_exclusive(reference, parent, ctx)); | ||
893 | Some(OutlivedLocal { local: var, mut_usage_outside_body: has_mut_usages }) | ||
851 | } | 894 | } |
852 | 895 | ||
853 | fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> { | 896 | fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> { |
@@ -927,16 +970,25 @@ fn format_replacement(ctx: &AssistContext, fun: &Function, indent: IndentLevel) | |||
927 | let mut buf = String::new(); | 970 | let mut buf = String::new(); |
928 | match fun.vars_defined_in_body_and_outlive.as_slice() { | 971 | match fun.vars_defined_in_body_and_outlive.as_slice() { |
929 | [] => {} | 972 | [] => {} |
930 | [var] => format_to!(buf, "let {} = ", var.name(ctx.db()).unwrap()), | 973 | [var] => { |
974 | format_to!(buf, "let {}{} = ", mut_modifier(var), var.local.name(ctx.db()).unwrap()) | ||
975 | } | ||
931 | [v0, vs @ ..] => { | 976 | [v0, vs @ ..] => { |
932 | buf.push_str("let ("); | 977 | buf.push_str("let ("); |
933 | format_to!(buf, "{}", v0.name(ctx.db()).unwrap()); | 978 | format_to!(buf, "{}{}", mut_modifier(v0), v0.local.name(ctx.db()).unwrap()); |
934 | for var in vs { | 979 | for var in vs { |
935 | format_to!(buf, ", {}", var.name(ctx.db()).unwrap()); | 980 | format_to!(buf, ", {}{}", mut_modifier(var), var.local.name(ctx.db()).unwrap()); |
936 | } | 981 | } |
937 | buf.push_str(") = "); | 982 | buf.push_str(") = "); |
938 | } | 983 | } |
939 | } | 984 | } |
985 | fn mut_modifier(var: &OutlivedLocal) -> &'static str { | ||
986 | if var.mut_usage_outside_body { | ||
987 | "mut " | ||
988 | } else { | ||
989 | "" | ||
990 | } | ||
991 | } | ||
940 | format_to!(buf, "{}", expr); | 992 | format_to!(buf, "{}", expr); |
941 | if fun.ret_ty.is_unit() | 993 | if fun.ret_ty.is_unit() |
942 | && (!fun.vars_defined_in_body_and_outlive.is_empty() || !expr.is_block_like()) | 994 | && (!fun.vars_defined_in_body_and_outlive.is_empty() || !expr.is_block_like()) |
@@ -1175,9 +1227,19 @@ fn make_body( | |||
1175 | FunctionBody::Expr(expr) => { | 1227 | FunctionBody::Expr(expr) => { |
1176 | let expr = rewrite_body_segment(ctx, &fun.params, &handler, expr.syntax()); | 1228 | let expr = rewrite_body_segment(ctx, &fun.params, &handler, expr.syntax()); |
1177 | let expr = ast::Expr::cast(expr).unwrap(); | 1229 | let expr = ast::Expr::cast(expr).unwrap(); |
1178 | let expr = expr.dedent(old_indent).indent(IndentLevel(1)); | 1230 | match expr { |
1231 | ast::Expr::BlockExpr(block) => { | ||
1232 | // If the extracted expression is itself a block, there is no need to wrap it inside another block. | ||
1233 | let block = block.dedent(old_indent); | ||
1234 | // Recreate the block for formatting consistency with other extracted functions. | ||
1235 | make::block_expr(block.statements(), block.tail_expr()) | ||
1236 | } | ||
1237 | _ => { | ||
1238 | let expr = expr.dedent(old_indent).indent(IndentLevel(1)); | ||
1179 | 1239 | ||
1180 | make::block_expr(Vec::new(), Some(expr)) | 1240 | make::block_expr(Vec::new(), Some(expr)) |
1241 | } | ||
1242 | } | ||
1181 | } | 1243 | } |
1182 | FunctionBody::Span { parent, text_range } => { | 1244 | FunctionBody::Span { parent, text_range } => { |
1183 | let mut elements: Vec<_> = parent | 1245 | let mut elements: Vec<_> = parent |
@@ -1199,10 +1261,10 @@ fn make_body( | |||
1199 | match fun.vars_defined_in_body_and_outlive.as_slice() { | 1261 | match fun.vars_defined_in_body_and_outlive.as_slice() { |
1200 | [] => {} | 1262 | [] => {} |
1201 | [var] => { | 1263 | [var] => { |
1202 | tail_expr = Some(path_expr_from_local(ctx, *var)); | 1264 | tail_expr = Some(path_expr_from_local(ctx, var.local)); |
1203 | } | 1265 | } |
1204 | vars => { | 1266 | vars => { |
1205 | let exprs = vars.iter().map(|var| path_expr_from_local(ctx, *var)); | 1267 | let exprs = vars.iter().map(|var| path_expr_from_local(ctx, var.local)); |
1206 | let expr = make::expr_tuple(exprs); | 1268 | let expr = make::expr_tuple(exprs); |
1207 | tail_expr = Some(expr); | 1269 | tail_expr = Some(expr); |
1208 | } | 1270 | } |
@@ -1492,7 +1554,7 @@ fn foo() { | |||
1492 | } | 1554 | } |
1493 | 1555 | ||
1494 | fn $0fun_name() -> i32 { | 1556 | fn $0fun_name() -> i32 { |
1495 | { 1 + 1 } | 1557 | 1 + 1 |
1496 | }"#, | 1558 | }"#, |
1497 | ); | 1559 | ); |
1498 | } | 1560 | } |
@@ -1739,6 +1801,60 @@ fn $0fun_name() -> i32 { | |||
1739 | } | 1801 | } |
1740 | 1802 | ||
1741 | #[test] | 1803 | #[test] |
1804 | fn extract_partial_block_single_line() { | ||
1805 | check_assist( | ||
1806 | extract_function, | ||
1807 | r#" | ||
1808 | fn foo() { | ||
1809 | let n = 1; | ||
1810 | let mut v = $0n * n;$0 | ||
1811 | v += 1; | ||
1812 | }"#, | ||
1813 | r#" | ||
1814 | fn foo() { | ||
1815 | let n = 1; | ||
1816 | let mut v = fun_name(n); | ||
1817 | v += 1; | ||
1818 | } | ||
1819 | |||
1820 | fn $0fun_name(n: i32) -> i32 { | ||
1821 | let mut v = n * n; | ||
1822 | v | ||
1823 | }"#, | ||
1824 | ); | ||
1825 | } | ||
1826 | |||
1827 | #[test] | ||
1828 | fn extract_partial_block() { | ||
1829 | check_assist( | ||
1830 | extract_function, | ||
1831 | r#" | ||
1832 | fn foo() { | ||
1833 | let m = 2; | ||
1834 | let n = 1; | ||
1835 | let mut v = m $0* n; | ||
1836 | let mut w = 3;$0 | ||
1837 | v += 1; | ||
1838 | w += 1; | ||
1839 | }"#, | ||
1840 | r#" | ||
1841 | fn foo() { | ||
1842 | let m = 2; | ||
1843 | let n = 1; | ||
1844 | let (mut v, mut w) = fun_name(m, n); | ||
1845 | v += 1; | ||
1846 | w += 1; | ||
1847 | } | ||
1848 | |||
1849 | fn $0fun_name(m: i32, n: i32) -> (i32, i32) { | ||
1850 | let mut v = m * n; | ||
1851 | let mut w = 3; | ||
1852 | (v, w) | ||
1853 | }"#, | ||
1854 | ); | ||
1855 | } | ||
1856 | |||
1857 | #[test] | ||
1742 | fn argument_form_expr() { | 1858 | fn argument_form_expr() { |
1743 | check_assist( | 1859 | check_assist( |
1744 | extract_function, | 1860 | extract_function, |
@@ -2111,6 +2227,30 @@ fn $0fun_name(n: i32) -> i32 { | |||
2111 | } | 2227 | } |
2112 | 2228 | ||
2113 | #[test] | 2229 | #[test] |
2230 | fn variable_defined_inside_and_used_after_mutably_no_ret() { | ||
2231 | check_assist( | ||
2232 | extract_function, | ||
2233 | r" | ||
2234 | fn foo() { | ||
2235 | let n = 1; | ||
2236 | $0let mut k = n * n;$0 | ||
2237 | k += 1; | ||
2238 | }", | ||
2239 | r" | ||
2240 | fn foo() { | ||
2241 | let n = 1; | ||
2242 | let mut k = fun_name(n); | ||
2243 | k += 1; | ||
2244 | } | ||
2245 | |||
2246 | fn $0fun_name(n: i32) -> i32 { | ||
2247 | let mut k = n * n; | ||
2248 | k | ||
2249 | }", | ||
2250 | ); | ||
2251 | } | ||
2252 | |||
2253 | #[test] | ||
2114 | fn two_variables_defined_inside_and_used_after_no_ret() { | 2254 | fn two_variables_defined_inside_and_used_after_no_ret() { |
2115 | check_assist( | 2255 | check_assist( |
2116 | extract_function, | 2256 | extract_function, |
@@ -2137,6 +2277,38 @@ fn $0fun_name(n: i32) -> (i32, i32) { | |||
2137 | } | 2277 | } |
2138 | 2278 | ||
2139 | #[test] | 2279 | #[test] |
2280 | fn multi_variables_defined_inside_and_used_after_mutably_no_ret() { | ||
2281 | check_assist( | ||
2282 | extract_function, | ||
2283 | r" | ||
2284 | fn foo() { | ||
2285 | let n = 1; | ||
2286 | $0let mut k = n * n; | ||
2287 | let mut m = k + 2; | ||
2288 | let mut o = m + 3; | ||
2289 | o += 1;$0 | ||
2290 | k += o; | ||
2291 | m = 1; | ||
2292 | }", | ||
2293 | r" | ||
2294 | fn foo() { | ||
2295 | let n = 1; | ||
2296 | let (mut k, mut m, o) = fun_name(n); | ||
2297 | k += o; | ||
2298 | m = 1; | ||
2299 | } | ||
2300 | |||
2301 | fn $0fun_name(n: i32) -> (i32, i32, i32) { | ||
2302 | let mut k = n * n; | ||
2303 | let mut m = k + 2; | ||
2304 | let mut o = m + 3; | ||
2305 | o += 1; | ||
2306 | (k, m, o) | ||
2307 | }", | ||
2308 | ); | ||
2309 | } | ||
2310 | |||
2311 | #[test] | ||
2140 | fn nontrivial_patterns_define_variables() { | 2312 | fn nontrivial_patterns_define_variables() { |
2141 | check_assist( | 2313 | check_assist( |
2142 | extract_function, | 2314 | extract_function, |
@@ -2364,17 +2536,15 @@ fn foo() { | |||
2364 | } | 2536 | } |
2365 | 2537 | ||
2366 | fn $0fun_name(n: &mut i32) { | 2538 | fn $0fun_name(n: &mut i32) { |
2367 | { | 2539 | *n += *n; |
2368 | *n += *n; | 2540 | bar(*n); |
2369 | bar(*n); | 2541 | bar(*n+1); |
2370 | bar(*n+1); | 2542 | bar(*n**n); |
2371 | bar(*n**n); | 2543 | bar(&*n); |
2372 | bar(&*n); | 2544 | n.inc(); |
2373 | n.inc(); | 2545 | let v = n; |
2374 | let v = n; | 2546 | *v = v.succ(); |
2375 | *v = v.succ(); | 2547 | n.succ(); |
2376 | n.succ(); | ||
2377 | } | ||
2378 | }", | 2548 | }", |
2379 | ); | 2549 | ); |
2380 | } | 2550 | } |
@@ -3372,4 +3542,36 @@ fn foo() -> Result<(), i64> { | |||
3372 | }"##, | 3542 | }"##, |
3373 | ); | 3543 | ); |
3374 | } | 3544 | } |
3545 | |||
3546 | #[test] | ||
3547 | fn param_usage_in_macro() { | ||
3548 | check_assist( | ||
3549 | extract_function, | ||
3550 | r" | ||
3551 | macro_rules! m { | ||
3552 | ($val:expr) => { $val }; | ||
3553 | } | ||
3554 | |||
3555 | fn foo() { | ||
3556 | let n = 1; | ||
3557 | $0let k = n * m!(n);$0 | ||
3558 | let m = k + 1; | ||
3559 | }", | ||
3560 | r" | ||
3561 | macro_rules! m { | ||
3562 | ($val:expr) => { $val }; | ||
3563 | } | ||
3564 | |||
3565 | fn foo() { | ||
3566 | let n = 1; | ||
3567 | let k = fun_name(n); | ||
3568 | let m = k + 1; | ||
3569 | } | ||
3570 | |||
3571 | fn $0fun_name(n: i32) -> i32 { | ||
3572 | let k = n * m!(n); | ||
3573 | k | ||
3574 | }", | ||
3575 | ); | ||
3576 | } | ||
3375 | } | 3577 | } |
diff --git a/crates/ide_assists/src/handlers/extract_variable.rs b/crates/ide_assists/src/handlers/extract_variable.rs index 7a32483dc..136b9a55b 100644 --- a/crates/ide_assists/src/handlers/extract_variable.rs +++ b/crates/ide_assists/src/handlers/extract_variable.rs | |||
@@ -2,7 +2,8 @@ use stdx::format_to; | |||
2 | use syntax::{ | 2 | use syntax::{ |
3 | ast::{self, AstNode}, | 3 | ast::{self, AstNode}, |
4 | SyntaxKind::{ | 4 | SyntaxKind::{ |
5 | BLOCK_EXPR, BREAK_EXPR, CLOSURE_EXPR, COMMENT, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, | 5 | BLOCK_EXPR, BREAK_EXPR, CLOSURE_EXPR, COMMENT, LOOP_EXPR, MATCH_ARM, MATCH_GUARD, |
6 | PATH_EXPR, RETURN_EXPR, | ||
6 | }, | 7 | }, |
7 | SyntaxNode, | 8 | SyntaxNode, |
8 | }; | 9 | }; |
@@ -147,9 +148,18 @@ impl Anchor { | |||
147 | } | 148 | } |
148 | 149 | ||
149 | if let Some(parent) = node.parent() { | 150 | if let Some(parent) = node.parent() { |
150 | if parent.kind() == MATCH_ARM || parent.kind() == CLOSURE_EXPR { | 151 | if parent.kind() == CLOSURE_EXPR { |
152 | cov_mark::hit!(test_extract_var_in_closure_no_block); | ||
151 | return Some(Anchor::WrapInBlock(node)); | 153 | return Some(Anchor::WrapInBlock(node)); |
152 | } | 154 | } |
155 | if parent.kind() == MATCH_ARM { | ||
156 | if node.kind() == MATCH_GUARD { | ||
157 | cov_mark::hit!(test_extract_var_in_match_guard); | ||
158 | } else { | ||
159 | cov_mark::hit!(test_extract_var_in_match_arm_no_block); | ||
160 | return Some(Anchor::WrapInBlock(node)); | ||
161 | } | ||
162 | } | ||
153 | } | 163 | } |
154 | 164 | ||
155 | if let Some(stmt) = ast::Stmt::cast(node.clone()) { | 165 | if let Some(stmt) = ast::Stmt::cast(node.clone()) { |
@@ -280,9 +290,10 @@ fn foo() { | |||
280 | 290 | ||
281 | #[test] | 291 | #[test] |
282 | fn test_extract_var_in_match_arm_no_block() { | 292 | fn test_extract_var_in_match_arm_no_block() { |
293 | cov_mark::check!(test_extract_var_in_match_arm_no_block); | ||
283 | check_assist( | 294 | check_assist( |
284 | extract_variable, | 295 | extract_variable, |
285 | " | 296 | r#" |
286 | fn main() { | 297 | fn main() { |
287 | let x = true; | 298 | let x = true; |
288 | let tuple = match x { | 299 | let tuple = match x { |
@@ -290,8 +301,8 @@ fn main() { | |||
290 | _ => (0, false) | 301 | _ => (0, false) |
291 | }; | 302 | }; |
292 | } | 303 | } |
293 | ", | 304 | "#, |
294 | " | 305 | r#" |
295 | fn main() { | 306 | fn main() { |
296 | let x = true; | 307 | let x = true; |
297 | let tuple = match x { | 308 | let tuple = match x { |
@@ -299,7 +310,7 @@ fn main() { | |||
299 | _ => (0, false) | 310 | _ => (0, false) |
300 | }; | 311 | }; |
301 | } | 312 | } |
302 | ", | 313 | "#, |
303 | ); | 314 | ); |
304 | } | 315 | } |
305 | 316 | ||
@@ -307,7 +318,7 @@ fn main() { | |||
307 | fn test_extract_var_in_match_arm_with_block() { | 318 | fn test_extract_var_in_match_arm_with_block() { |
308 | check_assist( | 319 | check_assist( |
309 | extract_variable, | 320 | extract_variable, |
310 | " | 321 | r#" |
311 | fn main() { | 322 | fn main() { |
312 | let x = true; | 323 | let x = true; |
313 | let tuple = match x { | 324 | let tuple = match x { |
@@ -318,8 +329,8 @@ fn main() { | |||
318 | _ => (0, false) | 329 | _ => (0, false) |
319 | }; | 330 | }; |
320 | } | 331 | } |
321 | ", | 332 | "#, |
322 | " | 333 | r#" |
323 | fn main() { | 334 | fn main() { |
324 | let x = true; | 335 | let x = true; |
325 | let tuple = match x { | 336 | let tuple = match x { |
@@ -331,24 +342,50 @@ fn main() { | |||
331 | _ => (0, false) | 342 | _ => (0, false) |
332 | }; | 343 | }; |
333 | } | 344 | } |
334 | ", | 345 | "#, |
346 | ); | ||
347 | } | ||
348 | |||
349 | #[test] | ||
350 | fn test_extract_var_in_match_guard() { | ||
351 | cov_mark::check!(test_extract_var_in_match_guard); | ||
352 | check_assist( | ||
353 | extract_variable, | ||
354 | r#" | ||
355 | fn main() { | ||
356 | match () { | ||
357 | () if $010 > 0$0 => 1 | ||
358 | _ => 2 | ||
359 | }; | ||
360 | } | ||
361 | "#, | ||
362 | r#" | ||
363 | fn main() { | ||
364 | let $0var_name = 10 > 0; | ||
365 | match () { | ||
366 | () if var_name => 1 | ||
367 | _ => 2 | ||
368 | }; | ||
369 | } | ||
370 | "#, | ||
335 | ); | 371 | ); |
336 | } | 372 | } |
337 | 373 | ||
338 | #[test] | 374 | #[test] |
339 | fn test_extract_var_in_closure_no_block() { | 375 | fn test_extract_var_in_closure_no_block() { |
376 | cov_mark::check!(test_extract_var_in_closure_no_block); | ||
340 | check_assist( | 377 | check_assist( |
341 | extract_variable, | 378 | extract_variable, |
342 | " | 379 | r#" |
343 | fn main() { | 380 | fn main() { |
344 | let lambda = |x: u32| $0x * 2$0; | 381 | let lambda = |x: u32| $0x * 2$0; |
345 | } | 382 | } |
346 | ", | 383 | "#, |
347 | " | 384 | r#" |
348 | fn main() { | 385 | fn main() { |
349 | let lambda = |x: u32| { let $0var_name = x * 2; var_name }; | 386 | let lambda = |x: u32| { let $0var_name = x * 2; var_name }; |
350 | } | 387 | } |
351 | ", | 388 | "#, |
352 | ); | 389 | ); |
353 | } | 390 | } |
354 | 391 | ||
@@ -356,16 +393,16 @@ fn main() { | |||
356 | fn test_extract_var_in_closure_with_block() { | 393 | fn test_extract_var_in_closure_with_block() { |
357 | check_assist( | 394 | check_assist( |
358 | extract_variable, | 395 | extract_variable, |
359 | " | 396 | r#" |
360 | fn main() { | 397 | fn main() { |
361 | let lambda = |x: u32| { $0x * 2$0 }; | 398 | let lambda = |x: u32| { $0x * 2$0 }; |
362 | } | 399 | } |
363 | ", | 400 | "#, |
364 | " | 401 | r#" |
365 | fn main() { | 402 | fn main() { |
366 | let lambda = |x: u32| { let $0var_name = x * 2; var_name }; | 403 | let lambda = |x: u32| { let $0var_name = x * 2; var_name }; |
367 | } | 404 | } |
368 | ", | 405 | "#, |
369 | ); | 406 | ); |
370 | } | 407 | } |
371 | 408 | ||
diff --git a/crates/ide_assists/src/handlers/fill_match_arms.rs b/crates/ide_assists/src/handlers/fill_match_arms.rs index 878b3a3fa..be927cc1c 100644 --- a/crates/ide_assists/src/handlers/fill_match_arms.rs +++ b/crates/ide_assists/src/handlers/fill_match_arms.rs | |||
@@ -1,5 +1,6 @@ | |||
1 | use std::iter; | 1 | use std::iter; |
2 | 2 | ||
3 | use either::Either; | ||
3 | use hir::{Adt, HasSource, ModuleDef, Semantics}; | 4 | use hir::{Adt, HasSource, ModuleDef, Semantics}; |
4 | use ide_db::helpers::{mod_path_to_ast, FamousDefs}; | 5 | use ide_db::helpers::{mod_path_to_ast, FamousDefs}; |
5 | use ide_db::RootDatabase; | 6 | use ide_db::RootDatabase; |
@@ -7,7 +8,7 @@ use itertools::Itertools; | |||
7 | use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; | 8 | use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; |
8 | 9 | ||
9 | use crate::{ | 10 | use crate::{ |
10 | utils::{does_pat_match_variant, render_snippet, Cursor}, | 11 | utils::{self, render_snippet, Cursor}, |
11 | AssistContext, AssistId, AssistKind, Assists, | 12 | AssistContext, AssistId, AssistKind, Assists, |
12 | }; | 13 | }; |
13 | 14 | ||
@@ -48,6 +49,18 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
48 | } | 49 | } |
49 | } | 50 | } |
50 | 51 | ||
52 | let top_lvl_pats: Vec<_> = arms | ||
53 | .iter() | ||
54 | .filter_map(ast::MatchArm::pat) | ||
55 | .flat_map(|pat| match pat { | ||
56 | // Special case OrPat as separate top-level pats | ||
57 | Pat::OrPat(or_pat) => Either::Left(or_pat.pats()), | ||
58 | _ => Either::Right(iter::once(pat)), | ||
59 | }) | ||
60 | // Exclude top level wildcards so that they are expanded by this assist, retains status quo in #8129. | ||
61 | .filter(|pat| !matches!(pat, Pat::WildcardPat(_))) | ||
62 | .collect(); | ||
63 | |||
51 | let module = ctx.sema.scope(expr.syntax()).module()?; | 64 | let module = ctx.sema.scope(expr.syntax()).module()?; |
52 | 65 | ||
53 | let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { | 66 | let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { |
@@ -56,27 +69,20 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
56 | let mut variants = variants | 69 | let mut variants = variants |
57 | .into_iter() | 70 | .into_iter() |
58 | .filter_map(|variant| build_pat(ctx.db(), module, variant)) | 71 | .filter_map(|variant| build_pat(ctx.db(), module, variant)) |
59 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) | 72 | .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat)) |
60 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) | 73 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) |
61 | .collect::<Vec<_>>(); | 74 | .collect::<Vec<_>>(); |
62 | if Some(enum_def) == FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option() { | 75 | if Some(enum_def) |
76 | == FamousDefs(&ctx.sema, Some(module.krate())) | ||
77 | .core_option_Option() | ||
78 | .map(|x| lift_enum(x)) | ||
79 | { | ||
63 | // Match `Some` variant first. | 80 | // Match `Some` variant first. |
64 | cov_mark::hit!(option_order); | 81 | cov_mark::hit!(option_order); |
65 | variants.reverse() | 82 | variants.reverse() |
66 | } | 83 | } |
67 | variants | 84 | variants |
68 | } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) { | 85 | } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) { |
69 | // Partial fill not currently supported for tuple of enums. | ||
70 | if !arms.is_empty() { | ||
71 | return None; | ||
72 | } | ||
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 | 86 | // When calculating the match arms for a tuple of enums, we want |
81 | // to create a match arm for each possible combination of enum | 87 | // to create a match arm for each possible combination of enum |
82 | // values. The `multi_cartesian_product` method transforms | 88 | // values. The `multi_cartesian_product` method transforms |
@@ -91,7 +97,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
91 | variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant)); | 97 | variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant)); |
92 | ast::Pat::from(make::tuple_pat(patterns)) | 98 | ast::Pat::from(make::tuple_pat(patterns)) |
93 | }) | 99 | }) |
94 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) | 100 | .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat)) |
95 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) | 101 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) |
96 | .collect() | 102 | .collect() |
97 | } else { | 103 | } else { |
@@ -134,61 +140,114 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
134 | ) | 140 | ) |
135 | } | 141 | } |
136 | 142 | ||
137 | fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool { | 143 | fn is_variant_missing(existing_pats: &[Pat], var: &Pat) -> bool { |
138 | existing_arms.iter().filter_map(|arm| arm.pat()).all(|pat| { | 144 | !existing_pats.iter().any(|pat| does_pat_match_variant(pat, var)) |
139 | // Special casee OrPat as separate top-level pats | 145 | } |
140 | let top_level_pats: Vec<Pat> = match pat { | ||
141 | Pat::OrPat(pats) => pats.pats().collect::<Vec<_>>(), | ||
142 | _ => vec![pat], | ||
143 | }; | ||
144 | 146 | ||
145 | !top_level_pats.iter().any(|pat| does_pat_match_variant(pat, var)) | 147 | // Fixme: this is still somewhat limited, use hir_ty::diagnostics::match_check? |
146 | }) | 148 | fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool { |
149 | match (pat, var) { | ||
150 | (Pat::WildcardPat(_), _) => true, | ||
151 | (Pat::TuplePat(tpat), Pat::TuplePat(tvar)) => { | ||
152 | tpat.fields().zip(tvar.fields()).all(|(p, v)| does_pat_match_variant(&p, &v)) | ||
153 | } | ||
154 | _ => utils::does_pat_match_variant(pat, var), | ||
155 | } | ||
156 | } | ||
157 | |||
158 | #[derive(Eq, PartialEq, Clone)] | ||
159 | enum ExtendedEnum { | ||
160 | Bool, | ||
161 | Enum(hir::Enum), | ||
162 | } | ||
163 | |||
164 | #[derive(Eq, PartialEq, Clone)] | ||
165 | enum ExtendedVariant { | ||
166 | True, | ||
167 | False, | ||
168 | Variant(hir::Variant), | ||
169 | } | ||
170 | |||
171 | fn lift_enum(e: hir::Enum) -> ExtendedEnum { | ||
172 | ExtendedEnum::Enum(e) | ||
173 | } | ||
174 | |||
175 | impl ExtendedEnum { | ||
176 | fn variants(&self, db: &RootDatabase) -> Vec<ExtendedVariant> { | ||
177 | match self { | ||
178 | ExtendedEnum::Enum(e) => { | ||
179 | e.variants(db).into_iter().map(|x| ExtendedVariant::Variant(x)).collect::<Vec<_>>() | ||
180 | } | ||
181 | ExtendedEnum::Bool => { | ||
182 | Vec::<ExtendedVariant>::from([ExtendedVariant::True, ExtendedVariant::False]) | ||
183 | } | ||
184 | } | ||
185 | } | ||
147 | } | 186 | } |
148 | 187 | ||
149 | fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> { | 188 | fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<ExtendedEnum> { |
150 | sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() { | 189 | sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() { |
151 | Some(Adt::Enum(e)) => Some(e), | 190 | Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)), |
152 | _ => None, | 191 | _ => { |
192 | if ty.is_bool() { | ||
193 | Some(ExtendedEnum::Bool) | ||
194 | } else { | ||
195 | None | ||
196 | } | ||
197 | } | ||
153 | }) | 198 | }) |
154 | } | 199 | } |
155 | 200 | ||
156 | fn resolve_tuple_of_enum_def( | 201 | fn resolve_tuple_of_enum_def( |
157 | sema: &Semantics<RootDatabase>, | 202 | sema: &Semantics<RootDatabase>, |
158 | expr: &ast::Expr, | 203 | expr: &ast::Expr, |
159 | ) -> Option<Vec<hir::Enum>> { | 204 | ) -> Option<Vec<ExtendedEnum>> { |
160 | sema.type_of_expr(&expr)? | 205 | sema.type_of_expr(&expr)? |
161 | .tuple_fields(sema.db) | 206 | .tuple_fields(sema.db) |
162 | .iter() | 207 | .iter() |
163 | .map(|ty| { | 208 | .map(|ty| { |
164 | ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() { | 209 | ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() { |
165 | Some(Adt::Enum(e)) => Some(e), | 210 | Some(Adt::Enum(e)) => Some(lift_enum(e)), |
166 | // For now we only handle expansion for a tuple of enums. Here | 211 | // For now we only handle expansion for a tuple of enums. Here |
167 | // we map non-enum items to None and rely on `collect` to | 212 | // we map non-enum items to None and rely on `collect` to |
168 | // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>. | 213 | // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>. |
169 | _ => None, | 214 | _ => { |
215 | if ty.is_bool() { | ||
216 | Some(ExtendedEnum::Bool) | ||
217 | } else { | ||
218 | None | ||
219 | } | ||
220 | } | ||
170 | }) | 221 | }) |
171 | }) | 222 | }) |
172 | .collect() | 223 | .collect() |
173 | } | 224 | } |
174 | 225 | ||
175 | fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::Variant) -> Option<ast::Pat> { | 226 | fn build_pat(db: &RootDatabase, module: hir::Module, var: ExtendedVariant) -> Option<ast::Pat> { |
176 | let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?); | 227 | match var { |
228 | ExtendedVariant::Variant(var) => { | ||
229 | let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?); | ||
230 | |||
231 | // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though | ||
232 | let pat: ast::Pat = match var.source(db)?.value.kind() { | ||
233 | ast::StructKind::Tuple(field_list) => { | ||
234 | let pats = | ||
235 | iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count()); | ||
236 | make::tuple_struct_pat(path, pats).into() | ||
237 | } | ||
238 | ast::StructKind::Record(field_list) => { | ||
239 | let pats = | ||
240 | field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into()); | ||
241 | make::record_pat(path, pats).into() | ||
242 | } | ||
243 | ast::StructKind::Unit => make::path_pat(path), | ||
244 | }; | ||
177 | 245 | ||
178 | // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though | 246 | Some(pat) |
179 | let pat: ast::Pat = match var.source(db)?.value.kind() { | ||
180 | ast::StructKind::Tuple(field_list) => { | ||
181 | let pats = iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count()); | ||
182 | make::tuple_struct_pat(path, pats).into() | ||
183 | } | ||
184 | ast::StructKind::Record(field_list) => { | ||
185 | let pats = field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into()); | ||
186 | make::record_pat(path, pats).into() | ||
187 | } | 247 | } |
188 | ast::StructKind::Unit => make::path_pat(path), | 248 | ExtendedVariant::True => Some(ast::Pat::from(make::literal_pat("true"))), |
189 | }; | 249 | ExtendedVariant::False => Some(ast::Pat::from(make::literal_pat("false"))), |
190 | 250 | } | |
191 | Some(pat) | ||
192 | } | 251 | } |
193 | 252 | ||
194 | #[cfg(test)] | 253 | #[cfg(test)] |
@@ -221,6 +280,21 @@ mod tests { | |||
221 | } | 280 | } |
222 | 281 | ||
223 | #[test] | 282 | #[test] |
283 | fn all_boolean_match_arms_provided() { | ||
284 | check_assist_not_applicable( | ||
285 | fill_match_arms, | ||
286 | r#" | ||
287 | fn foo(a: bool) { | ||
288 | match a$0 { | ||
289 | true => {} | ||
290 | false => {} | ||
291 | } | ||
292 | } | ||
293 | "#, | ||
294 | ) | ||
295 | } | ||
296 | |||
297 | #[test] | ||
224 | fn tuple_of_non_enum() { | 298 | fn tuple_of_non_enum() { |
225 | // for now this case is not handled, although it potentially could be | 299 | // for now this case is not handled, although it potentially could be |
226 | // in the future | 300 | // in the future |
@@ -236,6 +310,113 @@ mod tests { | |||
236 | } | 310 | } |
237 | 311 | ||
238 | #[test] | 312 | #[test] |
313 | fn fill_match_arms_boolean() { | ||
314 | check_assist( | ||
315 | fill_match_arms, | ||
316 | r#" | ||
317 | fn foo(a: bool) { | ||
318 | match a$0 { | ||
319 | } | ||
320 | } | ||
321 | "#, | ||
322 | r#" | ||
323 | fn foo(a: bool) { | ||
324 | match a { | ||
325 | $0true => {} | ||
326 | false => {} | ||
327 | } | ||
328 | } | ||
329 | "#, | ||
330 | ) | ||
331 | } | ||
332 | |||
333 | #[test] | ||
334 | fn partial_fill_boolean() { | ||
335 | check_assist( | ||
336 | fill_match_arms, | ||
337 | r#" | ||
338 | fn foo(a: bool) { | ||
339 | match a$0 { | ||
340 | true => {} | ||
341 | } | ||
342 | } | ||
343 | "#, | ||
344 | r#" | ||
345 | fn foo(a: bool) { | ||
346 | match a { | ||
347 | true => {} | ||
348 | $0false => {} | ||
349 | } | ||
350 | } | ||
351 | "#, | ||
352 | ) | ||
353 | } | ||
354 | |||
355 | #[test] | ||
356 | fn all_boolean_tuple_arms_provided() { | ||
357 | check_assist_not_applicable( | ||
358 | fill_match_arms, | ||
359 | r#" | ||
360 | fn foo(a: bool) { | ||
361 | match (a, a)$0 { | ||
362 | (true, true) => {} | ||
363 | (true, false) => {} | ||
364 | (false, true) => {} | ||
365 | (false, false) => {} | ||
366 | } | ||
367 | } | ||
368 | "#, | ||
369 | ) | ||
370 | } | ||
371 | |||
372 | #[test] | ||
373 | fn fill_boolean_tuple() { | ||
374 | check_assist( | ||
375 | fill_match_arms, | ||
376 | r#" | ||
377 | fn foo(a: bool) { | ||
378 | match (a, a)$0 { | ||
379 | } | ||
380 | } | ||
381 | "#, | ||
382 | r#" | ||
383 | fn foo(a: bool) { | ||
384 | match (a, a) { | ||
385 | $0(true, true) => {} | ||
386 | (true, false) => {} | ||
387 | (false, true) => {} | ||
388 | (false, false) => {} | ||
389 | } | ||
390 | } | ||
391 | "#, | ||
392 | ) | ||
393 | } | ||
394 | |||
395 | #[test] | ||
396 | fn partial_fill_boolean_tuple() { | ||
397 | check_assist( | ||
398 | fill_match_arms, | ||
399 | r#" | ||
400 | fn foo(a: bool) { | ||
401 | match (a, a)$0 { | ||
402 | (false, true) => {} | ||
403 | } | ||
404 | } | ||
405 | "#, | ||
406 | r#" | ||
407 | fn foo(a: bool) { | ||
408 | match (a, a) { | ||
409 | (false, true) => {} | ||
410 | $0(true, true) => {} | ||
411 | (true, false) => {} | ||
412 | (false, false) => {} | ||
413 | } | ||
414 | } | ||
415 | "#, | ||
416 | ) | ||
417 | } | ||
418 | |||
419 | #[test] | ||
239 | fn partial_fill_record_tuple() { | 420 | fn partial_fill_record_tuple() { |
240 | check_assist( | 421 | check_assist( |
241 | fill_match_arms, | 422 | fill_match_arms, |
@@ -473,20 +654,81 @@ fn main() { | |||
473 | 654 | ||
474 | #[test] | 655 | #[test] |
475 | fn fill_match_arms_tuple_of_enum_partial() { | 656 | fn fill_match_arms_tuple_of_enum_partial() { |
476 | check_assist_not_applicable( | 657 | check_assist( |
477 | fill_match_arms, | 658 | fill_match_arms, |
478 | r#" | 659 | r#" |
479 | enum A { One, Two } | 660 | enum A { One, Two } |
480 | enum B { One, Two } | 661 | enum B { One, Two } |
481 | 662 | ||
482 | fn main() { | 663 | fn main() { |
483 | let a = A::One; | 664 | let a = A::One; |
484 | let b = B::One; | 665 | let b = B::One; |
485 | match (a$0, b) { | 666 | match (a$0, b) { |
486 | (A::Two, B::One) => {} | 667 | (A::Two, B::One) => {} |
487 | } | 668 | } |
488 | } | 669 | } |
489 | "#, | 670 | "#, |
671 | r#" | ||
672 | enum A { One, Two } | ||
673 | enum B { One, Two } | ||
674 | |||
675 | fn main() { | ||
676 | let a = A::One; | ||
677 | let b = B::One; | ||
678 | match (a, b) { | ||
679 | (A::Two, B::One) => {} | ||
680 | $0(A::One, B::One) => {} | ||
681 | (A::One, B::Two) => {} | ||
682 | (A::Two, B::Two) => {} | ||
683 | } | ||
684 | } | ||
685 | "#, | ||
686 | ); | ||
687 | } | ||
688 | |||
689 | #[test] | ||
690 | fn fill_match_arms_tuple_of_enum_partial_with_wildcards() { | ||
691 | let ra_fixture = r#" | ||
692 | fn main() { | ||
693 | let a = Some(1); | ||
694 | let b = Some(()); | ||
695 | match (a$0, b) { | ||
696 | (Some(_), _) => {} | ||
697 | (None, Some(_)) => {} | ||
698 | } | ||
699 | } | ||
700 | "#; | ||
701 | check_assist( | ||
702 | fill_match_arms, | ||
703 | &format!("//- /main.rs crate:main deps:core{}{}", ra_fixture, FamousDefs::FIXTURE), | ||
704 | r#" | ||
705 | fn main() { | ||
706 | let a = Some(1); | ||
707 | let b = Some(()); | ||
708 | match (a, b) { | ||
709 | (Some(_), _) => {} | ||
710 | (None, Some(_)) => {} | ||
711 | $0(None, None) => {} | ||
712 | } | ||
713 | } | ||
714 | "#, | ||
715 | ); | ||
716 | } | ||
717 | |||
718 | #[test] | ||
719 | fn fill_match_arms_partial_with_deep_pattern() { | ||
720 | // Fixme: cannot handle deep patterns | ||
721 | let ra_fixture = r#" | ||
722 | fn main() { | ||
723 | match $0Some(true) { | ||
724 | Some(true) => {} | ||
725 | None => {} | ||
726 | } | ||
727 | } | ||
728 | "#; | ||
729 | check_assist_not_applicable( | ||
730 | fill_match_arms, | ||
731 | &format!("//- /main.rs crate:main deps:core{}{}", ra_fixture, FamousDefs::FIXTURE), | ||
490 | ); | 732 | ); |
491 | } | 733 | } |
492 | 734 | ||
@@ -514,10 +756,7 @@ fn main() { | |||
514 | 756 | ||
515 | #[test] | 757 | #[test] |
516 | fn fill_match_arms_single_element_tuple_of_enum() { | 758 | fn fill_match_arms_single_element_tuple_of_enum() { |
517 | // For now we don't hande the case of a single element tuple, but | 759 | 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, | 760 | fill_match_arms, |
522 | r#" | 761 | r#" |
523 | enum A { One, Two } | 762 | enum A { One, Two } |
@@ -528,6 +767,17 @@ fn main() { | |||
528 | } | 767 | } |
529 | } | 768 | } |
530 | "#, | 769 | "#, |
770 | r#" | ||
771 | enum A { One, Two } | ||
772 | |||
773 | fn main() { | ||
774 | let a = A::One; | ||
775 | match (a, ) { | ||
776 | $0(A::One,) => {} | ||
777 | (A::Two,) => {} | ||
778 | } | ||
779 | } | ||
780 | "#, | ||
531 | ); | 781 | ); |
532 | } | 782 | } |
533 | 783 | ||
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_deref.rs b/crates/ide_assists/src/handlers/generate_deref.rs new file mode 100644 index 000000000..4998ff7a4 --- /dev/null +++ b/crates/ide_assists/src/handlers/generate_deref.rs | |||
@@ -0,0 +1,227 @@ | |||
1 | use std::fmt::Display; | ||
2 | |||
3 | use ide_db::{helpers::FamousDefs, RootDatabase}; | ||
4 | use syntax::{ | ||
5 | ast::{self, NameOwner}, | ||
6 | AstNode, SyntaxNode, | ||
7 | }; | ||
8 | |||
9 | use crate::{ | ||
10 | assist_context::{AssistBuilder, AssistContext, Assists}, | ||
11 | utils::generate_trait_impl_text, | ||
12 | AssistId, AssistKind, | ||
13 | }; | ||
14 | |||
15 | // Assist: generate_deref | ||
16 | // | ||
17 | // Generate `Deref` impl using the given struct field. | ||
18 | // | ||
19 | // ``` | ||
20 | // struct A; | ||
21 | // struct B { | ||
22 | // $0a: A | ||
23 | // } | ||
24 | // ``` | ||
25 | // -> | ||
26 | // ``` | ||
27 | // struct A; | ||
28 | // struct B { | ||
29 | // a: A | ||
30 | // } | ||
31 | // | ||
32 | // impl std::ops::Deref for B { | ||
33 | // type Target = A; | ||
34 | // | ||
35 | // fn deref(&self) -> &Self::Target { | ||
36 | // &self.a | ||
37 | // } | ||
38 | // } | ||
39 | // ``` | ||
40 | pub(crate) fn generate_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
41 | generate_record_deref(acc, ctx).or_else(|| generate_tuple_deref(acc, ctx)) | ||
42 | } | ||
43 | |||
44 | fn generate_record_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
45 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; | ||
46 | let field = ctx.find_node_at_offset::<ast::RecordField>()?; | ||
47 | |||
48 | if existing_deref_impl(&ctx.sema, &strukt).is_some() { | ||
49 | cov_mark::hit!(test_add_record_deref_impl_already_exists); | ||
50 | return None; | ||
51 | } | ||
52 | |||
53 | let field_type = field.ty()?; | ||
54 | let field_name = field.name()?; | ||
55 | let target = field.syntax().text_range(); | ||
56 | acc.add( | ||
57 | AssistId("generate_deref", AssistKind::Generate), | ||
58 | format!("Generate `Deref` impl using `{}`", field_name), | ||
59 | target, | ||
60 | |edit| generate_edit(edit, strukt, field_type.syntax(), field_name.syntax()), | ||
61 | ) | ||
62 | } | ||
63 | |||
64 | fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
65 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; | ||
66 | let field = ctx.find_node_at_offset::<ast::TupleField>()?; | ||
67 | let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?; | ||
68 | let field_list_index = | ||
69 | field_list.syntax().children().into_iter().position(|s| &s == field.syntax())?; | ||
70 | |||
71 | if existing_deref_impl(&ctx.sema, &strukt).is_some() { | ||
72 | cov_mark::hit!(test_add_field_deref_impl_already_exists); | ||
73 | return None; | ||
74 | } | ||
75 | |||
76 | let field_type = field.ty()?; | ||
77 | let target = field.syntax().text_range(); | ||
78 | acc.add( | ||
79 | AssistId("generate_deref", AssistKind::Generate), | ||
80 | format!("Generate `Deref` impl using `{}`", field.syntax()), | ||
81 | target, | ||
82 | |edit| generate_edit(edit, strukt, field_type.syntax(), field_list_index), | ||
83 | ) | ||
84 | } | ||
85 | |||
86 | fn generate_edit( | ||
87 | edit: &mut AssistBuilder, | ||
88 | strukt: ast::Struct, | ||
89 | field_type_syntax: &SyntaxNode, | ||
90 | field_name: impl Display, | ||
91 | ) { | ||
92 | let start_offset = strukt.syntax().text_range().end(); | ||
93 | let impl_code = format!( | ||
94 | r#" type Target = {0}; | ||
95 | |||
96 | fn deref(&self) -> &Self::Target {{ | ||
97 | &self.{1} | ||
98 | }}"#, | ||
99 | field_type_syntax, field_name | ||
100 | ); | ||
101 | let strukt_adt = ast::Adt::Struct(strukt); | ||
102 | let deref_impl = generate_trait_impl_text(&strukt_adt, "std::ops::Deref", &impl_code); | ||
103 | edit.insert(start_offset, deref_impl); | ||
104 | } | ||
105 | |||
106 | fn existing_deref_impl( | ||
107 | sema: &'_ hir::Semantics<'_, RootDatabase>, | ||
108 | strukt: &ast::Struct, | ||
109 | ) -> Option<()> { | ||
110 | let strukt = sema.to_def(strukt)?; | ||
111 | let krate = strukt.module(sema.db).krate(); | ||
112 | |||
113 | let deref_trait = FamousDefs(sema, Some(krate)).core_ops_Deref()?; | ||
114 | let strukt_type = strukt.ty(sema.db); | ||
115 | |||
116 | if strukt_type.impls_trait(sema.db, deref_trait, &[]) { | ||
117 | Some(()) | ||
118 | } else { | ||
119 | None | ||
120 | } | ||
121 | } | ||
122 | |||
123 | #[cfg(test)] | ||
124 | mod tests { | ||
125 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
126 | |||
127 | use super::*; | ||
128 | |||
129 | #[test] | ||
130 | fn test_generate_record_deref() { | ||
131 | check_assist( | ||
132 | generate_deref, | ||
133 | r#"struct A { } | ||
134 | struct B { $0a: A }"#, | ||
135 | r#"struct A { } | ||
136 | struct B { a: A } | ||
137 | |||
138 | impl std::ops::Deref for B { | ||
139 | type Target = A; | ||
140 | |||
141 | fn deref(&self) -> &Self::Target { | ||
142 | &self.a | ||
143 | } | ||
144 | }"#, | ||
145 | ); | ||
146 | } | ||
147 | |||
148 | #[test] | ||
149 | fn test_generate_field_deref_idx_0() { | ||
150 | check_assist( | ||
151 | generate_deref, | ||
152 | r#"struct A { } | ||
153 | struct B($0A);"#, | ||
154 | r#"struct A { } | ||
155 | struct B(A); | ||
156 | |||
157 | impl std::ops::Deref for B { | ||
158 | type Target = A; | ||
159 | |||
160 | fn deref(&self) -> &Self::Target { | ||
161 | &self.0 | ||
162 | } | ||
163 | }"#, | ||
164 | ); | ||
165 | } | ||
166 | #[test] | ||
167 | fn test_generate_field_deref_idx_1() { | ||
168 | check_assist( | ||
169 | generate_deref, | ||
170 | r#"struct A { } | ||
171 | struct B(u8, $0A);"#, | ||
172 | r#"struct A { } | ||
173 | struct B(u8, A); | ||
174 | |||
175 | impl std::ops::Deref for B { | ||
176 | type Target = A; | ||
177 | |||
178 | fn deref(&self) -> &Self::Target { | ||
179 | &self.1 | ||
180 | } | ||
181 | }"#, | ||
182 | ); | ||
183 | } | ||
184 | |||
185 | fn check_not_applicable(ra_fixture: &str) { | ||
186 | let fixture = format!( | ||
187 | "//- /main.rs crate:main deps:core,std\n{}\n{}", | ||
188 | ra_fixture, | ||
189 | FamousDefs::FIXTURE | ||
190 | ); | ||
191 | check_assist_not_applicable(generate_deref, &fixture) | ||
192 | } | ||
193 | |||
194 | #[test] | ||
195 | fn test_generate_record_deref_not_applicable_if_already_impl() { | ||
196 | cov_mark::check!(test_add_record_deref_impl_already_exists); | ||
197 | check_not_applicable( | ||
198 | r#"struct A { } | ||
199 | struct B { $0a: A } | ||
200 | |||
201 | impl std::ops::Deref for B { | ||
202 | type Target = A; | ||
203 | |||
204 | fn deref(&self) -> &Self::Target { | ||
205 | &self.a | ||
206 | } | ||
207 | }"#, | ||
208 | ) | ||
209 | } | ||
210 | |||
211 | #[test] | ||
212 | fn test_generate_field_deref_not_applicable_if_already_impl() { | ||
213 | cov_mark::check!(test_add_field_deref_impl_already_exists); | ||
214 | check_not_applicable( | ||
215 | r#"struct A { } | ||
216 | struct B($0A) | ||
217 | |||
218 | impl std::ops::Deref for B { | ||
219 | type Target = A; | ||
220 | |||
221 | fn deref(&self) -> &Self::Target { | ||
222 | &self.0 | ||
223 | } | ||
224 | }"#, | ||
225 | ) | ||
226 | } | ||
227 | } | ||
diff --git a/crates/ide_assists/src/handlers/inline_local_variable.rs b/crates/ide_assists/src/handlers/inline_local_variable.rs index ea1466dc8..f5dafc8cb 100644 --- a/crates/ide_assists/src/handlers/inline_local_variable.rs +++ b/crates/ide_assists/src/handlers/inline_local_variable.rs | |||
@@ -1,7 +1,9 @@ | |||
1 | use ide_db::{defs::Definition, search::FileReference}; | 1 | use either::Either; |
2 | use hir::PathResolution; | ||
3 | use ide_db::{base_db::FileId, defs::Definition, search::FileReference}; | ||
2 | use rustc_hash::FxHashMap; | 4 | use rustc_hash::FxHashMap; |
3 | use syntax::{ | 5 | use syntax::{ |
4 | ast::{self, AstNode, AstToken}, | 6 | ast::{self, AstNode, AstToken, NameOwner}, |
5 | TextRange, | 7 | TextRange, |
6 | }; | 8 | }; |
7 | 9 | ||
@@ -27,44 +29,28 @@ use crate::{ | |||
27 | // } | 29 | // } |
28 | // ``` | 30 | // ``` |
29 | pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 31 | pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
30 | let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; | 32 | let InlineData { let_stmt, delete_let, replace_usages, target } = |
31 | let bind_pat = match let_stmt.pat()? { | 33 | inline_let(ctx).or_else(|| inline_usage(ctx))?; |
32 | ast::Pat::IdentPat(pat) => pat, | ||
33 | _ => return None, | ||
34 | }; | ||
35 | if bind_pat.mut_token().is_some() { | ||
36 | cov_mark::hit!(test_not_inline_mut_variable); | ||
37 | return None; | ||
38 | } | ||
39 | if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) { | ||
40 | cov_mark::hit!(not_applicable_outside_of_bind_pat); | ||
41 | return None; | ||
42 | } | ||
43 | let initializer_expr = let_stmt.initializer()?; | 34 | let initializer_expr = let_stmt.initializer()?; |
44 | 35 | ||
45 | let def = ctx.sema.to_def(&bind_pat)?; | 36 | let delete_range = if delete_let { |
46 | let def = Definition::Local(def); | 37 | if let Some(whitespace) = let_stmt |
47 | let usages = def.usages(&ctx.sema).all(); | 38 | .syntax() |
48 | if usages.is_empty() { | 39 | .next_sibling_or_token() |
49 | cov_mark::hit!(test_not_applicable_if_variable_unused); | 40 | .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone())) |
50 | return None; | 41 | { |
51 | }; | 42 | Some(TextRange::new( |
52 | 43 | let_stmt.syntax().text_range().start(), | |
53 | let delete_range = if let Some(whitespace) = let_stmt | 44 | whitespace.syntax().text_range().end(), |
54 | .syntax() | 45 | )) |
55 | .next_sibling_or_token() | 46 | } else { |
56 | .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone())) | 47 | Some(let_stmt.syntax().text_range()) |
57 | { | 48 | } |
58 | TextRange::new( | ||
59 | let_stmt.syntax().text_range().start(), | ||
60 | whitespace.syntax().text_range().end(), | ||
61 | ) | ||
62 | } else { | 49 | } else { |
63 | let_stmt.syntax().text_range() | 50 | None |
64 | }; | 51 | }; |
65 | 52 | ||
66 | let wrap_in_parens = usages | 53 | let wrap_in_parens = replace_usages |
67 | .references | ||
68 | .iter() | 54 | .iter() |
69 | .map(|(&file_id, refs)| { | 55 | .map(|(&file_id, refs)| { |
70 | refs.iter() | 56 | refs.iter() |
@@ -114,14 +100,20 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O | |||
114 | let init_str = initializer_expr.syntax().text().to_string(); | 100 | let init_str = initializer_expr.syntax().text().to_string(); |
115 | let init_in_paren = format!("({})", &init_str); | 101 | let init_in_paren = format!("({})", &init_str); |
116 | 102 | ||
117 | let target = bind_pat.syntax().text_range(); | 103 | let target = match target { |
104 | ast::NameOrNameRef::Name(it) => it.syntax().text_range(), | ||
105 | ast::NameOrNameRef::NameRef(it) => it.syntax().text_range(), | ||
106 | }; | ||
107 | |||
118 | acc.add( | 108 | acc.add( |
119 | AssistId("inline_local_variable", AssistKind::RefactorInline), | 109 | AssistId("inline_local_variable", AssistKind::RefactorInline), |
120 | "Inline variable", | 110 | "Inline variable", |
121 | target, | 111 | target, |
122 | move |builder| { | 112 | move |builder| { |
123 | builder.delete(delete_range); | 113 | if let Some(range) = delete_range { |
124 | for (file_id, references) in usages.references { | 114 | builder.delete(range); |
115 | } | ||
116 | for (file_id, references) in replace_usages { | ||
125 | for (&should_wrap, reference) in wrap_in_parens[&file_id].iter().zip(references) { | 117 | for (&should_wrap, reference) in wrap_in_parens[&file_id].iter().zip(references) { |
126 | let replacement = | 118 | let replacement = |
127 | if should_wrap { init_in_paren.clone() } else { init_str.clone() }; | 119 | if should_wrap { init_in_paren.clone() } else { init_str.clone() }; |
@@ -140,6 +132,81 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O | |||
140 | ) | 132 | ) |
141 | } | 133 | } |
142 | 134 | ||
135 | struct InlineData { | ||
136 | let_stmt: ast::LetStmt, | ||
137 | delete_let: bool, | ||
138 | target: ast::NameOrNameRef, | ||
139 | replace_usages: FxHashMap<FileId, Vec<FileReference>>, | ||
140 | } | ||
141 | |||
142 | fn inline_let(ctx: &AssistContext) -> Option<InlineData> { | ||
143 | let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; | ||
144 | let bind_pat = match let_stmt.pat()? { | ||
145 | ast::Pat::IdentPat(pat) => pat, | ||
146 | _ => return None, | ||
147 | }; | ||
148 | if bind_pat.mut_token().is_some() { | ||
149 | cov_mark::hit!(test_not_inline_mut_variable); | ||
150 | return None; | ||
151 | } | ||
152 | if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) { | ||
153 | cov_mark::hit!(not_applicable_outside_of_bind_pat); | ||
154 | return None; | ||
155 | } | ||
156 | |||
157 | let def = ctx.sema.to_def(&bind_pat)?; | ||
158 | let def = Definition::Local(def); | ||
159 | let usages = def.usages(&ctx.sema).all(); | ||
160 | if usages.is_empty() { | ||
161 | cov_mark::hit!(test_not_applicable_if_variable_unused); | ||
162 | return None; | ||
163 | }; | ||
164 | |||
165 | Some(InlineData { | ||
166 | let_stmt, | ||
167 | delete_let: true, | ||
168 | target: ast::NameOrNameRef::Name(bind_pat.name()?), | ||
169 | replace_usages: usages.references, | ||
170 | }) | ||
171 | } | ||
172 | |||
173 | fn inline_usage(ctx: &AssistContext) -> Option<InlineData> { | ||
174 | let path_expr = ctx.find_node_at_offset::<ast::PathExpr>()?; | ||
175 | let path = path_expr.path()?; | ||
176 | let name = match path.as_single_segment()?.kind()? { | ||
177 | ast::PathSegmentKind::Name(name) => name, | ||
178 | _ => return None, | ||
179 | }; | ||
180 | |||
181 | let local = match ctx.sema.resolve_path(&path)? { | ||
182 | PathResolution::Local(local) => local, | ||
183 | _ => return None, | ||
184 | }; | ||
185 | |||
186 | let bind_pat = match local.source(ctx.db()).value { | ||
187 | Either::Left(ident) => ident, | ||
188 | _ => return None, | ||
189 | }; | ||
190 | |||
191 | let let_stmt = ast::LetStmt::cast(bind_pat.syntax().parent()?)?; | ||
192 | |||
193 | let def = Definition::Local(local); | ||
194 | let mut usages = def.usages(&ctx.sema).all(); | ||
195 | |||
196 | let delete_let = usages.references.values().map(|v| v.len()).sum::<usize>() == 1; | ||
197 | |||
198 | for references in usages.references.values_mut() { | ||
199 | references.retain(|reference| reference.name.as_name_ref() == Some(&name)); | ||
200 | } | ||
201 | |||
202 | Some(InlineData { | ||
203 | let_stmt, | ||
204 | delete_let, | ||
205 | target: ast::NameOrNameRef::NameRef(name), | ||
206 | replace_usages: usages.references, | ||
207 | }) | ||
208 | } | ||
209 | |||
143 | #[cfg(test)] | 210 | #[cfg(test)] |
144 | mod tests { | 211 | mod tests { |
145 | use crate::tests::{check_assist, check_assist_not_applicable}; | 212 | use crate::tests::{check_assist, check_assist_not_applicable}; |
@@ -726,4 +793,84 @@ fn main() { | |||
726 | ", | 793 | ", |
727 | ) | 794 | ) |
728 | } | 795 | } |
796 | |||
797 | #[test] | ||
798 | fn works_on_local_usage() { | ||
799 | check_assist( | ||
800 | inline_local_variable, | ||
801 | r#" | ||
802 | fn f() { | ||
803 | let xyz = 0; | ||
804 | xyz$0; | ||
805 | } | ||
806 | "#, | ||
807 | r#" | ||
808 | fn f() { | ||
809 | 0; | ||
810 | } | ||
811 | "#, | ||
812 | ); | ||
813 | } | ||
814 | |||
815 | #[test] | ||
816 | fn does_not_remove_let_when_multiple_usages() { | ||
817 | check_assist( | ||
818 | inline_local_variable, | ||
819 | r#" | ||
820 | fn f() { | ||
821 | let xyz = 0; | ||
822 | xyz$0; | ||
823 | xyz; | ||
824 | } | ||
825 | "#, | ||
826 | r#" | ||
827 | fn f() { | ||
828 | let xyz = 0; | ||
829 | 0; | ||
830 | xyz; | ||
831 | } | ||
832 | "#, | ||
833 | ); | ||
834 | } | ||
835 | |||
836 | #[test] | ||
837 | fn not_applicable_with_non_ident_pattern() { | ||
838 | check_assist_not_applicable( | ||
839 | inline_local_variable, | ||
840 | r#" | ||
841 | fn main() { | ||
842 | let (x, y) = (0, 1); | ||
843 | x$0; | ||
844 | } | ||
845 | "#, | ||
846 | ); | ||
847 | } | ||
848 | |||
849 | #[test] | ||
850 | fn not_applicable_on_local_usage_in_macro() { | ||
851 | check_assist_not_applicable( | ||
852 | inline_local_variable, | ||
853 | r#" | ||
854 | macro_rules! m { | ||
855 | ($i:ident) => { $i } | ||
856 | } | ||
857 | fn f() { | ||
858 | let xyz = 0; | ||
859 | m!(xyz$0); // replacing it would break the macro | ||
860 | } | ||
861 | "#, | ||
862 | ); | ||
863 | check_assist_not_applicable( | ||
864 | inline_local_variable, | ||
865 | r#" | ||
866 | macro_rules! m { | ||
867 | ($i:ident) => { $i } | ||
868 | } | ||
869 | fn f() { | ||
870 | let xyz$0 = 0; | ||
871 | m!(xyz); // replacing it would break the macro | ||
872 | } | ||
873 | "#, | ||
874 | ); | ||
875 | } | ||
729 | } | 876 | } |
diff --git a/crates/ide_assists/src/handlers/introduce_named_lifetime.rs b/crates/ide_assists/src/handlers/introduce_named_lifetime.rs index 02782eb6d..9f4f71d6c 100644 --- a/crates/ide_assists/src/handlers/introduce_named_lifetime.rs +++ b/crates/ide_assists/src/handlers/introduce_named_lifetime.rs | |||
@@ -1,7 +1,8 @@ | |||
1 | use rustc_hash::FxHashSet; | 1 | use rustc_hash::FxHashSet; |
2 | use syntax::{ | 2 | use syntax::{ |
3 | ast::{self, GenericParamsOwner, NameOwner}, | 3 | ast::{self, edit_in_place::GenericParamsOwnerEdit, make, GenericParamsOwner}, |
4 | AstNode, TextRange, TextSize, | 4 | ted::{self, Position}, |
5 | AstNode, TextRange, | ||
5 | }; | 6 | }; |
6 | 7 | ||
7 | use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists}; | 8 | use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists}; |
@@ -37,10 +38,12 @@ static ASSIST_LABEL: &str = "Introduce named lifetime"; | |||
37 | pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 38 | pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
38 | let lifetime = | 39 | let lifetime = |
39 | ctx.find_node_at_offset::<ast::Lifetime>().filter(|lifetime| lifetime.text() == "'_")?; | 40 | ctx.find_node_at_offset::<ast::Lifetime>().filter(|lifetime| lifetime.text() == "'_")?; |
41 | let lifetime_loc = lifetime.lifetime_ident_token()?.text_range(); | ||
42 | |||
40 | if let Some(fn_def) = lifetime.syntax().ancestors().find_map(ast::Fn::cast) { | 43 | if let Some(fn_def) = lifetime.syntax().ancestors().find_map(ast::Fn::cast) { |
41 | generate_fn_def_assist(acc, &fn_def, lifetime.lifetime_ident_token()?.text_range()) | 44 | generate_fn_def_assist(acc, fn_def, lifetime_loc, lifetime) |
42 | } else if let Some(impl_def) = lifetime.syntax().ancestors().find_map(ast::Impl::cast) { | 45 | } else if let Some(impl_def) = lifetime.syntax().ancestors().find_map(ast::Impl::cast) { |
43 | generate_impl_def_assist(acc, &impl_def, lifetime.lifetime_ident_token()?.text_range()) | 46 | generate_impl_def_assist(acc, impl_def, lifetime_loc, lifetime) |
44 | } else { | 47 | } else { |
45 | None | 48 | None |
46 | } | 49 | } |
@@ -49,26 +52,26 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) - | |||
49 | /// Generate the assist for the fn def case | 52 | /// Generate the assist for the fn def case |
50 | fn generate_fn_def_assist( | 53 | fn generate_fn_def_assist( |
51 | acc: &mut Assists, | 54 | acc: &mut Assists, |
52 | fn_def: &ast::Fn, | 55 | fn_def: ast::Fn, |
53 | lifetime_loc: TextRange, | 56 | lifetime_loc: TextRange, |
57 | lifetime: ast::Lifetime, | ||
54 | ) -> Option<()> { | 58 | ) -> Option<()> { |
55 | let param_list: ast::ParamList = fn_def.param_list()?; | 59 | let param_list: ast::ParamList = fn_def.param_list()?; |
56 | let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.generic_param_list())?; | 60 | let new_lifetime_param = generate_unique_lifetime_param_name(fn_def.generic_param_list())?; |
57 | let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end(); | ||
58 | let self_param = | 61 | let self_param = |
59 | // use the self if it's a reference and has no explicit lifetime | 62 | // use the self if it's a reference and has no explicit lifetime |
60 | param_list.self_param().filter(|p| p.lifetime().is_none() && p.amp_token().is_some()); | 63 | param_list.self_param().filter(|p| p.lifetime().is_none() && p.amp_token().is_some()); |
61 | // compute the location which implicitly has the same lifetime as the anonymous lifetime | 64 | // compute the location which implicitly has the same lifetime as the anonymous lifetime |
62 | let loc_needing_lifetime = if let Some(self_param) = self_param { | 65 | let loc_needing_lifetime = if let Some(self_param) = self_param { |
63 | // if we have a self reference, use that | 66 | // if we have a self reference, use that |
64 | Some(self_param.name()?.syntax().text_range().start()) | 67 | Some(NeedsLifetime::SelfParam(self_param)) |
65 | } else { | 68 | } else { |
66 | // otherwise, if there's a single reference parameter without a named liftime, use that | 69 | // otherwise, if there's a single reference parameter without a named liftime, use that |
67 | let fn_params_without_lifetime: Vec<_> = param_list | 70 | let fn_params_without_lifetime: Vec<_> = param_list |
68 | .params() | 71 | .params() |
69 | .filter_map(|param| match param.ty() { | 72 | .filter_map(|param| match param.ty() { |
70 | Some(ast::Type::RefType(ascribed_type)) if ascribed_type.lifetime().is_none() => { | 73 | Some(ast::Type::RefType(ascribed_type)) if ascribed_type.lifetime().is_none() => { |
71 | Some(ascribed_type.amp_token()?.text_range().end()) | 74 | Some(NeedsLifetime::RefType(ascribed_type)) |
72 | } | 75 | } |
73 | _ => None, | 76 | _ => None, |
74 | }) | 77 | }) |
@@ -81,30 +84,46 @@ fn generate_fn_def_assist( | |||
81 | } | 84 | } |
82 | }; | 85 | }; |
83 | acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| { | 86 | acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| { |
84 | add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param); | 87 | let fn_def = builder.make_ast_mut(fn_def); |
85 | builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); | 88 | let lifetime = builder.make_ast_mut(lifetime); |
86 | loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param))); | 89 | let loc_needing_lifetime = |
90 | loc_needing_lifetime.and_then(|it| it.make_mut(builder).to_position()); | ||
91 | |||
92 | add_lifetime_param(fn_def.get_or_create_generic_param_list(), new_lifetime_param); | ||
93 | ted::replace( | ||
94 | lifetime.syntax(), | ||
95 | make_ast_lifetime(new_lifetime_param).clone_for_update().syntax(), | ||
96 | ); | ||
97 | loc_needing_lifetime.map(|position| { | ||
98 | ted::insert(position, make_ast_lifetime(new_lifetime_param).clone_for_update().syntax()) | ||
99 | }); | ||
87 | }) | 100 | }) |
88 | } | 101 | } |
89 | 102 | ||
90 | /// Generate the assist for the impl def case | 103 | /// Generate the assist for the impl def case |
91 | fn generate_impl_def_assist( | 104 | fn generate_impl_def_assist( |
92 | acc: &mut Assists, | 105 | acc: &mut Assists, |
93 | impl_def: &ast::Impl, | 106 | impl_def: ast::Impl, |
94 | lifetime_loc: TextRange, | 107 | lifetime_loc: TextRange, |
108 | lifetime: ast::Lifetime, | ||
95 | ) -> Option<()> { | 109 | ) -> Option<()> { |
96 | let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.generic_param_list())?; | 110 | let new_lifetime_param = generate_unique_lifetime_param_name(impl_def.generic_param_list())?; |
97 | let end_of_impl_kw = impl_def.impl_token()?.text_range().end(); | ||
98 | acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| { | 111 | acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| { |
99 | add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param); | 112 | let impl_def = builder.make_ast_mut(impl_def); |
100 | builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); | 113 | let lifetime = builder.make_ast_mut(lifetime); |
114 | |||
115 | add_lifetime_param(impl_def.get_or_create_generic_param_list(), new_lifetime_param); | ||
116 | ted::replace( | ||
117 | lifetime.syntax(), | ||
118 | make_ast_lifetime(new_lifetime_param).clone_for_update().syntax(), | ||
119 | ); | ||
101 | }) | 120 | }) |
102 | } | 121 | } |
103 | 122 | ||
104 | /// Given a type parameter list, generate a unique lifetime parameter name | 123 | /// Given a type parameter list, generate a unique lifetime parameter name |
105 | /// which is not in the list | 124 | /// which is not in the list |
106 | fn generate_unique_lifetime_param_name( | 125 | fn generate_unique_lifetime_param_name( |
107 | existing_type_param_list: &Option<ast::GenericParamList>, | 126 | existing_type_param_list: Option<ast::GenericParamList>, |
108 | ) -> Option<char> { | 127 | ) -> Option<char> { |
109 | match existing_type_param_list { | 128 | match existing_type_param_list { |
110 | Some(type_params) => { | 129 | Some(type_params) => { |
@@ -118,25 +137,37 @@ fn generate_unique_lifetime_param_name( | |||
118 | } | 137 | } |
119 | } | 138 | } |
120 | 139 | ||
121 | /// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise | 140 | fn add_lifetime_param(type_params: ast::GenericParamList, new_lifetime_param: char) { |
122 | /// add new type params brackets with the lifetime parameter at `new_type_params_loc`. | 141 | let generic_param = |
123 | fn add_lifetime_param<TypeParamsOwner: ast::GenericParamsOwner>( | 142 | make::generic_param(format!("'{}", new_lifetime_param), None).clone_for_update(); |
124 | type_params_owner: &TypeParamsOwner, | 143 | type_params.add_generic_param(generic_param); |
125 | builder: &mut AssistBuilder, | 144 | } |
126 | new_type_params_loc: TextSize, | 145 | |
127 | new_lifetime_param: char, | 146 | fn make_ast_lifetime(new_lifetime_param: char) -> ast::Lifetime { |
128 | ) { | 147 | make::generic_param(format!("'{}", new_lifetime_param), None) |
129 | match type_params_owner.generic_param_list() { | 148 | .syntax() |
130 | // add the new lifetime parameter to an existing type param list | 149 | .descendants() |
131 | Some(type_params) => { | 150 | .find_map(ast::Lifetime::cast) |
132 | builder.insert( | 151 | .unwrap() |
133 | (u32::from(type_params.syntax().text_range().end()) - 1).into(), | 152 | } |
134 | format!(", '{}", new_lifetime_param), | 153 | |
135 | ); | 154 | enum NeedsLifetime { |
155 | SelfParam(ast::SelfParam), | ||
156 | RefType(ast::RefType), | ||
157 | } | ||
158 | |||
159 | impl NeedsLifetime { | ||
160 | fn make_mut(self, builder: &mut AssistBuilder) -> Self { | ||
161 | match self { | ||
162 | Self::SelfParam(it) => Self::SelfParam(builder.make_ast_mut(it)), | ||
163 | Self::RefType(it) => Self::RefType(builder.make_ast_mut(it)), | ||
136 | } | 164 | } |
137 | // create a new type param list containing only the new lifetime parameter | 165 | } |
138 | None => { | 166 | |
139 | builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param)); | 167 | fn to_position(self) -> Option<Position> { |
168 | match self { | ||
169 | Self::SelfParam(it) => Some(Position::after(it.amp_token()?)), | ||
170 | Self::RefType(it) => Some(Position::after(it.amp_token()?)), | ||
140 | } | 171 | } |
141 | } | 172 | } |
142 | } | 173 | } |
@@ -312,4 +343,13 @@ mod tests { | |||
312 | r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#, | 343 | r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#, |
313 | ); | 344 | ); |
314 | } | 345 | } |
346 | |||
347 | #[test] | ||
348 | fn test_function_add_lifetime_to_self_ref_mut() { | ||
349 | check_assist( | ||
350 | introduce_named_lifetime, | ||
351 | r#"fn foo(&mut self) -> &'_$0 ()"#, | ||
352 | r#"fn foo<'a>(&'a mut self) -> &'a ()"#, | ||
353 | ); | ||
354 | } | ||
315 | } | 355 | } |
diff --git a/crates/ide_assists/src/handlers/remove_dbg.rs b/crates/ide_assists/src/handlers/remove_dbg.rs index 6114091f2..c8226550f 100644 --- a/crates/ide_assists/src/handlers/remove_dbg.rs +++ b/crates/ide_assists/src/handlers/remove_dbg.rs | |||
@@ -1,5 +1,5 @@ | |||
1 | use syntax::{ | 1 | use syntax::{ |
2 | ast::{self, AstNode}, | 2 | ast::{self, AstNode, AstToken}, |
3 | match_ast, SyntaxElement, TextRange, TextSize, T, | 3 | match_ast, SyntaxElement, TextRange, TextSize, T, |
4 | }; | 4 | }; |
5 | 5 | ||
@@ -24,7 +24,39 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
24 | let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; | 24 | let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; |
25 | let new_contents = adjusted_macro_contents(¯o_call)?; | 25 | let new_contents = adjusted_macro_contents(¯o_call)?; |
26 | 26 | ||
27 | let macro_text_range = macro_call.syntax().text_range(); | 27 | let parent = macro_call.syntax().parent(); |
28 | |||
29 | let macro_text_range = if let Some(it) = parent.as_ref() { | ||
30 | if new_contents.is_empty() { | ||
31 | match_ast! { | ||
32 | match it { | ||
33 | ast::BlockExpr(_it) => { | ||
34 | macro_call.syntax() | ||
35 | .prev_sibling_or_token() | ||
36 | .and_then(whitespace_start) | ||
37 | .map(|start| TextRange::new(start, macro_call.syntax().text_range().end())) | ||
38 | .unwrap_or(macro_call.syntax().text_range()) | ||
39 | }, | ||
40 | ast::ExprStmt(it) => { | ||
41 | let start = it | ||
42 | .syntax() | ||
43 | .prev_sibling_or_token() | ||
44 | .and_then(whitespace_start) | ||
45 | .unwrap_or(it.syntax().text_range().start()); | ||
46 | let end = it.syntax().text_range().end(); | ||
47 | |||
48 | TextRange::new(start, end) | ||
49 | }, | ||
50 | _ => macro_call.syntax().text_range() | ||
51 | } | ||
52 | } | ||
53 | } else { | ||
54 | macro_call.syntax().text_range() | ||
55 | } | ||
56 | } else { | ||
57 | macro_call.syntax().text_range() | ||
58 | }; | ||
59 | |||
28 | let macro_end = if macro_call.semicolon_token().is_some() { | 60 | let macro_end = if macro_call.semicolon_token().is_some() { |
29 | macro_text_range.end() - TextSize::of(';') | 61 | macro_text_range.end() - TextSize::of(';') |
30 | } else { | 62 | } else { |
@@ -36,11 +68,22 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
36 | "Remove dbg!()", | 68 | "Remove dbg!()", |
37 | macro_text_range, | 69 | macro_text_range, |
38 | |builder| { | 70 | |builder| { |
39 | builder.replace(TextRange::new(macro_text_range.start(), macro_end), new_contents); | 71 | builder.replace( |
72 | TextRange::new(macro_text_range.start(), macro_end), | ||
73 | if new_contents.is_empty() && parent.and_then(ast::LetStmt::cast).is_some() { | ||
74 | ast::make::expr_unit().to_string() | ||
75 | } else { | ||
76 | new_contents | ||
77 | }, | ||
78 | ); | ||
40 | }, | 79 | }, |
41 | ) | 80 | ) |
42 | } | 81 | } |
43 | 82 | ||
83 | fn whitespace_start(it: SyntaxElement) -> Option<TextSize> { | ||
84 | Some(it.into_token().and_then(ast::Whitespace::cast)?.syntax().text_range().start()) | ||
85 | } | ||
86 | |||
44 | fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> { | 87 | fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> { |
45 | let contents = get_valid_macrocall_contents(¯o_call, "dbg")?; | 88 | let contents = get_valid_macrocall_contents(¯o_call, "dbg")?; |
46 | let macro_text_with_brackets = macro_call.token_tree()?.syntax().text(); | 89 | let macro_text_with_brackets = macro_call.token_tree()?.syntax().text(); |
@@ -94,15 +137,11 @@ fn get_valid_macrocall_contents( | |||
94 | let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>(); | 137 | let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>(); |
95 | let last_child = contents_between_brackets.pop()?; | 138 | let last_child = contents_between_brackets.pop()?; |
96 | 139 | ||
97 | if contents_between_brackets.is_empty() { | 140 | match (first_child.kind(), last_child.kind()) { |
98 | None | 141 | (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => { |
99 | } else { | 142 | Some(contents_between_brackets) |
100 | match (first_child.kind(), last_child.kind()) { | ||
101 | (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => { | ||
102 | Some(contents_between_brackets) | ||
103 | } | ||
104 | _ => None, | ||
105 | } | 143 | } |
144 | _ => None, | ||
106 | } | 145 | } |
107 | } | 146 | } |
108 | 147 | ||
@@ -418,4 +457,48 @@ fn main() { | |||
418 | }"#, | 457 | }"#, |
419 | ); | 458 | ); |
420 | } | 459 | } |
460 | |||
461 | #[test] | ||
462 | fn test_remove_empty_dbg() { | ||
463 | check_assist(remove_dbg, r#"fn foo() { $0dbg!(); }"#, r#"fn foo() { }"#); | ||
464 | check_assist( | ||
465 | remove_dbg, | ||
466 | r#" | ||
467 | fn foo() { | ||
468 | $0dbg!(); | ||
469 | } | ||
470 | "#, | ||
471 | r#" | ||
472 | fn foo() { | ||
473 | } | ||
474 | "#, | ||
475 | ); | ||
476 | check_assist( | ||
477 | remove_dbg, | ||
478 | r#" | ||
479 | fn foo() { | ||
480 | let test = $0dbg!(); | ||
481 | }"#, | ||
482 | r#" | ||
483 | fn foo() { | ||
484 | let test = (); | ||
485 | }"#, | ||
486 | ); | ||
487 | check_assist( | ||
488 | remove_dbg, | ||
489 | r#" | ||
490 | fn foo() { | ||
491 | let t = { | ||
492 | println!("Hello, world"); | ||
493 | $0dbg!() | ||
494 | }; | ||
495 | }"#, | ||
496 | r#" | ||
497 | fn foo() { | ||
498 | let t = { | ||
499 | println!("Hello, world"); | ||
500 | }; | ||
501 | }"#, | ||
502 | ); | ||
503 | } | ||
421 | } | 504 | } |
diff --git a/crates/ide_assists/src/handlers/reorder_fields.rs b/crates/ide_assists/src/handlers/reorder_fields.rs index 383ca6c47..1a95135ca 100644 --- a/crates/ide_assists/src/handlers/reorder_fields.rs +++ b/crates/ide_assists/src/handlers/reorder_fields.rs | |||
@@ -1,6 +1,8 @@ | |||
1 | use either::Either; | ||
2 | use itertools::Itertools; | ||
1 | use rustc_hash::FxHashMap; | 3 | use rustc_hash::FxHashMap; |
2 | 4 | ||
3 | use syntax::{algo, ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode}; | 5 | use syntax::{ast, ted, AstNode}; |
4 | 6 | ||
5 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 7 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
6 | 8 | ||
@@ -22,60 +24,70 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; | |||
22 | pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 24 | pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
23 | let record = ctx | 25 | let record = ctx |
24 | .find_node_at_offset::<ast::RecordExpr>() | 26 | .find_node_at_offset::<ast::RecordExpr>() |
25 | .map(|it| it.syntax().clone()) | 27 | .map(Either::Left) |
26 | .or_else(|| ctx.find_node_at_offset::<ast::RecordPat>().map(|it| it.syntax().clone()))?; | 28 | .or_else(|| ctx.find_node_at_offset::<ast::RecordPat>().map(Either::Right))?; |
27 | |||
28 | let path = record.children().find_map(ast::Path::cast)?; | ||
29 | 29 | ||
30 | let path = record.as_ref().either(|it| it.path(), |it| it.path())?; | ||
30 | let ranks = compute_fields_ranks(&path, &ctx)?; | 31 | let ranks = compute_fields_ranks(&path, &ctx)?; |
32 | let get_rank_of_field = | ||
33 | |of: Option<_>| *ranks.get(&of.unwrap_or_default()).unwrap_or(&usize::MAX); | ||
31 | 34 | ||
32 | let fields: Vec<SyntaxNode> = { | 35 | let field_list = match &record { |
33 | let field_kind = match record.kind() { | 36 | Either::Left(it) => Either::Left(it.record_expr_field_list()?), |
34 | RECORD_EXPR => RECORD_EXPR_FIELD, | 37 | Either::Right(it) => Either::Right(it.record_pat_field_list()?), |
35 | RECORD_PAT => RECORD_PAT_FIELD, | ||
36 | _ => { | ||
37 | stdx::never!(); | ||
38 | return None; | ||
39 | } | ||
40 | }; | ||
41 | record.children().flat_map(|n| n.children()).filter(|n| n.kind() == field_kind).collect() | ||
42 | }; | 38 | }; |
43 | 39 | let fields = match field_list { | |
44 | let sorted_fields = { | 40 | Either::Left(it) => Either::Left(( |
45 | let mut fields = fields.clone(); | 41 | it.fields() |
46 | fields.sort_by_key(|node| *ranks.get(&get_field_name(node)).unwrap_or(&usize::max_value())); | 42 | .sorted_unstable_by_key(|field| { |
47 | fields | 43 | get_rank_of_field(field.field_name().map(|it| it.to_string())) |
44 | }) | ||
45 | .collect::<Vec<_>>(), | ||
46 | it, | ||
47 | )), | ||
48 | Either::Right(it) => Either::Right(( | ||
49 | it.fields() | ||
50 | .sorted_unstable_by_key(|field| { | ||
51 | get_rank_of_field(field.field_name().map(|it| it.to_string())) | ||
52 | }) | ||
53 | .collect::<Vec<_>>(), | ||
54 | it, | ||
55 | )), | ||
48 | }; | 56 | }; |
49 | 57 | ||
50 | if sorted_fields == fields { | 58 | let is_sorted = fields.as_ref().either( |
59 | |(sorted, field_list)| field_list.fields().zip(sorted).all(|(a, b)| a == *b), | ||
60 | |(sorted, field_list)| field_list.fields().zip(sorted).all(|(a, b)| a == *b), | ||
61 | ); | ||
62 | if is_sorted { | ||
51 | cov_mark::hit!(reorder_sorted_fields); | 63 | cov_mark::hit!(reorder_sorted_fields); |
52 | return None; | 64 | return None; |
53 | } | 65 | } |
54 | 66 | let target = record.as_ref().either(AstNode::syntax, AstNode::syntax).text_range(); | |
55 | let target = record.text_range(); | ||
56 | acc.add( | 67 | acc.add( |
57 | AssistId("reorder_fields", AssistKind::RefactorRewrite), | 68 | AssistId("reorder_fields", AssistKind::RefactorRewrite), |
58 | "Reorder record fields", | 69 | "Reorder record fields", |
59 | target, | 70 | target, |
60 | |edit| { | 71 | |builder| match fields { |
61 | let mut rewriter = algo::SyntaxRewriter::default(); | 72 | Either::Left((sorted, field_list)) => { |
62 | for (old, new) in fields.iter().zip(&sorted_fields) { | 73 | replace(builder.make_ast_mut(field_list).fields(), sorted) |
63 | rewriter.replace(old, new); | 74 | } |
75 | Either::Right((sorted, field_list)) => { | ||
76 | replace(builder.make_ast_mut(field_list).fields(), sorted) | ||
64 | } | 77 | } |
65 | edit.rewrite(rewriter); | ||
66 | }, | 78 | }, |
67 | ) | 79 | ) |
68 | } | 80 | } |
69 | 81 | ||
70 | fn get_field_name(node: &SyntaxNode) -> String { | 82 | fn replace<T: AstNode + PartialEq>( |
71 | let res = match_ast! { | 83 | fields: impl Iterator<Item = T>, |
72 | match node { | 84 | sorted_fields: impl IntoIterator<Item = T>, |
73 | ast::RecordExprField(field) => field.field_name().map(|it| it.to_string()), | 85 | ) { |
74 | ast::RecordPatField(field) => field.field_name().map(|it| it.to_string()), | 86 | fields.zip(sorted_fields).filter(|(field, sorted)| field != sorted).for_each( |
75 | _ => None, | 87 | |(field, sorted_field)| { |
76 | } | 88 | ted::replace(field.syntax(), sorted_field.syntax().clone_for_update()); |
77 | }; | 89 | }, |
78 | res.unwrap_or_default() | 90 | ); |
79 | } | 91 | } |
80 | 92 | ||
81 | fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { | 93 | fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { |
@@ -86,7 +98,7 @@ fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashM | |||
86 | 98 | ||
87 | let res = strukt | 99 | let res = strukt |
88 | .fields(ctx.db()) | 100 | .fields(ctx.db()) |
89 | .iter() | 101 | .into_iter() |
90 | .enumerate() | 102 | .enumerate() |
91 | .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx)) | 103 | .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx)) |
92 | .collect(); | 104 | .collect(); |
@@ -137,7 +149,6 @@ const test: Foo = Foo { foo: 1, bar: 0 }; | |||
137 | "#, | 149 | "#, |
138 | ) | 150 | ) |
139 | } | 151 | } |
140 | |||
141 | #[test] | 152 | #[test] |
142 | fn reorder_struct_pattern() { | 153 | fn reorder_struct_pattern() { |
143 | check_assist( | 154 | check_assist( |
diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs index f872d20c8..694d897d1 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 | |||
@@ -5,7 +5,6 @@ use itertools::Itertools; | |||
5 | use syntax::{ | 5 | use syntax::{ |
6 | ast::{self, make, AstNode, NameOwner}, | 6 | ast::{self, make, AstNode, NameOwner}, |
7 | SyntaxKind::{IDENT, WHITESPACE}, | 7 | SyntaxKind::{IDENT, WHITESPACE}, |
8 | TextSize, | ||
9 | }; | 8 | }; |
10 | 9 | ||
11 | use crate::{ | 10 | use crate::{ |
@@ -43,32 +42,28 @@ pub(crate) fn replace_derive_with_manual_impl( | |||
43 | ctx: &AssistContext, | 42 | ctx: &AssistContext, |
44 | ) -> Option<()> { | 43 | ) -> Option<()> { |
45 | let attr = ctx.find_node_at_offset::<ast::Attr>()?; | 44 | let attr = ctx.find_node_at_offset::<ast::Attr>()?; |
45 | let (name, args) = attr.as_simple_call()?; | ||
46 | if name != "derive" { | ||
47 | return None; | ||
48 | } | ||
46 | 49 | ||
47 | let has_derive = attr | 50 | if !args.syntax().text_range().contains(ctx.offset()) { |
48 | .syntax() | 51 | cov_mark::hit!(outside_of_attr_args); |
49 | .descendants_with_tokens() | ||
50 | .filter(|t| t.kind() == IDENT) | ||
51 | .find_map(syntax::NodeOrToken::into_token) | ||
52 | .filter(|t| t.text() == "derive") | ||
53 | .is_some(); | ||
54 | if !has_derive { | ||
55 | return None; | 52 | return None; |
56 | } | 53 | } |
57 | 54 | ||
58 | let trait_token = ctx.token_at_offset().find(|t| t.kind() == IDENT && t.text() != "derive")?; | 55 | let trait_token = args.syntax().token_at_offset(ctx.offset()).find(|t| t.kind() == IDENT)?; |
59 | let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text()))); | 56 | let trait_name = trait_token.text(); |
60 | 57 | ||
61 | let adt = attr.syntax().parent().and_then(ast::Adt::cast)?; | 58 | let adt = attr.syntax().parent().and_then(ast::Adt::cast)?; |
62 | let annotated_name = adt.name()?; | ||
63 | let insert_pos = adt.syntax().text_range().end(); | ||
64 | 59 | ||
65 | let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; | 60 | let current_module = ctx.sema.scope(adt.syntax()).module()?; |
66 | let current_crate = current_module.krate(); | 61 | let current_crate = current_module.krate(); |
67 | 62 | ||
68 | let found_traits = items_locator::items_with_name( | 63 | let found_traits = items_locator::items_with_name( |
69 | &ctx.sema, | 64 | &ctx.sema, |
70 | current_crate, | 65 | current_crate, |
71 | NameToImport::Exact(trait_token.text().to_string()), | 66 | NameToImport::Exact(trait_name.to_string()), |
72 | items_locator::AssocItemSearch::Exclude, | 67 | items_locator::AssocItemSearch::Exclude, |
73 | Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT), | 68 | Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT), |
74 | ) | 69 | ) |
@@ -86,10 +81,11 @@ pub(crate) fn replace_derive_with_manual_impl( | |||
86 | 81 | ||
87 | let mut no_traits_found = true; | 82 | let mut no_traits_found = true; |
88 | for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { | 83 | for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { |
89 | add_assist(acc, ctx, &attr, &trait_path, Some(trait_), &adt, &annotated_name, insert_pos)?; | 84 | add_assist(acc, ctx, &attr, &args, &trait_path, Some(trait_), &adt)?; |
90 | } | 85 | } |
91 | if no_traits_found { | 86 | if no_traits_found { |
92 | add_assist(acc, ctx, &attr, &trait_path, None, &adt, &annotated_name, insert_pos)?; | 87 | let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_name))); |
88 | add_assist(acc, ctx, &attr, &args, &trait_path, None, &adt)?; | ||
93 | } | 89 | } |
94 | Some(()) | 90 | Some(()) |
95 | } | 91 | } |
@@ -98,15 +94,14 @@ fn add_assist( | |||
98 | acc: &mut Assists, | 94 | acc: &mut Assists, |
99 | ctx: &AssistContext, | 95 | ctx: &AssistContext, |
100 | attr: &ast::Attr, | 96 | attr: &ast::Attr, |
97 | input: &ast::TokenTree, | ||
101 | trait_path: &ast::Path, | 98 | trait_path: &ast::Path, |
102 | trait_: Option<hir::Trait>, | 99 | trait_: Option<hir::Trait>, |
103 | adt: &ast::Adt, | 100 | adt: &ast::Adt, |
104 | annotated_name: &ast::Name, | ||
105 | insert_pos: TextSize, | ||
106 | ) -> Option<()> { | 101 | ) -> Option<()> { |
107 | let target = attr.syntax().text_range(); | 102 | let target = attr.syntax().text_range(); |
108 | let input = attr.token_tree()?; | 103 | let annotated_name = adt.name()?; |
109 | let label = format!("Convert to manual `impl {} for {}`", trait_path, annotated_name); | 104 | let label = format!("Convert to manual `impl {} for {}`", trait_path, annotated_name); |
110 | let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?; | 105 | let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?; |
111 | 106 | ||
112 | acc.add( | 107 | acc.add( |
@@ -114,8 +109,9 @@ fn add_assist( | |||
114 | label, | 109 | label, |
115 | target, | 110 | target, |
116 | |builder| { | 111 | |builder| { |
112 | let insert_pos = adt.syntax().text_range().end(); | ||
117 | let impl_def_with_items = | 113 | let impl_def_with_items = |
118 | impl_def_from_trait(&ctx.sema, annotated_name, trait_, trait_path); | 114 | impl_def_from_trait(&ctx.sema, &annotated_name, trait_, trait_path); |
119 | update_attribute(builder, &input, &trait_name, &attr); | 115 | update_attribute(builder, &input, &trait_name, &attr); |
120 | let trait_path = format!("{}", trait_path); | 116 | let trait_path = format!("{}", trait_path); |
121 | match (ctx.config.snippet_cap, impl_def_with_items) { | 117 | match (ctx.config.snippet_cap, impl_def_with_items) { |
@@ -216,7 +212,7 @@ mod tests { | |||
216 | fn add_custom_impl_debug() { | 212 | fn add_custom_impl_debug() { |
217 | check_assist( | 213 | check_assist( |
218 | replace_derive_with_manual_impl, | 214 | replace_derive_with_manual_impl, |
219 | " | 215 | r#" |
220 | mod fmt { | 216 | mod fmt { |
221 | pub struct Error; | 217 | pub struct Error; |
222 | pub type Result = Result<(), Error>; | 218 | pub type Result = Result<(), Error>; |
@@ -230,8 +226,8 @@ mod fmt { | |||
230 | struct Foo { | 226 | struct Foo { |
231 | bar: String, | 227 | bar: String, |
232 | } | 228 | } |
233 | ", | 229 | "#, |
234 | " | 230 | r#" |
235 | mod fmt { | 231 | mod fmt { |
236 | pub struct Error; | 232 | pub struct Error; |
237 | pub type Result = Result<(), Error>; | 233 | pub type Result = Result<(), Error>; |
@@ -250,14 +246,14 @@ impl fmt::Debug for Foo { | |||
250 | ${0:todo!()} | 246 | ${0:todo!()} |
251 | } | 247 | } |
252 | } | 248 | } |
253 | ", | 249 | "#, |
254 | ) | 250 | ) |
255 | } | 251 | } |
256 | #[test] | 252 | #[test] |
257 | fn add_custom_impl_all() { | 253 | fn add_custom_impl_all() { |
258 | check_assist( | 254 | check_assist( |
259 | replace_derive_with_manual_impl, | 255 | replace_derive_with_manual_impl, |
260 | " | 256 | r#" |
261 | mod foo { | 257 | mod foo { |
262 | pub trait Bar { | 258 | pub trait Bar { |
263 | type Qux; | 259 | type Qux; |
@@ -272,8 +268,8 @@ mod foo { | |||
272 | struct Foo { | 268 | struct Foo { |
273 | bar: String, | 269 | bar: String, |
274 | } | 270 | } |
275 | ", | 271 | "#, |
276 | " | 272 | r#" |
277 | mod foo { | 273 | mod foo { |
278 | pub trait Bar { | 274 | pub trait Bar { |
279 | type Qux; | 275 | type Qux; |
@@ -299,20 +295,20 @@ impl foo::Bar for Foo { | |||
299 | todo!() | 295 | todo!() |
300 | } | 296 | } |
301 | } | 297 | } |
302 | ", | 298 | "#, |
303 | ) | 299 | ) |
304 | } | 300 | } |
305 | #[test] | 301 | #[test] |
306 | fn add_custom_impl_for_unique_input() { | 302 | fn add_custom_impl_for_unique_input() { |
307 | check_assist( | 303 | check_assist( |
308 | replace_derive_with_manual_impl, | 304 | replace_derive_with_manual_impl, |
309 | " | 305 | r#" |
310 | #[derive(Debu$0g)] | 306 | #[derive(Debu$0g)] |
311 | struct Foo { | 307 | struct Foo { |
312 | bar: String, | 308 | bar: String, |
313 | } | 309 | } |
314 | ", | 310 | "#, |
315 | " | 311 | r#" |
316 | struct Foo { | 312 | struct Foo { |
317 | bar: String, | 313 | bar: String, |
318 | } | 314 | } |
@@ -320,7 +316,7 @@ struct Foo { | |||
320 | impl Debug for Foo { | 316 | impl Debug for Foo { |
321 | $0 | 317 | $0 |
322 | } | 318 | } |
323 | ", | 319 | "#, |
324 | ) | 320 | ) |
325 | } | 321 | } |
326 | 322 | ||
@@ -328,13 +324,13 @@ impl Debug for Foo { | |||
328 | fn add_custom_impl_for_with_visibility_modifier() { | 324 | fn add_custom_impl_for_with_visibility_modifier() { |
329 | check_assist( | 325 | check_assist( |
330 | replace_derive_with_manual_impl, | 326 | replace_derive_with_manual_impl, |
331 | " | 327 | r#" |
332 | #[derive(Debug$0)] | 328 | #[derive(Debug$0)] |
333 | pub struct Foo { | 329 | pub struct Foo { |
334 | bar: String, | 330 | bar: String, |
335 | } | 331 | } |
336 | ", | 332 | "#, |
337 | " | 333 | r#" |
338 | pub struct Foo { | 334 | pub struct Foo { |
339 | bar: String, | 335 | bar: String, |
340 | } | 336 | } |
@@ -342,7 +338,7 @@ pub struct Foo { | |||
342 | impl Debug for Foo { | 338 | impl Debug for Foo { |
343 | $0 | 339 | $0 |
344 | } | 340 | } |
345 | ", | 341 | "#, |
346 | ) | 342 | ) |
347 | } | 343 | } |
348 | 344 | ||
@@ -350,18 +346,18 @@ impl Debug for Foo { | |||
350 | fn add_custom_impl_when_multiple_inputs() { | 346 | fn add_custom_impl_when_multiple_inputs() { |
351 | check_assist( | 347 | check_assist( |
352 | replace_derive_with_manual_impl, | 348 | replace_derive_with_manual_impl, |
353 | " | 349 | r#" |
354 | #[derive(Display, Debug$0, Serialize)] | 350 | #[derive(Display, Debug$0, Serialize)] |
355 | struct Foo {} | 351 | struct Foo {} |
356 | ", | 352 | "#, |
357 | " | 353 | r#" |
358 | #[derive(Display, Serialize)] | 354 | #[derive(Display, Serialize)] |
359 | struct Foo {} | 355 | struct Foo {} |
360 | 356 | ||
361 | impl Debug for Foo { | 357 | impl Debug for Foo { |
362 | $0 | 358 | $0 |
363 | } | 359 | } |
364 | ", | 360 | "#, |
365 | ) | 361 | ) |
366 | } | 362 | } |
367 | 363 | ||
@@ -369,10 +365,10 @@ impl Debug for Foo { | |||
369 | fn test_ignore_derive_macro_without_input() { | 365 | fn test_ignore_derive_macro_without_input() { |
370 | check_assist_not_applicable( | 366 | check_assist_not_applicable( |
371 | replace_derive_with_manual_impl, | 367 | replace_derive_with_manual_impl, |
372 | " | 368 | r#" |
373 | #[derive($0)] | 369 | #[derive($0)] |
374 | struct Foo {} | 370 | struct Foo {} |
375 | ", | 371 | "#, |
376 | ) | 372 | ) |
377 | } | 373 | } |
378 | 374 | ||
@@ -380,18 +376,18 @@ struct Foo {} | |||
380 | fn test_ignore_if_cursor_on_param() { | 376 | fn test_ignore_if_cursor_on_param() { |
381 | check_assist_not_applicable( | 377 | check_assist_not_applicable( |
382 | replace_derive_with_manual_impl, | 378 | replace_derive_with_manual_impl, |
383 | " | 379 | r#" |
384 | #[derive$0(Debug)] | 380 | #[derive$0(Debug)] |
385 | struct Foo {} | 381 | struct Foo {} |
386 | ", | 382 | "#, |
387 | ); | 383 | ); |
388 | 384 | ||
389 | check_assist_not_applicable( | 385 | check_assist_not_applicable( |
390 | replace_derive_with_manual_impl, | 386 | replace_derive_with_manual_impl, |
391 | " | 387 | r#" |
392 | #[derive(Debug)$0] | 388 | #[derive(Debug)$0] |
393 | struct Foo {} | 389 | struct Foo {} |
394 | ", | 390 | "#, |
395 | ) | 391 | ) |
396 | } | 392 | } |
397 | 393 | ||
@@ -399,10 +395,22 @@ struct Foo {} | |||
399 | fn test_ignore_if_not_derive() { | 395 | fn test_ignore_if_not_derive() { |
400 | check_assist_not_applicable( | 396 | check_assist_not_applicable( |
401 | replace_derive_with_manual_impl, | 397 | replace_derive_with_manual_impl, |
402 | " | 398 | r#" |
403 | #[allow(non_camel_$0case_types)] | 399 | #[allow(non_camel_$0case_types)] |
404 | struct Foo {} | 400 | struct Foo {} |
405 | ", | 401 | "#, |
406 | ) | 402 | ) |
407 | } | 403 | } |
404 | |||
405 | #[test] | ||
406 | fn works_at_start_of_file() { | ||
407 | cov_mark::check!(outside_of_attr_args); | ||
408 | check_assist_not_applicable( | ||
409 | replace_derive_with_manual_impl, | ||
410 | r#" | ||
411 | $0#[derive(Debug)] | ||
412 | struct S; | ||
413 | "#, | ||
414 | ); | ||
415 | } | ||
408 | } | 416 | } |
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 1c55b9fbf..88ae5c9a9 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -28,7 +28,9 @@ pub use assist_config::AssistConfig; | |||
28 | 28 | ||
29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
30 | pub enum AssistKind { | 30 | pub enum AssistKind { |
31 | // FIXME: does the None variant make sense? Probably not. | ||
31 | None, | 32 | None, |
33 | |||
32 | QuickFix, | 34 | QuickFix, |
33 | Generate, | 35 | Generate, |
34 | Refactor, | 36 | Refactor, |
@@ -133,6 +135,7 @@ mod handlers { | |||
133 | mod generate_default_from_enum_variant; | 135 | mod generate_default_from_enum_variant; |
134 | mod generate_default_from_new; | 136 | mod generate_default_from_new; |
135 | mod generate_is_empty_from_len; | 137 | mod generate_is_empty_from_len; |
138 | mod generate_deref; | ||
136 | mod generate_derive; | 139 | mod generate_derive; |
137 | mod generate_enum_is_method; | 140 | mod generate_enum_is_method; |
138 | mod generate_enum_projection_method; | 141 | mod generate_enum_projection_method; |
@@ -201,6 +204,7 @@ mod handlers { | |||
201 | generate_default_from_enum_variant::generate_default_from_enum_variant, | 204 | generate_default_from_enum_variant::generate_default_from_enum_variant, |
202 | generate_default_from_new::generate_default_from_new, | 205 | generate_default_from_new::generate_default_from_new, |
203 | generate_is_empty_from_len::generate_is_empty_from_len, | 206 | generate_is_empty_from_len::generate_is_empty_from_len, |
207 | generate_deref::generate_deref, | ||
204 | generate_derive::generate_derive, | 208 | generate_derive::generate_derive, |
205 | generate_enum_is_method::generate_enum_is_method, | 209 | generate_enum_is_method::generate_enum_is_method, |
206 | generate_enum_projection_method::generate_enum_as_method, | 210 | generate_enum_projection_method::generate_enum_as_method, |
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs index a7a923beb..49533e7d2 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; |
@@ -191,6 +193,7 @@ fn assist_order_field_struct() { | |||
191 | let mut assists = assists.iter(); | 193 | let mut assists = assists.iter(); |
192 | 194 | ||
193 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); | 195 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); |
196 | assert_eq!(assists.next().expect("expected assist").label, "Generate `Deref` impl using `bar`"); | ||
194 | assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method"); | 197 | assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method"); |
195 | assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method"); | 198 | assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method"); |
196 | assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method"); | 199 | assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method"); |
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index f4a4749c8..59bcef8fb 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs | |||
@@ -593,6 +593,33 @@ impl Default for Example { | |||
593 | } | 593 | } |
594 | 594 | ||
595 | #[test] | 595 | #[test] |
596 | fn doctest_generate_deref() { | ||
597 | check_doc_test( | ||
598 | "generate_deref", | ||
599 | r#####" | ||
600 | struct A; | ||
601 | struct B { | ||
602 | $0a: A | ||
603 | } | ||
604 | "#####, | ||
605 | r#####" | ||
606 | struct A; | ||
607 | struct B { | ||
608 | a: A | ||
609 | } | ||
610 | |||
611 | impl std::ops::Deref for B { | ||
612 | type Target = A; | ||
613 | |||
614 | fn deref(&self) -> &Self::Target { | ||
615 | &self.a | ||
616 | } | ||
617 | } | ||
618 | "#####, | ||
619 | ) | ||
620 | } | ||
621 | |||
622 | #[test] | ||
596 | fn doctest_generate_derive() { | 623 | fn doctest_generate_derive() { |
597 | check_doc_test( | 624 | check_doc_test( |
598 | "generate_derive", | 625 | "generate_derive", |