aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/src/handlers/extract_function.rs246
1 files changed, 112 insertions, 134 deletions
diff --git a/crates/assists/src/handlers/extract_function.rs b/crates/assists/src/handlers/extract_function.rs
index d876eabca..4bddd4eec 100644
--- a/crates/assists/src/handlers/extract_function.rs
+++ b/crates/assists/src/handlers/extract_function.rs
@@ -1,3 +1,6 @@
1use std::{fmt, iter};
2
3use ast::make;
1use either::Either; 4use either::Either;
2use hir::{HirDisplay, Local}; 5use hir::{HirDisplay, Local};
3use ide_db::{ 6use ide_db::{
@@ -82,10 +85,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
82 return None; 85 return None;
83 } 86 }
84 87
85 let target_range = match &body { 88 let target_range = body.text_range();
86 FunctionBody::Expr(expr) => expr.syntax().text_range(),
87 FunctionBody::Span { .. } => ctx.frange.range,
88 };
89 89
90 acc.add( 90 acc.add(
91 AssistId("extract_function", crate::AssistKind::RefactorExtract), 91 AssistId("extract_function", crate::AssistKind::RefactorExtract),
@@ -209,7 +209,7 @@ impl RetType {
209#[derive(Debug)] 209#[derive(Debug)]
210enum FunctionBody { 210enum FunctionBody {
211 Expr(ast::Expr), 211 Expr(ast::Expr),
212 Span { elements: Vec<SyntaxElement>, leading_indent: String }, 212 Span { parent: ast::BlockExpr, text_range: TextRange },
213} 213}
214 214
215impl FunctionBody { 215impl FunctionBody {
@@ -226,58 +226,28 @@ impl FunctionBody {
226 } 226 }
227 } 227 }
228 228
229 fn from_range(node: &SyntaxNode, range: TextRange) -> Option<FunctionBody> { 229 fn from_range(node: SyntaxNode, text_range: TextRange) -> Option<FunctionBody> {
230 let mut first = node.token_at_offset(range.start()).left_biased()?; 230 let block = ast::BlockExpr::cast(node)?;
231 let last = node.token_at_offset(range.end()).right_biased()?; 231 Some(Self::Span { parent: block, text_range })
232
233 let mut leading_indent = String::new();
234
235 let leading_trivia = first
236 .siblings_with_tokens(Direction::Prev)
237 .skip(1)
238 .take_while(|e| e.kind() == SyntaxKind::WHITESPACE && e.as_token().is_some());
239
240 for e in leading_trivia {
241 let token = e.as_token().unwrap();
242 let text = token.text();
243 match text.rfind('\n') {
244 Some(pos) => {
245 leading_indent = text[pos..].to_owned();
246 break;
247 }
248 None => first = token.clone(),
249 }
250 }
251
252 let mut elements: Vec<_> = first
253 .siblings_with_tokens(Direction::Next)
254 .take_while(|e| e.as_token() != Some(&last))
255 .collect();
256
257 if !(last.kind() == SyntaxKind::WHITESPACE && last.text().lines().count() <= 2) {
258 elements.push(last.into());
259 }
260
261 Some(FunctionBody::Span { elements, leading_indent })
262 } 232 }
263 233
264 fn indent_level(&self) -> IndentLevel { 234 fn indent_level(&self) -> IndentLevel {
265 match &self { 235 match &self {
266 FunctionBody::Expr(expr) => IndentLevel::from_node(expr.syntax()), 236 FunctionBody::Expr(expr) => IndentLevel::from_node(expr.syntax()),
267 FunctionBody::Span { elements, .. } => elements 237 FunctionBody::Span { parent, .. } => IndentLevel::from_node(parent.syntax()),
268 .iter()
269 .filter_map(SyntaxElement::as_node)
270 .map(IndentLevel::from_node)
271 .min_by_key(|level| level.0)
272 .expect("body must contain at least one node"),
273 } 238 }
274 } 239 }
275 240
276 fn tail_expr(&self) -> Option<ast::Expr> { 241 fn tail_expr(&self) -> Option<ast::Expr> {
277 match &self { 242 match &self {
278 FunctionBody::Expr(expr) => Some(expr.clone()), 243 FunctionBody::Expr(expr) => Some(expr.clone()),
279 FunctionBody::Span { elements, .. } => { 244 FunctionBody::Span { parent, text_range } => {
280 elements.iter().rev().find_map(|e| e.as_node()).cloned().and_then(ast::Expr::cast) 245 let tail_expr = parent.tail_expr()?;
246 if text_range.contains_range(tail_expr.syntax().text_range()) {
247 Some(tail_expr)
248 } else {
249 None
250 }
281 } 251 }
282 } 252 }
283 } 253 }
@@ -285,11 +255,11 @@ impl FunctionBody {
285 fn descendants(&self) -> impl Iterator<Item = SyntaxNode> + '_ { 255 fn descendants(&self) -> impl Iterator<Item = SyntaxNode> + '_ {
286 match self { 256 match self {
287 FunctionBody::Expr(expr) => Either::Right(expr.syntax().descendants()), 257 FunctionBody::Expr(expr) => Either::Right(expr.syntax().descendants()),
288 FunctionBody::Span { elements, .. } => Either::Left( 258 FunctionBody::Span { parent, text_range } => Either::Left(
289 elements 259 parent
290 .iter() 260 .syntax()
291 .filter_map(SyntaxElement::as_node) 261 .descendants()
292 .flat_map(SyntaxNode::descendants), 262 .filter(move |it| text_range.contains_range(it.text_range())),
293 ), 263 ),
294 } 264 }
295 } 265 }
@@ -297,10 +267,7 @@ impl FunctionBody {
297 fn text_range(&self) -> TextRange { 267 fn text_range(&self) -> TextRange {
298 match self { 268 match self {
299 FunctionBody::Expr(expr) => expr.syntax().text_range(), 269 FunctionBody::Expr(expr) => expr.syntax().text_range(),
300 FunctionBody::Span { elements, .. } => TextRange::new( 270 FunctionBody::Span { parent: _, text_range } => *text_range,
301 elements.first().unwrap().text_range().start(),
302 elements.last().unwrap().text_range().end(),
303 ),
304 } 271 }
305 } 272 }
306 273
@@ -321,30 +288,27 @@ impl HasTokenAtOffset for FunctionBody {
321 fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset<SyntaxToken> { 288 fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset<SyntaxToken> {
322 match self { 289 match self {
323 FunctionBody::Expr(expr) => expr.syntax().token_at_offset(offset), 290 FunctionBody::Expr(expr) => expr.syntax().token_at_offset(offset),
324 FunctionBody::Span { elements, .. } => { 291 FunctionBody::Span { parent, text_range } => {
325 stdx::always!(self.text_range().contains(offset)); 292 match parent.syntax().token_at_offset(offset) {
326 let mut iter = elements 293 TokenAtOffset::None => TokenAtOffset::None,
327 .iter() 294 TokenAtOffset::Single(t) => {
328 .filter(|element| element.text_range().contains_inclusive(offset)); 295 if text_range.contains_range(t.text_range()) {
329 let element1 = iter.next().expect("offset does not fall into body"); 296 TokenAtOffset::Single(t)
330 let element2 = iter.next(); 297 } else {
331 stdx::always!(iter.next().is_none(), "> 2 tokens at offset"); 298 TokenAtOffset::None
332 let t1 = match element1 { 299 }
333 syntax::NodeOrToken::Node(node) => node.token_at_offset(offset), 300 }
334 syntax::NodeOrToken::Token(token) => TokenAtOffset::Single(token.clone()), 301 TokenAtOffset::Between(a, b) => {
335 }; 302 match (
336 let t2 = element2.map(|e| match e { 303 text_range.contains_range(a.text_range()),
337 syntax::NodeOrToken::Node(node) => node.token_at_offset(offset), 304 text_range.contains_range(b.text_range()),
338 syntax::NodeOrToken::Token(token) => TokenAtOffset::Single(token.clone()), 305 ) {
339 }); 306 (true, true) => TokenAtOffset::Between(a, b),
340 307 (true, false) => TokenAtOffset::Single(a),
341 match t2 { 308 (false, true) => TokenAtOffset::Single(b),
342 Some(t2) => match (t1.clone().right_biased(), t2.clone().left_biased()) { 309 (false, false) => TokenAtOffset::None,
343 (Some(e1), Some(e2)) => TokenAtOffset::Between(e1, e2), 310 }
344 (Some(_), None) => t1, 311 }
345 (None, _) => t2,
346 },
347 None => t1,
348 } 312 }
349 } 313 }
350 } 314 }
@@ -389,7 +353,7 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu
389 // we have selected a few statements in a block 353 // we have selected a few statements in a block
390 // so covering_element returns the whole block 354 // so covering_element returns the whole block
391 if node.kind() == BLOCK_EXPR { 355 if node.kind() == BLOCK_EXPR {
392 let body = FunctionBody::from_range(&node, selection_range); 356 let body = FunctionBody::from_range(node.clone(), selection_range);
393 if body.is_some() { 357 if body.is_some() {
394 return body; 358 return body;
395 } 359 }
@@ -400,7 +364,7 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu
400 // so we try to expand covering_element to parent and repeat the previous 364 // so we try to expand covering_element to parent and repeat the previous
401 if let Some(parent) = node.parent() { 365 if let Some(parent) = node.parent() {
402 if parent.kind() == BLOCK_EXPR { 366 if parent.kind() == BLOCK_EXPR {
403 let body = FunctionBody::from_range(&parent, selection_range); 367 let body = FunctionBody::from_range(parent, selection_range);
404 if body.is_some() { 368 if body.is_some() {
405 return body; 369 return body;
406 } 370 }
@@ -642,13 +606,9 @@ fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode {
642 606
643/// checks if local variable is used after(outside of) body 607/// checks if local variable is used after(outside of) body
644fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool { 608fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool {
645 let usages = Definition::Local(*var) 609 let usages = LocalUsages::find(ctx, *var);
646 .usages(&ctx.sema) 610 let has_usages = usages.iter().any(|reference| body.preceedes_range(reference.range));
647 .in_scope(SearchScope::single_file(ctx.frange.file_id)) 611 has_usages
648 .all();
649 let mut usages = usages.iter().flat_map(|(_, rs)| rs.iter());
650
651 usages.any(|reference| body.preceedes_range(reference.range))
652} 612}
653 613
654fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> { 614fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> {
@@ -675,10 +635,7 @@ enum Anchor {
675fn scope_for_fn_insertion(body: &FunctionBody, anchor: Anchor) -> Option<SyntaxNode> { 635fn scope_for_fn_insertion(body: &FunctionBody, anchor: Anchor) -> Option<SyntaxNode> {
676 match body { 636 match body {
677 FunctionBody::Expr(e) => scope_for_fn_insertion_node(e.syntax(), anchor), 637 FunctionBody::Expr(e) => scope_for_fn_insertion_node(e.syntax(), anchor),
678 FunctionBody::Span { elements, .. } => { 638 FunctionBody::Span { parent, .. } => scope_for_fn_insertion_node(parent.syntax(), anchor),
679 let node = elements.iter().find_map(|e| e.as_node())?;
680 scope_for_fn_insertion_node(&node, anchor)
681 }
682 } 639 }
683} 640}
684 641
@@ -768,9 +725,8 @@ fn format_function(
768 format_function_param_list_to(&mut fn_def, ctx, module, fun); 725 format_function_param_list_to(&mut fn_def, ctx, module, fun);
769 fn_def.push(')'); 726 fn_def.push(')');
770 format_function_ret_to(&mut fn_def, ctx, module, fun); 727 format_function_ret_to(&mut fn_def, ctx, module, fun);
771 fn_def.push_str(" {"); 728 fn_def.push(' ');
772 format_function_body_to(&mut fn_def, ctx, old_indent, new_indent, fun); 729 format_function_body_to(&mut fn_def, ctx, old_indent, new_indent, fun);
773 format_to!(fn_def, "{}}}", new_indent);
774 730
775 fn_def 731 fn_def
776} 732}
@@ -836,60 +792,82 @@ fn format_function_body_to(
836 new_indent: IndentLevel, 792 new_indent: IndentLevel,
837 fun: &Function, 793 fun: &Function,
838) { 794) {
839 match &fun.body { 795 let block = match &fun.body {
840 FunctionBody::Expr(expr) => { 796 FunctionBody::Expr(expr) => {
841 fn_def.push('\n'); 797 let expr = rewrite_body_segment(ctx, &fun.params, expr.syntax());
842 let expr = expr.dedent(old_indent).indent(new_indent + 1); 798 let expr = ast::Expr::cast(expr).unwrap();
843 let expr = fix_param_usages(ctx, &fun.params, expr.syntax()); 799 let expr = expr.dedent(old_indent).indent(IndentLevel(1));
844 format_to!(fn_def, "{}{}", new_indent + 1, expr); 800 let block = make::block_expr(Vec::new(), Some(expr));
845 fn_def.push('\n'); 801 block.indent(new_indent)
846 } 802 }
847 FunctionBody::Span { elements, leading_indent } => { 803 FunctionBody::Span { parent, text_range } => {
848 format_to!(fn_def, "{}", leading_indent); 804 let mut elements: Vec<_> = parent
849 let new_indent_str = format!("\n{}", new_indent + 1); 805 .syntax()
850 for mut element in elements { 806 .children()
851 let new_ws; 807 .filter(|it| text_range.contains_range(it.text_range()))
852 if let Some(ws) = element.as_token().cloned().and_then(ast::Whitespace::cast) { 808 .map(|it| rewrite_body_segment(ctx, &fun.params, &it))
853 let text = ws.syntax().text(); 809 .collect();
854 if text.contains('\n') { 810
855 let new_text = text.replace(&format!("\n{}", old_indent), &new_indent_str); 811 let mut tail_expr = match elements.pop() {
856 new_ws = ast::make::tokens::whitespace(&new_text).into(); 812 Some(node) => ast::Expr::cast(node.clone()).or_else(|| {
857 element = &new_ws; 813 elements.push(node);
858 } 814 None
859 } 815 }),
816 None => None,
817 };
860 818
861 match element { 819 if tail_expr.is_none() {
862 syntax::NodeOrToken::Node(node) => { 820 match fun.vars_defined_in_body_and_outlive.as_slice() {
863 format_to!(fn_def, "{}", fix_param_usages(ctx, &fun.params, node)); 821 [] => {}
864 } 822 [var] => {
865 syntax::NodeOrToken::Token(token) => { 823 tail_expr = Some(path_expr_from_local(ctx, *var));
866 format_to!(fn_def, "{}", token); 824 },
825 vars => {
826 let exprs = vars.iter()
827 .map(|var| path_expr_from_local(ctx, *var));
828 let expr = make::expr_tuple(exprs);
829 tail_expr = Some(expr);
867 } 830 }
868 } 831 }
869 } 832 }
870 if !fn_def.ends_with('\n') { 833
871 fn_def.push('\n'); 834 let elements = elements.into_iter().filter_map(|node| match ast::Stmt::cast(node) {
872 } 835 Some(stmt) => Some(stmt),
836 None => {
837 stdx::never!("block contains non-statement");
838 None
839 }
840 });
841
842
843 let block = make::block_expr(elements, tail_expr);
844 block.dedent(old_indent).indent(new_indent)
873 } 845 }
874 } 846 };
875 847
876 match fun.vars_defined_in_body_and_outlive.as_slice() {
877 [] => {}
878 [var] => format_to!(fn_def, "{}{}\n", new_indent + 1, var.name(ctx.db()).unwrap()),
879 [v0, vs @ ..] => {
880 format_to!(fn_def, "{}({}", new_indent + 1, v0.name(ctx.db()).unwrap());
881 for var in vs {
882 format_to!(fn_def, ", {}", var.name(ctx.db()).unwrap());
883 }
884 fn_def.push_str(")\n");
885 } 848 }
886 } 849
850 format_to!(fn_def, "{}", block);
851}
852
853fn path_expr_from_local(ctx: &AssistContext, var: Local) -> ast::Expr {
854 let name = var.name(ctx.db()).unwrap().to_string();
855 let path = make::path_unqualified(make::path_segment(make::name_ref(&name)));
856 make::expr_path(path)
887} 857}
888 858
889fn format_type(ty: &hir::Type, ctx: &AssistContext, module: hir::Module) -> String { 859fn format_type(ty: &hir::Type, ctx: &AssistContext, module: hir::Module) -> String {
890 ty.display_source_code(ctx.db(), module.into()).ok().unwrap_or_else(|| "()".to_string()) 860 ty.display_source_code(ctx.db(), module.into()).ok().unwrap_or_else(|| "()".to_string())
891} 861}
892 862
863fn rewrite_body_segment(
864 ctx: &AssistContext,
865 params: &[Param],
866 syntax: &SyntaxNode,
867) -> SyntaxNode {
868 fix_param_usages(ctx, params, syntax)
869}
870
893/// change all usages to account for added `&`/`&mut` for some params 871/// change all usages to account for added `&`/`&mut` for some params
894fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) -> SyntaxNode { 872fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) -> SyntaxNode {
895 let mut rewriter = SyntaxRewriter::default(); 873 let mut rewriter = SyntaxRewriter::default();
@@ -919,7 +897,7 @@ fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode)
919 rewriter.replace_ast(&node.clone().into(), &node.expr().unwrap()); 897 rewriter.replace_ast(&node.clone().into(), &node.expr().unwrap());
920 } 898 }
921 Some(_) | None => { 899 Some(_) | None => {
922 rewriter.replace_ast(&path, &ast::make::expr_prefix(T![*], path.clone())); 900 rewriter.replace_ast(&path, &make::expr_prefix(T![*], path.clone()));
923 } 901 }
924 }; 902 };
925 } 903 }