aboutsummaryrefslogtreecommitdiff
path: root/crates/assists
diff options
context:
space:
mode:
authorVladyslav Katasonov <[email protected]>2021-02-04 23:14:32 +0000
committerVladyslav Katasonov <[email protected]>2021-02-04 23:14:32 +0000
commit4dc2a42500b52001299ef861d5105d8a8249ecd8 (patch)
tree8a15d1a667c4c714c17982f1d080442d18298a32 /crates/assists
parent0ff74467c0d107a0b9472e928f9f0845f68be088 (diff)
document extract_function assist implementation
Diffstat (limited to 'crates/assists')
-rw-r--r--crates/assists/src/handlers/extract_function.rs148
1 files changed, 126 insertions, 22 deletions
diff --git a/crates/assists/src/handlers/extract_function.rs b/crates/assists/src/handlers/extract_function.rs
index ac2a5a674..dfb3da7a5 100644
--- a/crates/assists/src/handlers/extract_function.rs
+++ b/crates/assists/src/handlers/extract_function.rs
@@ -64,7 +64,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
64 64
65 let body = extraction_target(&node, ctx.frange.range)?; 65 let body = extraction_target(&node, ctx.frange.range)?;
66 66
67 let vars_used_in_body = vars_used_in_body(&body, &ctx); 67 let vars_used_in_body = vars_used_in_body(ctx, &body);
68 let self_param = self_param_from_usages(ctx, &body, &vars_used_in_body); 68 let self_param = self_param_from_usages(ctx, &body, &vars_used_in_body);
69 69
70 let anchor = if self_param.is_some() { Anchor::Method } else { Anchor::Freestanding }; 70 let anchor = if self_param.is_some() { Anchor::Method } else { Anchor::Freestanding };
@@ -74,6 +74,9 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
74 let vars_defined_in_body_and_outlive = vars_defined_in_body_and_outlive(ctx, &body); 74 let vars_defined_in_body_and_outlive = vars_defined_in_body_and_outlive(ctx, &body);
75 let ret_ty = body_return_ty(ctx, &body)?; 75 let ret_ty = body_return_ty(ctx, &body)?;
76 76
77 // FIXME: we compute variables that outlive here just to check `never!` condition
78 // this requires traversing whole `body` (cheap) and finding all references (expensive)
79 // maybe we can move this check to `edit` closure somehow?
77 if stdx::never!(!vars_defined_in_body_and_outlive.is_empty() && !ret_ty.is_unit()) { 80 if stdx::never!(!vars_defined_in_body_and_outlive.is_empty() && !ret_ty.is_unit()) {
78 // We should not have variables that outlive body if we have expression block 81 // We should not have variables that outlive body if we have expression block
79 return None; 82 return None;
@@ -201,6 +204,7 @@ impl RetType {
201 } 204 }
202} 205}
203 206
207/// Semantically same as `ast::Expr`, but preserves identity when using only part of the Block
204#[derive(Debug)] 208#[derive(Debug)]
205enum FunctionBody { 209enum FunctionBody {
206 Expr(ast::Expr), 210 Expr(ast::Expr),
@@ -334,6 +338,7 @@ impl HasTokenAtOffset for FunctionBody {
334 } 338 }
335} 339}
336 340
341/// node or token's parent
337fn element_to_node(node: SyntaxElement) -> SyntaxNode { 342fn element_to_node(node: SyntaxElement) -> SyntaxNode {
338 match node { 343 match node {
339 syntax::NodeOrToken::Node(n) => n, 344 syntax::NodeOrToken::Node(n) => n,
@@ -341,7 +346,26 @@ fn element_to_node(node: SyntaxElement) -> SyntaxNode {
341 } 346 }
342} 347}
343 348
349/// Try to guess what user wants to extract
350///
351/// We have basically have two cases:
352/// * We want whole node, like `loop {}`, `2 + 2`, `{ let n = 1; }` exprs.
353/// Then we can use `ast::Expr`
354/// * We want a few statements for a block. E.g.
355/// ```rust,no_run
356/// fn foo() -> i32 {
357/// let m = 1;
358/// $0
359/// let n = 2;
360/// let k = 3;
361/// k + n
362/// $0
363/// }
364/// ```
365///
344fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<FunctionBody> { 366fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<FunctionBody> {
367 // we have selected exactly the expr node
368 // wrap it before anything else
345 if node.text_range() == selection_range { 369 if node.text_range() == selection_range {
346 let body = FunctionBody::from_whole_node(node.clone()); 370 let body = FunctionBody::from_whole_node(node.clone());
347 if body.is_some() { 371 if body.is_some() {
@@ -349,12 +373,18 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu
349 } 373 }
350 } 374 }
351 375
376 // we have selected a few statements in a block
377 // so covering_element returns the whole block
352 if node.kind() == BLOCK_EXPR { 378 if node.kind() == BLOCK_EXPR {
353 let body = FunctionBody::from_range(&node, selection_range); 379 let body = FunctionBody::from_range(&node, selection_range);
354 if body.is_some() { 380 if body.is_some() {
355 return body; 381 return body;
356 } 382 }
357 } 383 }
384
385 // we have selected single statement
386 // `from_whole_node` failed because (let) statement is not and expression
387 // so we try to expand covering_element to parent and repeat the previous
358 if let Some(parent) = node.parent() { 388 if let Some(parent) = node.parent() {
359 if parent.kind() == BLOCK_EXPR { 389 if parent.kind() == BLOCK_EXPR {
360 let body = FunctionBody::from_range(&parent, selection_range); 390 let body = FunctionBody::from_range(&parent, selection_range);
@@ -364,21 +394,13 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu
364 } 394 }
365 } 395 }
366 396
367 let body = FunctionBody::from_whole_node(node.clone()); 397 // select the closest containing expr (both ifs are used)
368 if body.is_some() { 398 std::iter::once(node.clone()).chain(node.ancestors()).find_map(FunctionBody::from_whole_node)
369 return body;
370 }
371
372 let body = node.ancestors().find_map(FunctionBody::from_whole_node);
373 if body.is_some() {
374 return body;
375 }
376
377 None
378} 399}
379 400
380/// Returns a vector of local variables that are referenced in `body` 401/// list local variables that are referenced in `body`
381fn vars_used_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> { 402fn vars_used_in_body(ctx: &AssistContext, body: &FunctionBody) -> Vec<Local> {
403 // FIXME: currently usages inside macros are not found
382 body.descendants() 404 body.descendants()
383 .filter_map(ast::NameRef::cast) 405 .filter_map(ast::NameRef::cast)
384 .filter_map(|name_ref| NameRefClass::classify(&ctx.sema, &name_ref)) 406 .filter_map(|name_ref| NameRefClass::classify(&ctx.sema, &name_ref))
@@ -391,6 +413,9 @@ fn vars_used_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> {
391 .collect() 413 .collect()
392} 414}
393 415
416/// find `self` param, that was not defined inside `body`
417///
418/// It should skip `self` params from impls inside `body`
394fn self_param_from_usages( 419fn self_param_from_usages(
395 ctx: &AssistContext, 420 ctx: &AssistContext,
396 body: &FunctionBody, 421 body: &FunctionBody,
@@ -412,12 +437,15 @@ fn self_param_from_usages(
412 let self_param = iter.next(); 437 let self_param = iter.next();
413 stdx::always!( 438 stdx::always!(
414 iter.next().is_none(), 439 iter.next().is_none(),
415 "body references two different self params both defined outside" 440 "body references two different self params, both defined outside"
416 ); 441 );
417 442
418 self_param 443 self_param
419} 444}
420 445
446/// find variables that should be extracted as params
447///
448/// Computes additional info that affects param type and mutability
421fn extracted_function_params( 449fn extracted_function_params(
422 ctx: &AssistContext, 450 ctx: &AssistContext,
423 body: &FunctionBody, 451 body: &FunctionBody,
@@ -455,6 +483,7 @@ fn has_usages_after_body(usages: &LocalUsages, body: &FunctionBody) -> bool {
455 usages.iter().any(|reference| body.preceedes_range(reference.range)) 483 usages.iter().any(|reference| body.preceedes_range(reference.range))
456} 484}
457 485
486/// checks if relevant var is used with `&mut` access inside body
458fn has_exclusive_usages(ctx: &AssistContext, usages: &LocalUsages, body: &FunctionBody) -> bool { 487fn has_exclusive_usages(ctx: &AssistContext, usages: &LocalUsages, body: &FunctionBody) -> bool {
459 usages 488 usages
460 .iter() 489 .iter()
@@ -462,27 +491,34 @@ fn has_exclusive_usages(ctx: &AssistContext, usages: &LocalUsages, body: &Functi
462 .any(|reference| reference_is_exclusive(reference, body, ctx)) 491 .any(|reference| reference_is_exclusive(reference, body, ctx))
463} 492}
464 493
494/// checks if this reference requires `&mut` access inside body
465fn reference_is_exclusive( 495fn reference_is_exclusive(
466 reference: &FileReference, 496 reference: &FileReference,
467 body: &FunctionBody, 497 body: &FunctionBody,
468 ctx: &AssistContext, 498 ctx: &AssistContext,
469) -> bool { 499) -> bool {
500 // we directly modify variable with set: `n = 0`, `n += 1`
470 if reference.access == Some(ReferenceAccess::Write) { 501 if reference.access == Some(ReferenceAccess::Write) {
471 return true; 502 return true;
472 } 503 }
473 504
474 let path = path_at_offset(body, reference); 505 // we take `&mut` reference to variable: `&mut v`
506 let path = path_element_of_reference(body, reference);
475 if is_mut_ref_expr(path.as_ref()).unwrap_or(false) { 507 if is_mut_ref_expr(path.as_ref()).unwrap_or(false) {
476 return true; 508 return true;
477 } 509 }
478 510
479 if is_mut_method_call(ctx, path.as_ref()).unwrap_or(false) { 511 // we call method with `&mut self` receiver
512 if is_mut_method_call_receiver(ctx, path.as_ref()).unwrap_or(false) {
480 return true; 513 return true;
481 } 514 }
482 515
483 false 516 false
484} 517}
485 518
519/// Container of local varaible usages
520///
521/// Semanticall same as `UsageSearchResult`, but provides more convenient interface
486struct LocalUsages(ide_db::search::UsageSearchResult); 522struct LocalUsages(ide_db::search::UsageSearchResult);
487 523
488impl LocalUsages { 524impl LocalUsages {
@@ -510,7 +546,15 @@ impl HasTokenAtOffset for SyntaxNode {
510 } 546 }
511} 547}
512 548
513fn path_at_offset(node: &dyn HasTokenAtOffset, reference: &FileReference) -> Option<ast::Expr> { 549/// find relevant `ast::PathExpr` for reference
550///
551/// # Preconditions
552///
553/// `node` must cover `reference`, that is `node.text_range().contains_range(reference.range)`
554fn path_element_of_reference(
555 node: &dyn HasTokenAtOffset,
556 reference: &FileReference,
557) -> Option<ast::Expr> {
514 let token = node.token_at_offset(reference.range.start()).right_biased().or_else(|| { 558 let token = node.token_at_offset(reference.range.start()).right_biased().or_else(|| {
515 stdx::never!(false, "cannot find token at variable usage: {:?}", reference); 559 stdx::never!(false, "cannot find token at variable usage: {:?}", reference);
516 None 560 None
@@ -529,7 +573,8 @@ fn is_mut_ref_expr(path: Option<&ast::Expr>) -> Option<bool> {
529 Some(ref_expr.mut_token().is_some()) 573 Some(ref_expr.mut_token().is_some())
530} 574}
531 575
532fn is_mut_method_call(ctx: &AssistContext, path: Option<&ast::Expr>) -> Option<bool> { 576/// checks if `path` is the receiver in method call that requires `&mut self` access
577fn is_mut_method_call_receiver(ctx: &AssistContext, path: Option<&ast::Expr>) -> Option<bool> {
533 let path = path?; 578 let path = path?;
534 let method_call = path.syntax().parent().and_then(ast::MethodCallExpr::cast)?; 579 let method_call = path.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
535 580
@@ -540,8 +585,10 @@ fn is_mut_method_call(ctx: &AssistContext, path: Option<&ast::Expr>) -> Option<b
540 Some(matches!(access, hir::Access::Exclusive)) 585 Some(matches!(access, hir::Access::Exclusive))
541} 586}
542 587
543/// Returns a vector of local variables that are defined in `body` 588/// list local variables defined inside `body`
544fn vars_defined_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> { 589fn vars_defined_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> {
590 // FIXME: this doesn't work well with macros
591 // see https://github.com/rust-analyzer/rust-analyzer/pull/7535#discussion_r570048550
545 body.descendants() 592 body.descendants()
546 .filter_map(ast::IdentPat::cast) 593 .filter_map(ast::IdentPat::cast)
547 .filter_map(|let_stmt| ctx.sema.to_def(&let_stmt)) 594 .filter_map(|let_stmt| ctx.sema.to_def(&let_stmt))
@@ -549,12 +596,14 @@ fn vars_defined_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local>
549 .collect() 596 .collect()
550} 597}
551 598
599/// list local variables defined inside `body` that should be returned from extracted function
552fn vars_defined_in_body_and_outlive(ctx: &AssistContext, body: &FunctionBody) -> Vec<Local> { 600fn vars_defined_in_body_and_outlive(ctx: &AssistContext, body: &FunctionBody) -> Vec<Local> {
553 let mut vars_defined_in_body = vars_defined_in_body(&body, ctx); 601 let mut vars_defined_in_body = vars_defined_in_body(&body, ctx);
554 vars_defined_in_body.retain(|var| var_outlives_body(ctx, body, var)); 602 vars_defined_in_body.retain(|var| var_outlives_body(ctx, body, var));
555 vars_defined_in_body 603 vars_defined_in_body
556} 604}
557 605
606/// checks if the relevant local was defined before(outside of) body
558fn is_defined_before( 607fn is_defined_before(
559 ctx: &AssistContext, 608 ctx: &AssistContext,
560 body: &FunctionBody, 609 body: &FunctionBody,
@@ -571,6 +620,7 @@ fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode {
571 } 620 }
572} 621}
573 622
623/// checks if local variable is used after(outside of) body
574fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool { 624fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool {
575 let usages = Definition::Local(*var) 625 let usages = Definition::Local(*var)
576 .usages(&ctx.sema) 626 .usages(&ctx.sema)
@@ -590,12 +640,18 @@ fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> {
590 None => Some(RetType::Stmt), 640 None => Some(RetType::Stmt),
591 } 641 }
592} 642}
643/// Where to put extracted function definition
593#[derive(Debug)] 644#[derive(Debug)]
594enum Anchor { 645enum Anchor {
646 /// Extract free function and put right after current top-level function
595 Freestanding, 647 Freestanding,
648 /// Extract method and put right after current function in the impl-block
596 Method, 649 Method,
597} 650}
598 651
652/// find where to put extracted function definition
653///
654/// Function should be put right after returned node
599fn scope_for_fn_insertion(body: &FunctionBody, anchor: Anchor) -> Option<SyntaxNode> { 655fn scope_for_fn_insertion(body: &FunctionBody, anchor: Anchor) -> Option<SyntaxNode> {
600 match body { 656 match body {
601 FunctionBody::Expr(e) => scope_for_fn_insertion_node(e.syntax(), anchor), 657 FunctionBody::Expr(e) => scope_for_fn_insertion_node(e.syntax(), anchor),
@@ -801,6 +857,7 @@ fn format_type(ty: &hir::Type, ctx: &AssistContext, module: hir::Module) -> Stri
801 ty.display_source_code(ctx.db(), module.into()).ok().unwrap_or_else(|| "()".to_string()) 857 ty.display_source_code(ctx.db(), module.into()).ok().unwrap_or_else(|| "()".to_string())
802} 858}
803 859
860/// change all usages to account for added `&`/`&mut` for some params
804fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) -> SyntaxNode { 861fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) -> SyntaxNode {
805 let mut rewriter = SyntaxRewriter::default(); 862 let mut rewriter = SyntaxRewriter::default();
806 for param in params { 863 for param in params {
@@ -812,7 +869,7 @@ fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode)
812 let usages = usages 869 let usages = usages
813 .iter() 870 .iter()
814 .filter(|reference| syntax.text_range().contains_range(reference.range)) 871 .filter(|reference| syntax.text_range().contains_range(reference.range))
815 .filter_map(|reference| path_at_offset(syntax, reference)); 872 .filter_map(|reference| path_element_of_reference(syntax, reference));
816 for path in usages { 873 for path in usages {
817 match path.syntax().ancestors().skip(1).find_map(ast::Expr::cast) { 874 match path.syntax().ancestors().skip(1).find_map(ast::Expr::cast) {
818 Some(ast::Expr::MethodCallExpr(_)) => { 875 Some(ast::Expr::MethodCallExpr(_)) => {
@@ -1425,6 +1482,54 @@ fn $0fun_name(n: i32) -> (i32, i32) {
1425 } 1482 }
1426 1483
1427 #[test] 1484 #[test]
1485 fn nontrivial_patterns_define_variables() {
1486 check_assist(
1487 extract_function,
1488 r"
1489struct Counter(i32);
1490fn foo() {
1491 $0let Counter(n) = Counter(0);$0
1492 let m = n;
1493}",
1494 r"
1495struct Counter(i32);
1496fn foo() {
1497 let n = fun_name();
1498 let m = n;
1499}
1500
1501fn $0fun_name() -> i32 {
1502 let Counter(n) = Counter(0);
1503 n
1504}",
1505 );
1506 }
1507
1508 #[test]
1509 fn struct_with_two_fields_pattern_define_variables() {
1510 check_assist(
1511 extract_function,
1512 r"
1513struct Counter { n: i32, m: i32 };
1514fn foo() {
1515 $0let Counter { n, m: k } = Counter { n: 1, m: 2 };$0
1516 let h = n + k;
1517}",
1518 r"
1519struct Counter { n: i32, m: i32 };
1520fn foo() {
1521 let (n, k) = fun_name();
1522 let h = n + k;
1523}
1524
1525fn $0fun_name() -> (i32, i32) {
1526 let Counter { n, m: k } = Counter { n: 1, m: 2 };
1527 (n, k)
1528}",
1529 );
1530 }
1531
1532 #[test]
1428 fn mut_var_from_outer_scope() { 1533 fn mut_var_from_outer_scope() {
1429 check_assist( 1534 check_assist(
1430 extract_function, 1535 extract_function,
@@ -1754,7 +1859,6 @@ fn $0fun_name(c: Counter) {
1754 ); 1859 );
1755 } 1860 }
1756 1861
1757
1758 #[test] 1862 #[test]
1759 fn non_copy_used_after() { 1863 fn non_copy_used_after() {
1760 check_assist( 1864 check_assist(