diff options
Diffstat (limited to 'crates/ide_assists')
-rw-r--r-- | crates/ide_assists/src/handlers/extract_function.rs | 176 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/flip_comma.rs | 18 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/remove_dbg.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 2 |
4 files changed, 160 insertions, 38 deletions
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs index 5fdc8bf38..059414274 100644 --- a/crates/ide_assists/src/handlers/extract_function.rs +++ b/crates/ide_assists/src/handlers/extract_function.rs | |||
@@ -75,7 +75,8 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
75 | let insert_after = scope_for_fn_insertion(&body, anchor)?; | 75 | let insert_after = scope_for_fn_insertion(&body, anchor)?; |
76 | let module = ctx.sema.scope(&insert_after).module()?; | 76 | let module = ctx.sema.scope(&insert_after).module()?; |
77 | 77 | ||
78 | let vars_defined_in_body_and_outlive = vars_defined_in_body_and_outlive(ctx, &body); | 78 | let vars_defined_in_body_and_outlive = |
79 | vars_defined_in_body_and_outlive(ctx, &body, &node.parent().as_ref().unwrap_or(&node)); | ||
79 | let ret_ty = body_return_ty(ctx, &body)?; | 80 | let ret_ty = body_return_ty(ctx, &body)?; |
80 | 81 | ||
81 | // FIXME: we compute variables that outlive here just to check `never!` condition | 82 | // FIXME: we compute variables that outlive here just to check `never!` condition |
@@ -257,7 +258,7 @@ struct Function { | |||
257 | control_flow: ControlFlow, | 258 | control_flow: ControlFlow, |
258 | ret_ty: RetType, | 259 | ret_ty: RetType, |
259 | body: FunctionBody, | 260 | body: FunctionBody, |
260 | vars_defined_in_body_and_outlive: Vec<Local>, | 261 | vars_defined_in_body_and_outlive: Vec<OutlivedLocal>, |
261 | } | 262 | } |
262 | 263 | ||
263 | #[derive(Debug)] | 264 | #[derive(Debug)] |
@@ -296,9 +297,9 @@ impl Function { | |||
296 | RetType::Expr(ty) => FunType::Single(ty.clone()), | 297 | RetType::Expr(ty) => FunType::Single(ty.clone()), |
297 | RetType::Stmt => match self.vars_defined_in_body_and_outlive.as_slice() { | 298 | RetType::Stmt => match self.vars_defined_in_body_and_outlive.as_slice() { |
298 | [] => FunType::Unit, | 299 | [] => FunType::Unit, |
299 | [var] => FunType::Single(var.ty(ctx.db())), | 300 | [var] => FunType::Single(var.local.ty(ctx.db())), |
300 | vars => { | 301 | vars => { |
301 | let types = vars.iter().map(|v| v.ty(ctx.db())).collect(); | 302 | let types = vars.iter().map(|v| v.local.ty(ctx.db())).collect(); |
302 | FunType::Tuple(types) | 303 | FunType::Tuple(types) |
303 | } | 304 | } |
304 | }, | 305 | }, |
@@ -562,6 +563,12 @@ impl HasTokenAtOffset for FunctionBody { | |||
562 | } | 563 | } |
563 | } | 564 | } |
564 | 565 | ||
566 | #[derive(Debug)] | ||
567 | struct OutlivedLocal { | ||
568 | local: Local, | ||
569 | mut_usage_outside_body: bool, | ||
570 | } | ||
571 | |||
565 | /// Try to guess what user wants to extract | 572 | /// Try to guess what user wants to extract |
566 | /// | 573 | /// |
567 | /// We have basically have two cases: | 574 | /// We have basically have two cases: |
@@ -707,10 +714,10 @@ fn has_exclusive_usages(ctx: &AssistContext, usages: &LocalUsages, body: &Functi | |||
707 | .any(|reference| reference_is_exclusive(reference, body, ctx)) | 714 | .any(|reference| reference_is_exclusive(reference, body, ctx)) |
708 | } | 715 | } |
709 | 716 | ||
710 | /// checks if this reference requires `&mut` access inside body | 717 | /// checks if this reference requires `&mut` access inside node |
711 | fn reference_is_exclusive( | 718 | fn reference_is_exclusive( |
712 | reference: &FileReference, | 719 | reference: &FileReference, |
713 | body: &FunctionBody, | 720 | node: &dyn HasTokenAtOffset, |
714 | ctx: &AssistContext, | 721 | ctx: &AssistContext, |
715 | ) -> bool { | 722 | ) -> bool { |
716 | // we directly modify variable with set: `n = 0`, `n += 1` | 723 | // we directly modify variable with set: `n = 0`, `n += 1` |
@@ -719,7 +726,7 @@ fn reference_is_exclusive( | |||
719 | } | 726 | } |
720 | 727 | ||
721 | // we take `&mut` reference to variable: `&mut v` | 728 | // we take `&mut` reference to variable: `&mut v` |
722 | let path = match path_element_of_reference(body, reference) { | 729 | let path = match path_element_of_reference(node, reference) { |
723 | Some(path) => path, | 730 | Some(path) => path, |
724 | None => return false, | 731 | None => return false, |
725 | }; | 732 | }; |
@@ -729,6 +736,14 @@ fn reference_is_exclusive( | |||
729 | 736 | ||
730 | /// checks if this expr requires `&mut` access, recurses on field access | 737 | /// checks if this expr requires `&mut` access, recurses on field access |
731 | fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> { | 738 | fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> { |
739 | match expr { | ||
740 | ast::Expr::MacroCall(_) => { | ||
741 | // FIXME: expand macro and check output for mutable usages of the variable? | ||
742 | return None; | ||
743 | } | ||
744 | _ => (), | ||
745 | } | ||
746 | |||
732 | let parent = expr.syntax().parent()?; | 747 | let parent = expr.syntax().parent()?; |
733 | 748 | ||
734 | if let Some(bin_expr) = ast::BinExpr::cast(parent.clone()) { | 749 | if let Some(bin_expr) = ast::BinExpr::cast(parent.clone()) { |
@@ -787,7 +802,7 @@ impl HasTokenAtOffset for SyntaxNode { | |||
787 | } | 802 | } |
788 | } | 803 | } |
789 | 804 | ||
790 | /// find relevant `ast::PathExpr` for reference | 805 | /// find relevant `ast::Expr` for reference |
791 | /// | 806 | /// |
792 | /// # Preconditions | 807 | /// # Preconditions |
793 | /// | 808 | /// |
@@ -804,7 +819,11 @@ fn path_element_of_reference( | |||
804 | stdx::never!(false, "cannot find path parent of variable usage: {:?}", token); | 819 | stdx::never!(false, "cannot find path parent of variable usage: {:?}", token); |
805 | None | 820 | None |
806 | })?; | 821 | })?; |
807 | stdx::always!(matches!(path, ast::Expr::PathExpr(_))); | 822 | stdx::always!( |
823 | matches!(path, ast::Expr::PathExpr(_) | ast::Expr::MacroCall(_)), | ||
824 | "unexpected expression type for variable usage: {:?}", | ||
825 | path | ||
826 | ); | ||
808 | Some(path) | 827 | Some(path) |
809 | } | 828 | } |
810 | 829 | ||
@@ -820,10 +839,16 @@ fn vars_defined_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> | |||
820 | } | 839 | } |
821 | 840 | ||
822 | /// list local variables defined inside `body` that should be returned from extracted function | 841 | /// list local variables defined inside `body` that should be returned from extracted function |
823 | fn vars_defined_in_body_and_outlive(ctx: &AssistContext, body: &FunctionBody) -> Vec<Local> { | 842 | fn vars_defined_in_body_and_outlive( |
824 | let mut vars_defined_in_body = vars_defined_in_body(&body, ctx); | 843 | ctx: &AssistContext, |
825 | vars_defined_in_body.retain(|var| var_outlives_body(ctx, body, var)); | 844 | body: &FunctionBody, |
845 | parent: &SyntaxNode, | ||
846 | ) -> Vec<OutlivedLocal> { | ||
847 | let vars_defined_in_body = vars_defined_in_body(&body, ctx); | ||
826 | vars_defined_in_body | 848 | vars_defined_in_body |
849 | .into_iter() | ||
850 | .filter_map(|var| var_outlives_body(ctx, body, var, parent)) | ||
851 | .collect() | ||
827 | } | 852 | } |
828 | 853 | ||
829 | /// checks if the relevant local was defined before(outside of) body | 854 | /// checks if the relevant local was defined before(outside of) body |
@@ -843,11 +868,23 @@ fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode { | |||
843 | } | 868 | } |
844 | } | 869 | } |
845 | 870 | ||
846 | /// checks if local variable is used after(outside of) body | 871 | /// returns usage details if local variable is used after(outside of) body |
847 | fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool { | 872 | fn var_outlives_body( |
848 | let usages = LocalUsages::find(ctx, *var); | 873 | ctx: &AssistContext, |
874 | body: &FunctionBody, | ||
875 | var: Local, | ||
876 | parent: &SyntaxNode, | ||
877 | ) -> Option<OutlivedLocal> { | ||
878 | let usages = LocalUsages::find(ctx, var); | ||
849 | let has_usages = usages.iter().any(|reference| body.preceedes_range(reference.range)); | 879 | let has_usages = usages.iter().any(|reference| body.preceedes_range(reference.range)); |
850 | has_usages | 880 | if !has_usages { |
881 | return None; | ||
882 | } | ||
883 | let has_mut_usages = usages | ||
884 | .iter() | ||
885 | .filter(|reference| body.preceedes_range(reference.range)) | ||
886 | .any(|reference| reference_is_exclusive(reference, parent, ctx)); | ||
887 | Some(OutlivedLocal { local: var, mut_usage_outside_body: has_mut_usages }) | ||
851 | } | 888 | } |
852 | 889 | ||
853 | fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> { | 890 | fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> { |
@@ -927,16 +964,25 @@ fn format_replacement(ctx: &AssistContext, fun: &Function, indent: IndentLevel) | |||
927 | let mut buf = String::new(); | 964 | let mut buf = String::new(); |
928 | match fun.vars_defined_in_body_and_outlive.as_slice() { | 965 | match fun.vars_defined_in_body_and_outlive.as_slice() { |
929 | [] => {} | 966 | [] => {} |
930 | [var] => format_to!(buf, "let {} = ", var.name(ctx.db()).unwrap()), | 967 | [var] => { |
968 | format_to!(buf, "let {}{} = ", mut_modifier(var), var.local.name(ctx.db()).unwrap()) | ||
969 | } | ||
931 | [v0, vs @ ..] => { | 970 | [v0, vs @ ..] => { |
932 | buf.push_str("let ("); | 971 | buf.push_str("let ("); |
933 | format_to!(buf, "{}", v0.name(ctx.db()).unwrap()); | 972 | format_to!(buf, "{}{}", mut_modifier(v0), v0.local.name(ctx.db()).unwrap()); |
934 | for var in vs { | 973 | for var in vs { |
935 | format_to!(buf, ", {}", var.name(ctx.db()).unwrap()); | 974 | format_to!(buf, ", {}{}", mut_modifier(var), var.local.name(ctx.db()).unwrap()); |
936 | } | 975 | } |
937 | buf.push_str(") = "); | 976 | buf.push_str(") = "); |
938 | } | 977 | } |
939 | } | 978 | } |
979 | fn mut_modifier(var: &OutlivedLocal) -> &'static str { | ||
980 | if var.mut_usage_outside_body { | ||
981 | "mut " | ||
982 | } else { | ||
983 | "" | ||
984 | } | ||
985 | } | ||
940 | format_to!(buf, "{}", expr); | 986 | format_to!(buf, "{}", expr); |
941 | if fun.ret_ty.is_unit() | 987 | if fun.ret_ty.is_unit() |
942 | && (!fun.vars_defined_in_body_and_outlive.is_empty() || !expr.is_block_like()) | 988 | && (!fun.vars_defined_in_body_and_outlive.is_empty() || !expr.is_block_like()) |
@@ -1199,10 +1245,10 @@ fn make_body( | |||
1199 | match fun.vars_defined_in_body_and_outlive.as_slice() { | 1245 | match fun.vars_defined_in_body_and_outlive.as_slice() { |
1200 | [] => {} | 1246 | [] => {} |
1201 | [var] => { | 1247 | [var] => { |
1202 | tail_expr = Some(path_expr_from_local(ctx, *var)); | 1248 | tail_expr = Some(path_expr_from_local(ctx, var.local)); |
1203 | } | 1249 | } |
1204 | vars => { | 1250 | vars => { |
1205 | let exprs = vars.iter().map(|var| path_expr_from_local(ctx, *var)); | 1251 | let exprs = vars.iter().map(|var| path_expr_from_local(ctx, var.local)); |
1206 | let expr = make::expr_tuple(exprs); | 1252 | let expr = make::expr_tuple(exprs); |
1207 | tail_expr = Some(expr); | 1253 | tail_expr = Some(expr); |
1208 | } | 1254 | } |
@@ -2111,6 +2157,30 @@ fn $0fun_name(n: i32) -> i32 { | |||
2111 | } | 2157 | } |
2112 | 2158 | ||
2113 | #[test] | 2159 | #[test] |
2160 | fn variable_defined_inside_and_used_after_mutably_no_ret() { | ||
2161 | check_assist( | ||
2162 | extract_function, | ||
2163 | r" | ||
2164 | fn foo() { | ||
2165 | let n = 1; | ||
2166 | $0let mut k = n * n;$0 | ||
2167 | k += 1; | ||
2168 | }", | ||
2169 | r" | ||
2170 | fn foo() { | ||
2171 | let n = 1; | ||
2172 | let mut k = fun_name(n); | ||
2173 | k += 1; | ||
2174 | } | ||
2175 | |||
2176 | fn $0fun_name(n: i32) -> i32 { | ||
2177 | let mut k = n * n; | ||
2178 | k | ||
2179 | }", | ||
2180 | ); | ||
2181 | } | ||
2182 | |||
2183 | #[test] | ||
2114 | fn two_variables_defined_inside_and_used_after_no_ret() { | 2184 | fn two_variables_defined_inside_and_used_after_no_ret() { |
2115 | check_assist( | 2185 | check_assist( |
2116 | extract_function, | 2186 | extract_function, |
@@ -2137,6 +2207,38 @@ fn $0fun_name(n: i32) -> (i32, i32) { | |||
2137 | } | 2207 | } |
2138 | 2208 | ||
2139 | #[test] | 2209 | #[test] |
2210 | fn multi_variables_defined_inside_and_used_after_mutably_no_ret() { | ||
2211 | check_assist( | ||
2212 | extract_function, | ||
2213 | r" | ||
2214 | fn foo() { | ||
2215 | let n = 1; | ||
2216 | $0let mut k = n * n; | ||
2217 | let mut m = k + 2; | ||
2218 | let mut o = m + 3; | ||
2219 | o += 1;$0 | ||
2220 | k += o; | ||
2221 | m = 1; | ||
2222 | }", | ||
2223 | r" | ||
2224 | fn foo() { | ||
2225 | let n = 1; | ||
2226 | let (mut k, mut m, o) = fun_name(n); | ||
2227 | k += o; | ||
2228 | m = 1; | ||
2229 | } | ||
2230 | |||
2231 | fn $0fun_name(n: i32) -> (i32, i32, i32) { | ||
2232 | let mut k = n * n; | ||
2233 | let mut m = k + 2; | ||
2234 | let mut o = m + 3; | ||
2235 | o += 1; | ||
2236 | (k, m, o) | ||
2237 | }", | ||
2238 | ); | ||
2239 | } | ||
2240 | |||
2241 | #[test] | ||
2140 | fn nontrivial_patterns_define_variables() { | 2242 | fn nontrivial_patterns_define_variables() { |
2141 | check_assist( | 2243 | check_assist( |
2142 | extract_function, | 2244 | extract_function, |
@@ -3372,4 +3474,36 @@ fn foo() -> Result<(), i64> { | |||
3372 | }"##, | 3474 | }"##, |
3373 | ); | 3475 | ); |
3374 | } | 3476 | } |
3477 | |||
3478 | #[test] | ||
3479 | fn param_usage_in_macro() { | ||
3480 | check_assist( | ||
3481 | extract_function, | ||
3482 | r" | ||
3483 | macro_rules! m { | ||
3484 | ($val:expr) => { $val }; | ||
3485 | } | ||
3486 | |||
3487 | fn foo() { | ||
3488 | let n = 1; | ||
3489 | $0let k = n * m!(n);$0 | ||
3490 | let m = k + 1; | ||
3491 | }", | ||
3492 | r" | ||
3493 | macro_rules! m { | ||
3494 | ($val:expr) => { $val }; | ||
3495 | } | ||
3496 | |||
3497 | fn foo() { | ||
3498 | let n = 1; | ||
3499 | let k = fun_name(n); | ||
3500 | let m = k + 1; | ||
3501 | } | ||
3502 | |||
3503 | fn $0fun_name(n: i32) -> i32 { | ||
3504 | let k = n * m!(n); | ||
3505 | k | ||
3506 | }", | ||
3507 | ); | ||
3508 | } | ||
3375 | } | 3509 | } |
diff --git a/crates/ide_assists/src/handlers/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/remove_dbg.rs b/crates/ide_assists/src/handlers/remove_dbg.rs index 2862cfa9c..c8226550f 100644 --- a/crates/ide_assists/src/handlers/remove_dbg.rs +++ b/crates/ide_assists/src/handlers/remove_dbg.rs | |||
@@ -30,7 +30,7 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
30 | if new_contents.is_empty() { | 30 | if new_contents.is_empty() { |
31 | match_ast! { | 31 | match_ast! { |
32 | match it { | 32 | match it { |
33 | ast::BlockExpr(it) => { | 33 | ast::BlockExpr(_it) => { |
34 | macro_call.syntax() | 34 | macro_call.syntax() |
35 | .prev_sibling_or_token() | 35 | .prev_sibling_or_token() |
36 | .and_then(whitespace_start) | 36 | .and_then(whitespace_start) |
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 3e2c82dac..3694f468f 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -28,7 +28,9 @@ pub use assist_config::AssistConfig; | |||
28 | 28 | ||
29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
30 | pub enum AssistKind { | 30 | pub enum AssistKind { |
31 | // FIXME: does the None variant make sense? Probably not. | ||
31 | None, | 32 | None, |
33 | |||
32 | QuickFix, | 34 | QuickFix, |
33 | Generate, | 35 | Generate, |
34 | Refactor, | 36 | Refactor, |