aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists')
-rw-r--r--crates/ide_assists/src/handlers/extract_function.rs128
-rw-r--r--crates/ide_assists/src/handlers/flip_comma.rs18
-rw-r--r--crates/ide_assists/src/handlers/remove_dbg.rs105
-rw-r--r--crates/ide_assists/src/lib.rs2
4 files changed, 207 insertions, 46 deletions
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs
index 719f22053..059414274 100644
--- a/crates/ide_assists/src/handlers/extract_function.rs
+++ b/crates/ide_assists/src/handlers/extract_function.rs
@@ -75,7 +75,8 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
75 let insert_after = scope_for_fn_insertion(&body, anchor)?; 75 let insert_after = scope_for_fn_insertion(&body, anchor)?;
76 let module = ctx.sema.scope(&insert_after).module()?; 76 let module = ctx.sema.scope(&insert_after).module()?;
77 77
78 let vars_defined_in_body_and_outlive = vars_defined_in_body_and_outlive(ctx, &body); 78 let vars_defined_in_body_and_outlive =
79 vars_defined_in_body_and_outlive(ctx, &body, &node.parent().as_ref().unwrap_or(&node));
79 let ret_ty = body_return_ty(ctx, &body)?; 80 let ret_ty = body_return_ty(ctx, &body)?;
80 81
81 // FIXME: we compute variables that outlive here just to check `never!` condition 82 // FIXME: we compute variables that outlive here just to check `never!` condition
@@ -257,7 +258,7 @@ struct Function {
257 control_flow: ControlFlow, 258 control_flow: ControlFlow,
258 ret_ty: RetType, 259 ret_ty: RetType,
259 body: FunctionBody, 260 body: FunctionBody,
260 vars_defined_in_body_and_outlive: Vec<Local>, 261 vars_defined_in_body_and_outlive: Vec<OutlivedLocal>,
261} 262}
262 263
263#[derive(Debug)] 264#[derive(Debug)]
@@ -296,9 +297,9 @@ impl Function {
296 RetType::Expr(ty) => FunType::Single(ty.clone()), 297 RetType::Expr(ty) => FunType::Single(ty.clone()),
297 RetType::Stmt => match self.vars_defined_in_body_and_outlive.as_slice() { 298 RetType::Stmt => match self.vars_defined_in_body_and_outlive.as_slice() {
298 [] => FunType::Unit, 299 [] => FunType::Unit,
299 [var] => FunType::Single(var.ty(ctx.db())), 300 [var] => FunType::Single(var.local.ty(ctx.db())),
300 vars => { 301 vars => {
301 let types = vars.iter().map(|v| v.ty(ctx.db())).collect(); 302 let types = vars.iter().map(|v| v.local.ty(ctx.db())).collect();
302 FunType::Tuple(types) 303 FunType::Tuple(types)
303 } 304 }
304 }, 305 },
@@ -562,6 +563,12 @@ impl HasTokenAtOffset for FunctionBody {
562 } 563 }
563} 564}
564 565
566#[derive(Debug)]
567struct OutlivedLocal {
568 local: Local,
569 mut_usage_outside_body: bool,
570}
571
565/// Try to guess what user wants to extract 572/// Try to guess what user wants to extract
566/// 573///
567/// We have basically have two cases: 574/// We have basically have two cases:
@@ -707,10 +714,10 @@ fn has_exclusive_usages(ctx: &AssistContext, usages: &LocalUsages, body: &Functi
707 .any(|reference| reference_is_exclusive(reference, body, ctx)) 714 .any(|reference| reference_is_exclusive(reference, body, ctx))
708} 715}
709 716
710/// checks if this reference requires `&mut` access inside body 717/// checks if this reference requires `&mut` access inside node
711fn reference_is_exclusive( 718fn reference_is_exclusive(
712 reference: &FileReference, 719 reference: &FileReference,
713 body: &FunctionBody, 720 node: &dyn HasTokenAtOffset,
714 ctx: &AssistContext, 721 ctx: &AssistContext,
715) -> bool { 722) -> bool {
716 // we directly modify variable with set: `n = 0`, `n += 1` 723 // we directly modify variable with set: `n = 0`, `n += 1`
@@ -719,7 +726,7 @@ fn reference_is_exclusive(
719 } 726 }
720 727
721 // we take `&mut` reference to variable: `&mut v` 728 // we take `&mut` reference to variable: `&mut v`
722 let path = match path_element_of_reference(body, reference) { 729 let path = match path_element_of_reference(node, reference) {
723 Some(path) => path, 730 Some(path) => path,
724 None => return false, 731 None => return false,
725 }; 732 };
@@ -832,10 +839,16 @@ fn vars_defined_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local>
832} 839}
833 840
834/// 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
835fn vars_defined_in_body_and_outlive(ctx: &AssistContext, body: &FunctionBody) -> Vec<Local> { 842fn vars_defined_in_body_and_outlive(
836 let mut vars_defined_in_body = vars_defined_in_body(&body, ctx); 843 ctx: &AssistContext,
837 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);
838 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()
839} 852}
840 853
841/// checks if the relevant local was defined before(outside of) body 854/// checks if the relevant local was defined before(outside of) body
@@ -855,11 +868,23 @@ fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode {
855 } 868 }
856} 869}
857 870
858/// checks if local variable is used after(outside of) body 871/// returns usage details if local variable is used after(outside of) body
859fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool { 872fn var_outlives_body(
860 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);
861 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));
862 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 })
863} 888}
864 889
865fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> { 890fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> {
@@ -939,16 +964,25 @@ fn format_replacement(ctx: &AssistContext, fun: &Function, indent: IndentLevel)
939 let mut buf = String::new(); 964 let mut buf = String::new();
940 match fun.vars_defined_in_body_and_outlive.as_slice() { 965 match fun.vars_defined_in_body_and_outlive.as_slice() {
941 [] => {} 966 [] => {}
942 [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 }
943 [v0, vs @ ..] => { 970 [v0, vs @ ..] => {
944 buf.push_str("let ("); 971 buf.push_str("let (");
945 format_to!(buf, "{}", v0.name(ctx.db()).unwrap()); 972 format_to!(buf, "{}{}", mut_modifier(v0), v0.local.name(ctx.db()).unwrap());
946 for var in vs { 973 for var in vs {
947 format_to!(buf, ", {}", var.name(ctx.db()).unwrap()); 974 format_to!(buf, ", {}{}", mut_modifier(var), var.local.name(ctx.db()).unwrap());
948 } 975 }
949 buf.push_str(") = "); 976 buf.push_str(") = ");
950 } 977 }
951 } 978 }
979 fn mut_modifier(var: &OutlivedLocal) -> &'static str {
980 if var.mut_usage_outside_body {
981 "mut "
982 } else {
983 ""
984 }
985 }
952 format_to!(buf, "{}", expr); 986 format_to!(buf, "{}", expr);
953 if fun.ret_ty.is_unit() 987 if fun.ret_ty.is_unit()
954 && (!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())
@@ -1211,10 +1245,10 @@ fn make_body(
1211 match fun.vars_defined_in_body_and_outlive.as_slice() { 1245 match fun.vars_defined_in_body_and_outlive.as_slice() {
1212 [] => {} 1246 [] => {}
1213 [var] => { 1247 [var] => {
1214 tail_expr = Some(path_expr_from_local(ctx, *var)); 1248 tail_expr = Some(path_expr_from_local(ctx, var.local));
1215 } 1249 }
1216 vars => { 1250 vars => {
1217 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));
1218 let expr = make::expr_tuple(exprs); 1252 let expr = make::expr_tuple(exprs);
1219 tail_expr = Some(expr); 1253 tail_expr = Some(expr);
1220 } 1254 }
@@ -2123,6 +2157,30 @@ fn $0fun_name(n: i32) -> i32 {
2123 } 2157 }
2124 2158
2125 #[test] 2159 #[test]
2160 fn variable_defined_inside_and_used_after_mutably_no_ret() {
2161 check_assist(
2162 extract_function,
2163 r"
2164fn foo() {
2165 let n = 1;
2166 $0let mut k = n * n;$0
2167 k += 1;
2168}",
2169 r"
2170fn foo() {
2171 let n = 1;
2172 let mut k = fun_name(n);
2173 k += 1;
2174}
2175
2176fn $0fun_name(n: i32) -> i32 {
2177 let mut k = n * n;
2178 k
2179}",
2180 );
2181 }
2182
2183 #[test]
2126 fn two_variables_defined_inside_and_used_after_no_ret() { 2184 fn two_variables_defined_inside_and_used_after_no_ret() {
2127 check_assist( 2185 check_assist(
2128 extract_function, 2186 extract_function,
@@ -2149,6 +2207,38 @@ fn $0fun_name(n: i32) -> (i32, i32) {
2149 } 2207 }
2150 2208
2151 #[test] 2209 #[test]
2210 fn multi_variables_defined_inside_and_used_after_mutably_no_ret() {
2211 check_assist(
2212 extract_function,
2213 r"
2214fn foo() {
2215 let n = 1;
2216 $0let mut k = n * n;
2217 let mut m = k + 2;
2218 let mut o = m + 3;
2219 o += 1;$0
2220 k += o;
2221 m = 1;
2222}",
2223 r"
2224fn foo() {
2225 let n = 1;
2226 let (mut k, mut m, o) = fun_name(n);
2227 k += o;
2228 m = 1;
2229}
2230
2231fn $0fun_name(n: i32) -> (i32, i32, i32) {
2232 let mut k = n * n;
2233 let mut m = k + 2;
2234 let mut o = m + 3;
2235 o += 1;
2236 (k, m, o)
2237}",
2238 );
2239 }
2240
2241 #[test]
2152 fn nontrivial_patterns_define_variables() { 2242 fn nontrivial_patterns_define_variables() {
2153 check_assist( 2243 check_assist(
2154 extract_function, 2244 extract_function,
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 6114091f2..c8226550f 100644
--- a/crates/ide_assists/src/handlers/remove_dbg.rs
+++ b/crates/ide_assists/src/handlers/remove_dbg.rs
@@ -1,5 +1,5 @@
1use syntax::{ 1use syntax::{
2 ast::{self, AstNode}, 2 ast::{self, AstNode, AstToken},
3 match_ast, SyntaxElement, TextRange, TextSize, T, 3 match_ast, SyntaxElement, TextRange, TextSize, T,
4}; 4};
5 5
@@ -24,7 +24,39 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; 24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
25 let new_contents = adjusted_macro_contents(&macro_call)?; 25 let new_contents = adjusted_macro_contents(&macro_call)?;
26 26
27 let macro_text_range = macro_call.syntax().text_range(); 27 let parent = macro_call.syntax().parent();
28
29 let macro_text_range = if let Some(it) = parent.as_ref() {
30 if new_contents.is_empty() {
31 match_ast! {
32 match it {
33 ast::BlockExpr(_it) => {
34 macro_call.syntax()
35 .prev_sibling_or_token()
36 .and_then(whitespace_start)
37 .map(|start| TextRange::new(start, macro_call.syntax().text_range().end()))
38 .unwrap_or(macro_call.syntax().text_range())
39 },
40 ast::ExprStmt(it) => {
41 let start = it
42 .syntax()
43 .prev_sibling_or_token()
44 .and_then(whitespace_start)
45 .unwrap_or(it.syntax().text_range().start());
46 let end = it.syntax().text_range().end();
47
48 TextRange::new(start, end)
49 },
50 _ => macro_call.syntax().text_range()
51 }
52 }
53 } else {
54 macro_call.syntax().text_range()
55 }
56 } else {
57 macro_call.syntax().text_range()
58 };
59
28 let macro_end = if macro_call.semicolon_token().is_some() { 60 let macro_end = if macro_call.semicolon_token().is_some() {
29 macro_text_range.end() - TextSize::of(';') 61 macro_text_range.end() - TextSize::of(';')
30 } else { 62 } else {
@@ -36,11 +68,22 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36 "Remove dbg!()", 68 "Remove dbg!()",
37 macro_text_range, 69 macro_text_range,
38 |builder| { 70 |builder| {
39 builder.replace(TextRange::new(macro_text_range.start(), macro_end), new_contents); 71 builder.replace(
72 TextRange::new(macro_text_range.start(), macro_end),
73 if new_contents.is_empty() && parent.and_then(ast::LetStmt::cast).is_some() {
74 ast::make::expr_unit().to_string()
75 } else {
76 new_contents
77 },
78 );
40 }, 79 },
41 ) 80 )
42} 81}
43 82
83fn whitespace_start(it: SyntaxElement) -> Option<TextSize> {
84 Some(it.into_token().and_then(ast::Whitespace::cast)?.syntax().text_range().start())
85}
86
44fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> { 87fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> {
45 let contents = get_valid_macrocall_contents(&macro_call, "dbg")?; 88 let contents = get_valid_macrocall_contents(&macro_call, "dbg")?;
46 let macro_text_with_brackets = macro_call.token_tree()?.syntax().text(); 89 let macro_text_with_brackets = macro_call.token_tree()?.syntax().text();
@@ -94,15 +137,11 @@ fn get_valid_macrocall_contents(
94 let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>(); 137 let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>();
95 let last_child = contents_between_brackets.pop()?; 138 let last_child = contents_between_brackets.pop()?;
96 139
97 if contents_between_brackets.is_empty() { 140 match (first_child.kind(), last_child.kind()) {
98 None 141 (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => {
99 } else { 142 Some(contents_between_brackets)
100 match (first_child.kind(), last_child.kind()) {
101 (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => {
102 Some(contents_between_brackets)
103 }
104 _ => None,
105 } 143 }
144 _ => None,
106 } 145 }
107} 146}
108 147
@@ -418,4 +457,48 @@ fn main() {
418}"#, 457}"#,
419 ); 458 );
420 } 459 }
460
461 #[test]
462 fn test_remove_empty_dbg() {
463 check_assist(remove_dbg, r#"fn foo() { $0dbg!(); }"#, r#"fn foo() { }"#);
464 check_assist(
465 remove_dbg,
466 r#"
467fn foo() {
468 $0dbg!();
469}
470"#,
471 r#"
472fn foo() {
473}
474"#,
475 );
476 check_assist(
477 remove_dbg,
478 r#"
479fn foo() {
480 let test = $0dbg!();
481}"#,
482 r#"
483fn foo() {
484 let test = ();
485}"#,
486 );
487 check_assist(
488 remove_dbg,
489 r#"
490fn foo() {
491 let t = {
492 println!("Hello, world");
493 $0dbg!()
494 };
495}"#,
496 r#"
497fn foo() {
498 let t = {
499 println!("Hello, world");
500 };
501}"#,
502 );
503 }
421} 504}
diff --git a/crates/ide_assists/src/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)]
30pub enum AssistKind { 30pub enum AssistKind {
31 // FIXME: does the None variant make sense? Probably not.
31 None, 32 None,
33
32 QuickFix, 34 QuickFix,
33 Generate, 35 Generate,
34 Refactor, 36 Refactor,