-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extract function assist #7535
Extract function assist #7535
Conversation
there are a few currently limitations: * no modifications of function body * does not handle mutability and references * no method support * may produce incorrect results
currently mut refernce will *not* be downgraded to shared if it is sufficient(see relevant test for example)
before child getter was used
when variable is defined inside extracted body export this variable to original scope via return value(s)
It currently allows only directly setting variable. No `&mut` references or methods.
Recognise &mut as variable modification. This allows extracting functions with `&mut var` with `var` being in outer scope
Regarding whether a type implements copy you should be able to make use of https://github.com/rust-analyzer/rust-analyzer/blob/74a223faa33156be1b8b1a6880f9b63463027946/crates/hir/src/code_model.rs#L1662-L1662 |
I am not sure. I'd use whichever is simple. Long term, we need a spearate refactor to change function's parameter from |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, this is one excellent PR @cpud36! I am absolutely blown away by the attention to details here, 👍. One day, once I (or someone else) writes how_to_add_assist.md
guide, this would the the showcase. Kudos!
To my mind, this is already in a state well above average for assists, so feel free to merge when you think this is ready.
One day, once the major infrastructure of ra is polished, we should go through all the existing assists and make sure they also meet a high standard of quality, set by this PR. Thanks!
@@ -487,7 +487,7 @@ pub mod tokens { | |||
use crate::{ast, AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken}; | |||
|
|||
pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = | |||
Lazy::new(|| SourceFile::parse("const C: <()>::Item = (1 != 1, 2 == 2, !true)\n;\n\n")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
SyntaxKind::{self, BLOCK_EXPR, BREAK_EXPR, COMMENT, PATH_EXPR, RETURN_EXPR}, | ||
SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, T, | ||
}; | ||
use test_utils::mark; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️
}) | ||
.collect(); | ||
|
||
let params: Vec<_> = param_pats |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can move this (and maybe some other things) into the lambda inside ctx.add
. That would be more efficient, as we won't call this code just to compute if the lightbulb should be visible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. Most of the things actually.
fn vars_defined_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> { | ||
body.descendants() | ||
.filter_map(ast::IdentPat::cast) | ||
.filter_map(|let_stmt| ctx.sema.to_def(&let_stmt)) | ||
.unique() | ||
.collect() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JFIY, it is an open question how to write this kind of logic properly. This very much depends on semantics, and to handle semantics precisely, you need to look at lowered expressions (hir::Body
, ExprId
), as they take macro expansion into account.
But its unclear how to loslessly go between lowering and the source syntax, and its the syntax you are ultimatelly interested in.
To be clear, this is a perfectly fine implementation for this PR, I just wist it were obvious how to make this pedantically correct.
.filter(|element| element.text_range().contains_inclusive(offset)); | ||
let element1 = iter.next().expect("offset does not fall into body"); | ||
let element2 = iter.next(); | ||
stdx::always!(iter.next().is_none(), "> 2 tokens at offset"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
let path = match token.ancestors().find_map(ast::Expr::cast) { | ||
Some(n) => n, | ||
None => { | ||
stdx::never!(false, "cannot find path parent of variable usage: {:?}", token); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like we should add support for stdx::never!("unreachable")
as an alias for false
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ParamKind::Value => "", | ||
ParamKind::MutValue => "", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 merge match arms
bors d+ |
✌️ cpud36 can now approve this pull request. To approve and merge a pull request, simply reply with |
Use shared ref if param is not `T: Copy` and is used after body
e4ebd71
to
7eaa3e5
Compare
bors r+ |
@cpud36 you might like this talk from jet brains conf: https://youtu.be/GRmOXuoe648?t=7872 |
@cpud36 In Neovim, usually I just type PS: I have already configured
Much appreciated. |
Similar to previous commenter, but - how do I access this from VSCode? I had no idea Rust Analyzer has this already implemented, and I don't see it in either "Refactor..." menu or the Command Palette. |
You select the code you want to extract, then click the lightbulb that appears or hit |
Yeah I found the problem - looks like it doesn't work inside macro bodies (the bulb doesn't appear), and most of my code in this project is wrapped into contextual macros like dbg_elapsed!("Context 1", {
something();
let value = dbg_elapsed!("Context 2", { ... });
}); I suppose I could convert those into closures, but it was a bit surprising considering that Rust Analyzer supports macros pretty well in other places. |
(to clarify, the 2nd argument in those macros is just |
This PR adds
extract function/method
assist. closes #5409.Supported features
Assist should support extracting from expressions(
1
,2 + 2
,loop { }
) and from a series of statements, e.g.:Assist also supports extracting parameters, like:
Extracting methods also generally works.
Assist allows referencing outer variables, both mutably and immutably, and handles handles access to variables local to extracted function:
So we handle both input and output paramters
Showcase
Working with non-
Copy
typesConsider the following example:
v
must be a parameter to extracted function.The question is, what type should it have.
It could be
v: Vec<i32>
, orv: &Vec<i32>
.The former is incorrect for
Vec<i32>
, but the later is silly fori32
.To resolve this we need to know if the type implements
Copy
trait.I didn't find any api available from assists to query this.
hir_ty::method_resolution::implements
seems relevant, but is isn't publicly re-exported fromhir
.Star(
*
) token and pointer dereferenceIf I understand correctly, in order to create expression like
*p
, one should useast::make::expr_prefix(T![*], ...)
, whichin turn calls
token(T![*])
.token
does not have star intokens::SOURCE_FILE
, so this panics.I had to add
*
toSOURCE_FILE
to make it work.Correct me if this is not intended way to do this.
Lowering access
value -> mut ref -> shared ref
Consider the following example:
v
is not used after extracted function body, so bothv: &Vec<i32>
andv: Vec<i32>
would work.Currently the later would be chosen.
We can however check the body of extracted function and conclude that
v: &Vec<i32>
is sufficient.Using
v: &Vec<i32>
(that is a minimal required access level) might be a better default.I am unsure.
Cleanup
The assist seems to be reasonably handling most of common cases.
If there are no concerns with code it produces(i.e. with test cases), I will start cleaning up
[edit]
added showcase