diff options
author | Vladyslav Katasonov <[email protected]> | 2021-02-04 23:14:32 +0000 |
---|---|---|
committer | Vladyslav Katasonov <[email protected]> | 2021-02-04 23:14:32 +0000 |
commit | 4dc2a42500b52001299ef861d5105d8a8249ecd8 (patch) | |
tree | 8a15d1a667c4c714c17982f1d080442d18298a32 /crates/assists | |
parent | 0ff74467c0d107a0b9472e928f9f0845f68be088 (diff) |
document extract_function assist implementation
Diffstat (limited to 'crates/assists')
-rw-r--r-- | crates/assists/src/handlers/extract_function.rs | 148 |
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)] |
205 | enum FunctionBody { | 209 | enum 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 | ||
337 | fn element_to_node(node: SyntaxElement) -> SyntaxNode { | 342 | fn 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 | /// | ||
344 | fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<FunctionBody> { | 366 | fn 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` |
381 | fn vars_used_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> { | 402 | fn 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` | ||
394 | fn self_param_from_usages( | 419 | fn 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 | ||
421 | fn extracted_function_params( | 449 | fn 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 | ||
458 | fn has_exclusive_usages(ctx: &AssistContext, usages: &LocalUsages, body: &FunctionBody) -> bool { | 487 | fn 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 | ||
465 | fn reference_is_exclusive( | 495 | fn 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 | ||
486 | struct LocalUsages(ide_db::search::UsageSearchResult); | 522 | struct LocalUsages(ide_db::search::UsageSearchResult); |
487 | 523 | ||
488 | impl LocalUsages { | 524 | impl LocalUsages { |
@@ -510,7 +546,15 @@ impl HasTokenAtOffset for SyntaxNode { | |||
510 | } | 546 | } |
511 | } | 547 | } |
512 | 548 | ||
513 | fn 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)` | ||
554 | fn 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 | ||
532 | fn 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 |
577 | fn 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` |
544 | fn vars_defined_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> { | 589 | fn 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 | ||
552 | fn vars_defined_in_body_and_outlive(ctx: &AssistContext, body: &FunctionBody) -> Vec<Local> { | 600 | fn 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 | ||
558 | fn is_defined_before( | 607 | fn 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 | ||
574 | fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool { | 624 | fn 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)] |
594 | enum Anchor { | 645 | enum 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 | ||
599 | fn scope_for_fn_insertion(body: &FunctionBody, anchor: Anchor) -> Option<SyntaxNode> { | 655 | fn 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 | ||
804 | fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) -> SyntaxNode { | 861 | fn 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" | ||
1489 | struct Counter(i32); | ||
1490 | fn foo() { | ||
1491 | $0let Counter(n) = Counter(0);$0 | ||
1492 | let m = n; | ||
1493 | }", | ||
1494 | r" | ||
1495 | struct Counter(i32); | ||
1496 | fn foo() { | ||
1497 | let n = fun_name(); | ||
1498 | let m = n; | ||
1499 | } | ||
1500 | |||
1501 | fn $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" | ||
1513 | struct Counter { n: i32, m: i32 }; | ||
1514 | fn foo() { | ||
1515 | $0let Counter { n, m: k } = Counter { n: 1, m: 2 };$0 | ||
1516 | let h = n + k; | ||
1517 | }", | ||
1518 | r" | ||
1519 | struct Counter { n: i32, m: i32 }; | ||
1520 | fn foo() { | ||
1521 | let (n, k) = fun_name(); | ||
1522 | let h = n + k; | ||
1523 | } | ||
1524 | |||
1525 | fn $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( |