diff options
author | Vladyslav Katasonov <[email protected]> | 2021-02-03 09:27:53 +0000 |
---|---|---|
committer | Vladyslav Katasonov <[email protected]> | 2021-02-03 09:27:53 +0000 |
commit | 1e6f13a0bee0d9600e7b582fbd9a2e1f4a9a24fc (patch) | |
tree | 08d6bacb9e2715bdde46bad274bb457aa2fe46d4 /crates/assists/src/handlers | |
parent | bc3ae81a873173346df6cb000e503233d7558d03 (diff) |
support extracting methods; no mut lowering
currently mut refernce will *not* be downgraded to shared
if it is sufficient(see relevant test for example)
Diffstat (limited to 'crates/assists/src/handlers')
-rw-r--r-- | crates/assists/src/handlers/extract_function.rs | 228 |
1 files changed, 191 insertions, 37 deletions
diff --git a/crates/assists/src/handlers/extract_function.rs b/crates/assists/src/handlers/extract_function.rs index 1a6cfebed..09c2a9bc7 100644 --- a/crates/assists/src/handlers/extract_function.rs +++ b/crates/assists/src/handlers/extract_function.rs | |||
@@ -68,6 +68,11 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
68 | if body.is_none() && node.kind() == BLOCK_EXPR { | 68 | if body.is_none() && node.kind() == BLOCK_EXPR { |
69 | body = FunctionBody::from_range(&node, ctx.frange.range); | 69 | body = FunctionBody::from_range(&node, ctx.frange.range); |
70 | } | 70 | } |
71 | if let Some(parent) = node.parent() { | ||
72 | if body.is_none() && parent.kind() == BLOCK_EXPR { | ||
73 | body = FunctionBody::from_range(&parent, ctx.frange.range); | ||
74 | } | ||
75 | } | ||
71 | if body.is_none() { | 76 | if body.is_none() { |
72 | body = FunctionBody::from_whole_node(node.clone()); | 77 | body = FunctionBody::from_whole_node(node.clone()); |
73 | } | 78 | } |
@@ -76,10 +81,47 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
76 | } | 81 | } |
77 | let body = body?; | 82 | let body = body?; |
78 | 83 | ||
79 | let insert_after = body.scope_for_fn_insertion()?; | 84 | let mut self_param = None; |
85 | let mut param_pats: Vec<_> = local_variables(&body, &ctx) | ||
86 | .into_iter() | ||
87 | .map(|node| node.source(ctx.db())) | ||
88 | .filter(|src| { | ||
89 | src.file_id.original_file(ctx.db()) == ctx.frange.file_id | ||
90 | && !body.contains_node(&either_syntax(&src.value)) | ||
91 | }) | ||
92 | .filter_map(|src| match src.value { | ||
93 | Either::Left(pat) => Some(pat), | ||
94 | Either::Right(it) => { | ||
95 | // we filter self param, as there can only be one | ||
96 | self_param = Some(it); | ||
97 | None | ||
98 | } | ||
99 | }) | ||
100 | .collect(); | ||
101 | deduplicate_params(&mut param_pats); | ||
80 | 102 | ||
103 | let anchor = if self_param.is_some() { Anchor::Method } else { Anchor::Freestanding }; | ||
104 | let insert_after = body.scope_for_fn_insertion(anchor)?; | ||
81 | let module = ctx.sema.scope(&insert_after).module()?; | 105 | let module = ctx.sema.scope(&insert_after).module()?; |
82 | 106 | ||
107 | let params = param_pats | ||
108 | .into_iter() | ||
109 | .map(|pat| { | ||
110 | let ty = pat | ||
111 | .pat() | ||
112 | .and_then(|pat| ctx.sema.type_of_pat(&pat)) | ||
113 | .and_then(|ty| ty.display_source_code(ctx.db(), module.into()).ok()) | ||
114 | .unwrap_or_else(|| "()".to_string()); | ||
115 | |||
116 | let name = pat.name().unwrap().to_string(); | ||
117 | |||
118 | Param { name, ty } | ||
119 | }) | ||
120 | .collect::<Vec<_>>(); | ||
121 | |||
122 | let self_param = | ||
123 | if let Some(self_param) = self_param { Some(self_param.to_string()) } else { None }; | ||
124 | |||
83 | let expr = body.tail_expr(); | 125 | let expr = body.tail_expr(); |
84 | let ret_ty = match expr { | 126 | let ret_ty = match expr { |
85 | Some(expr) => { | 127 | Some(expr) => { |
@@ -96,36 +138,12 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
96 | FunctionBody::Span { .. } => ctx.frange.range, | 138 | FunctionBody::Span { .. } => ctx.frange.range, |
97 | }; | 139 | }; |
98 | 140 | ||
99 | let mut params = local_variables(&body, &ctx) | ||
100 | .into_iter() | ||
101 | .map(|node| node.source(ctx.db())) | ||
102 | .filter(|src| src.file_id.original_file(ctx.db()) == ctx.frange.file_id) | ||
103 | .map(|src| match src.value { | ||
104 | Either::Left(pat) => { | ||
105 | (pat.syntax().clone(), pat.name(), ctx.sema.type_of_pat(&pat.into())) | ||
106 | } | ||
107 | Either::Right(it) => (it.syntax().clone(), it.name(), ctx.sema.type_of_self(&it)), | ||
108 | }) | ||
109 | .filter(|(node, _, _)| !body.contains_node(node)) | ||
110 | .map(|(_, name, ty)| { | ||
111 | let ty = ty | ||
112 | .and_then(|ty| ty.display_source_code(ctx.db(), module.into()).ok()) | ||
113 | .unwrap_or_else(|| "()".to_string()); | ||
114 | |||
115 | let name = name.unwrap().to_string(); | ||
116 | |||
117 | Param { name, ty } | ||
118 | }) | ||
119 | .collect::<Vec<_>>(); | ||
120 | deduplicate_params(&mut params); | ||
121 | |||
122 | acc.add( | 141 | acc.add( |
123 | AssistId("extract_function", crate::AssistKind::RefactorExtract), | 142 | AssistId("extract_function", crate::AssistKind::RefactorExtract), |
124 | "Extract into function", | 143 | "Extract into function", |
125 | target_range, | 144 | target_range, |
126 | move |builder| { | 145 | move |builder| { |
127 | 146 | let fun = Function { name: "fun_name".to_string(), self_param, params, ret_ty, body }; | |
128 | let fun = Function { name: "fun_name".to_string(), params, ret_ty, body }; | ||
129 | 147 | ||
130 | builder.replace(target_range, format_replacement(&fun)); | 148 | builder.replace(target_range, format_replacement(&fun)); |
131 | 149 | ||
@@ -140,6 +158,9 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
140 | 158 | ||
141 | fn format_replacement(fun: &Function) -> String { | 159 | fn format_replacement(fun: &Function) -> String { |
142 | let mut buf = String::new(); | 160 | let mut buf = String::new(); |
161 | if fun.self_param.is_some() { | ||
162 | format_to!(buf, "self."); | ||
163 | } | ||
143 | format_to!(buf, "{}(", fun.name); | 164 | format_to!(buf, "{}(", fun.name); |
144 | { | 165 | { |
145 | let mut it = fun.params.iter(); | 166 | let mut it = fun.params.iter(); |
@@ -161,6 +182,7 @@ fn format_replacement(fun: &Function) -> String { | |||
161 | 182 | ||
162 | struct Function { | 183 | struct Function { |
163 | name: String, | 184 | name: String, |
185 | self_param: Option<String>, | ||
164 | params: Vec<Param>, | 186 | params: Vec<Param>, |
165 | ret_ty: Option<String>, | 187 | ret_ty: Option<String>, |
166 | body: FunctionBody, | 188 | body: FunctionBody, |
@@ -186,7 +208,9 @@ fn format_function(fun: &Function, indent: IndentLevel) -> String { | |||
186 | format_to!(fn_def, "\n\n{}fn $0{}(", indent, fun.name); | 208 | format_to!(fn_def, "\n\n{}fn $0{}(", indent, fun.name); |
187 | { | 209 | { |
188 | let mut it = fun.params.iter(); | 210 | let mut it = fun.params.iter(); |
189 | if let Some(param) = it.next() { | 211 | if let Some(self_param) = &fun.self_param { |
212 | format_to!(fn_def, "{}", self_param); | ||
213 | } else if let Some(param) = it.next() { | ||
190 | format_to!(fn_def, "{}: {}", param.name, param.ty); | 214 | format_to!(fn_def, "{}: {}", param.name, param.ty); |
191 | } | 215 | } |
192 | for param in it { | 216 | for param in it { |
@@ -230,6 +254,11 @@ enum FunctionBody { | |||
230 | Span { elements: Vec<SyntaxElement>, leading_indent: String }, | 254 | Span { elements: Vec<SyntaxElement>, leading_indent: String }, |
231 | } | 255 | } |
232 | 256 | ||
257 | enum Anchor { | ||
258 | Freestanding, | ||
259 | Method, | ||
260 | } | ||
261 | |||
233 | impl FunctionBody { | 262 | impl FunctionBody { |
234 | fn from_whole_node(node: SyntaxNode) -> Option<Self> { | 263 | fn from_whole_node(node: SyntaxNode) -> Option<Self> { |
235 | match node.kind() { | 264 | match node.kind() { |
@@ -288,12 +317,12 @@ impl FunctionBody { | |||
288 | } | 317 | } |
289 | } | 318 | } |
290 | 319 | ||
291 | fn scope_for_fn_insertion(&self) -> Option<SyntaxNode> { | 320 | fn scope_for_fn_insertion(&self, anchor: Anchor) -> Option<SyntaxNode> { |
292 | match self { | 321 | match self { |
293 | FunctionBody::Expr(e) => scope_for_fn_insertion(e.syntax()), | 322 | FunctionBody::Expr(e) => scope_for_fn_insertion(e.syntax(), anchor), |
294 | FunctionBody::Span { elements, .. } => { | 323 | FunctionBody::Span { elements, .. } => { |
295 | let node = elements.iter().find_map(|e| e.as_node())?; | 324 | let node = elements.iter().find_map(|e| e.as_node())?; |
296 | scope_for_fn_insertion(&node) | 325 | scope_for_fn_insertion(&node, anchor) |
297 | } | 326 | } |
298 | } | 327 | } |
299 | } | 328 | } |
@@ -325,14 +354,25 @@ impl FunctionBody { | |||
325 | } | 354 | } |
326 | } | 355 | } |
327 | 356 | ||
328 | fn scope_for_fn_insertion(node: &SyntaxNode) -> Option<SyntaxNode> { | 357 | fn scope_for_fn_insertion(node: &SyntaxNode, anchor: Anchor) -> Option<SyntaxNode> { |
329 | let mut ancestors = node.ancestors().peekable(); | 358 | let mut ancestors = node.ancestors().peekable(); |
330 | let mut last_ancestor = None; | 359 | let mut last_ancestor = None; |
331 | while let Some(next_ancestor) = ancestors.next() { | 360 | while let Some(next_ancestor) = ancestors.next() { |
332 | match next_ancestor.kind() { | 361 | match next_ancestor.kind() { |
333 | SyntaxKind::SOURCE_FILE => break, | 362 | SyntaxKind::SOURCE_FILE => break, |
334 | SyntaxKind::ITEM_LIST => { | 363 | SyntaxKind::ITEM_LIST => { |
335 | if ancestors.peek().map(|a| a.kind()) == Some(SyntaxKind::MODULE) { | 364 | if !matches!(anchor, Anchor::Freestanding) { |
365 | continue; | ||
366 | } | ||
367 | if ancestors.peek().map(SyntaxNode::kind) == Some(SyntaxKind::MODULE) { | ||
368 | break; | ||
369 | } | ||
370 | } | ||
371 | SyntaxKind::ASSOC_ITEM_LIST => { | ||
372 | if !matches!(anchor, Anchor::Method) { | ||
373 | continue; | ||
374 | } | ||
375 | if ancestors.peek().map(SyntaxNode::kind) == Some(SyntaxKind::IMPL) { | ||
336 | break; | 376 | break; |
337 | } | 377 | } |
338 | } | 378 | } |
@@ -343,15 +383,21 @@ fn scope_for_fn_insertion(node: &SyntaxNode) -> Option<SyntaxNode> { | |||
343 | last_ancestor | 383 | last_ancestor |
344 | } | 384 | } |
345 | 385 | ||
346 | fn deduplicate_params(params: &mut Vec<Param>) { | 386 | fn deduplicate_params(params: &mut Vec<ast::IdentPat>) { |
347 | let mut seen_params = FxHashSet::default(); | 387 | let mut seen_params = FxHashSet::default(); |
348 | params.retain(|p| seen_params.insert(p.name.clone())); | 388 | params.retain(|p| seen_params.insert(p.clone())); |
389 | } | ||
390 | |||
391 | fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode { | ||
392 | match value { | ||
393 | Either::Left(pat) => pat.syntax(), | ||
394 | Either::Right(it) => it.syntax(), | ||
395 | } | ||
349 | } | 396 | } |
350 | 397 | ||
351 | /// Returns a vector of local variables that are refferenced in `body` | 398 | /// Returns a vector of local variables that are refferenced in `body` |
352 | fn local_variables(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> { | 399 | fn local_variables(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> { |
353 | body | 400 | body.descendants() |
354 | .descendants() | ||
355 | .filter_map(ast::NameRef::cast) | 401 | .filter_map(ast::NameRef::cast) |
356 | .filter_map(|name_ref| NameRefClass::classify(&ctx.sema, &name_ref)) | 402 | .filter_map(|name_ref| NameRefClass::classify(&ctx.sema, &name_ref)) |
357 | .map(|name_kind| name_kind.referenced(ctx.db())) | 403 | .map(|name_kind| name_kind.referenced(ctx.db())) |
@@ -386,7 +432,7 @@ fn $0fun_name() -> i32 { | |||
386 | }"#, | 432 | }"#, |
387 | ); | 433 | ); |
388 | } | 434 | } |
389 | 435 | ||
390 | #[test] | 436 | #[test] |
391 | fn no_args_from_binary_expr_in_module() { | 437 | fn no_args_from_binary_expr_in_module() { |
392 | check_assist( | 438 | check_assist( |
@@ -816,4 +862,112 @@ fn $0fun_name() -> u32 { | |||
816 | fn return_not_applicable() { | 862 | fn return_not_applicable() { |
817 | check_assist_not_applicable(extract_function, r"fn foo() { $0return$0; } "); | 863 | check_assist_not_applicable(extract_function, r"fn foo() { $0return$0; } "); |
818 | } | 864 | } |
865 | |||
866 | #[test] | ||
867 | fn method_to_freestanding() { | ||
868 | check_assist( | ||
869 | extract_function, | ||
870 | r" | ||
871 | struct S; | ||
872 | |||
873 | impl S { | ||
874 | fn foo(&self) -> i32 { | ||
875 | $01+1$0 | ||
876 | } | ||
877 | }", | ||
878 | r" | ||
879 | struct S; | ||
880 | |||
881 | impl S { | ||
882 | fn foo(&self) -> i32 { | ||
883 | fun_name() | ||
884 | } | ||
885 | } | ||
886 | |||
887 | fn $0fun_name() -> i32 { | ||
888 | 1+1 | ||
889 | }", | ||
890 | ); | ||
891 | } | ||
892 | |||
893 | #[test] | ||
894 | fn method_with_reference() { | ||
895 | check_assist( | ||
896 | extract_function, | ||
897 | r" | ||
898 | struct S { f: i32 }; | ||
899 | |||
900 | impl S { | ||
901 | fn foo(&self) -> i32 { | ||
902 | $01+self.f$0 | ||
903 | } | ||
904 | }", | ||
905 | r" | ||
906 | struct S { f: i32 }; | ||
907 | |||
908 | impl S { | ||
909 | fn foo(&self) -> i32 { | ||
910 | self.fun_name() | ||
911 | } | ||
912 | |||
913 | fn $0fun_name(&self) -> i32 { | ||
914 | 1+self.f | ||
915 | } | ||
916 | }", | ||
917 | ); | ||
918 | } | ||
919 | |||
920 | #[test] | ||
921 | fn method_with_mut() { | ||
922 | check_assist( | ||
923 | extract_function, | ||
924 | r" | ||
925 | struct S { f: i32 }; | ||
926 | |||
927 | impl S { | ||
928 | fn foo(&mut self) { | ||
929 | $0self.f += 1;$0 | ||
930 | } | ||
931 | }", | ||
932 | r" | ||
933 | struct S { f: i32 }; | ||
934 | |||
935 | impl S { | ||
936 | fn foo(&mut self) { | ||
937 | self.fun_name(); | ||
938 | } | ||
939 | |||
940 | fn $0fun_name(&mut self) { | ||
941 | self.f += 1; | ||
942 | } | ||
943 | }", | ||
944 | ); | ||
945 | } | ||
946 | |||
947 | #[test] | ||
948 | fn method_with_mut_downgrade_to_shared() { | ||
949 | check_assist( | ||
950 | extract_function, | ||
951 | r" | ||
952 | struct S { f: i32 }; | ||
953 | |||
954 | impl S { | ||
955 | fn foo(&mut self) -> i32 { | ||
956 | $01+self.f$0 | ||
957 | } | ||
958 | }", | ||
959 | r" | ||
960 | struct S { f: i32 }; | ||
961 | |||
962 | impl S { | ||
963 | fn foo(&mut self) -> i32 { | ||
964 | self.fun_name() | ||
965 | } | ||
966 | |||
967 | fn $0fun_name(&self) -> i32 { | ||
968 | 1+self.f | ||
969 | } | ||
970 | }", | ||
971 | ); | ||
972 | } | ||
819 | } | 973 | } |