Conversation
There was a problem hiding this comment.
Pull request overview
This PR implements a "use function" refactoring feature that identifies expressions matching a function's return pattern and offers to replace them with function calls. The pattern matcher targets single-return, non-decorated, non-async top-level functions with only positional parameters, avoiding boolean operators, ternary expressions, and chained comparisons to prevent evaluation-order changes.
Key changes:
- Adds pattern matching logic to find and replace expressions that structurally match a function's return expression
- Intelligently handles imports across modules, reusing existing imports or adding new ones while avoiding name conflicts
- Exposes the refactoring as a new
refactor.rewritecode action kind in the LSP
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
pyrefly/lib/state/lsp/quick_fixes/use_function.rs |
New file implementing the core pattern matching and refactoring logic |
pyrefly/lib/test/lsp/code_actions.rs |
Adds comprehensive tests covering cross-module refactoring, import reuse, shadowing, repeated parameters, and edge cases |
pyrefly/lib/state/lsp/quick_fixes/mod.rs |
Registers the new use_function module |
pyrefly/lib/state/lsp.rs |
Adds transaction method to access use_function code actions |
pyrefly/lib/lsp/non_wasm/server.rs |
Integrates use_function into code actions handler and advertises REFACTOR_REWRITE capability |
pyrefly/lib/test/lsp/lsp_interaction/basic.rs |
Updates LSP capability expectations to include refactor.rewrite |
crates/pyrefly_config/src/config.rs |
Removes unnecessary lifetime annotation from function signature |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This comment has been minimized.
This comment has been minimized.
d6d8e85 to
4e819f1
Compare
This comment has been minimized.
This comment has been minimized.
4e819f1 to
bd55bd0
Compare
This comment has been minimized.
This comment has been minimized.
jvansch1
left a comment
There was a problem hiding this comment.
Good start! I have a couple suggestions for some things that we can change.
Also, for these code action PRs it would be helpful if you could either include some instructions for how we can test these in our own IDE(we mostly use VScode) or a video showing how they work.
| @@ -0,0 +1,895 @@ | |||
| /* | |||
There was a problem hiding this comment.
I think most of the functions in this file need a docstring. A lot of these functions are quite large and it is not immediately clear what their purpose is.
| matches | ||
| } | ||
|
|
||
| fn match_expr( |
There was a problem hiding this comment.
Can we break this function up into smaller, easier to read functions. This function is over 300 lines and I think it will be easier to maintain if we can break this into smaller functions which are easier to reason about.
Co-authored-by: Copilot <[email protected]>
bd55bd0 to
b923145
Compare
b923145 to
54fe777
Compare
This comment has been minimized.
This comment has been minimized.
Screencast_20260204_042421_c.webm |
|
@asukaminato0721 Looks like there is a merge conflict here. Once this gets updated I will do another review of this. |
This comment has been minimized.
This comment has been minimized.
| selection: TextRange, | ||
| ) -> Option<Vec<LocalRefactorCodeAction>> { | ||
| let module_info = transaction.get_module_info(handle)?; | ||
| if transaction.is_third_party_module(&module_info, handle) |
There was a problem hiding this comment.
We don't have to do it in this PR, but it seems like this a check we are doing often. We check transaction.is_third_party_module(&target_info, &target_handle) && !transaction.is_source_file(&target_info, &target_handle) twice in this function and we also do it in lib/state/lsp.rs. At this point we can probably extract this out to a helper function.
Again not something that I think needs to be done in the PR but something that would improve code quality a bit.
| name: String, | ||
| params: Vec<String>, | ||
| param_set: HashSet<String>, | ||
| param_counts: HashMap<String, usize>, |
There was a problem hiding this comment.
Do we need all these representations of params? I can see how having the counts is useful, but wonder if we really need to have both params as a vector and as a set.
| let Stmt::Import(StmtImport { names, .. }) = stmt else { | ||
| continue; | ||
| }; | ||
| for alias in names { |
There was a problem hiding this comment.
This is very similar to the block on line 405. Maybe a small helper function should be introduced to reduce repeated code? I know we are comparing a module in one case and a function in another case, so if it becomes too complicated to make it one function then I think it is fine to leave as is.
|
|
||
| /// Matches dict expressions by comparing key/value pairs. | ||
| fn match_dict_expr( | ||
| lhs: &ExprDict, |
There was a problem hiding this comment.
It looks like we have a lot of functions that do very similar things here with the main difference being the type of lhs and rhs. Maybe we could make this a generic function that has a match statement that could properly handle the logic for different types of lhs and rhs?
There was a problem hiding this comment.
A single generic would likely recreate a big match internally
There was a problem hiding this comment.
I think a match might be preferable In this case. I guess we either need to have
- A lot of smaller functions like this
- One huge function containing all the match arms
I don't think either great but if we had to choose one I think what we have here is better since at least we have named functions which describe what is happening. Overall there is a done of duplicate logic so it would be great to see if we could get rid of that if possible.
e5fa47b to
d43669d
Compare
This comment has been minimized.
This comment has been minimized.
jvansch1
left a comment
There was a problem hiding this comment.
Can we also add some tests for the following cases:
- A function with zero arguments
- A function that has an argument with a default value.
|
|
||
| /// Matches dict expressions by comparing key/value pairs. | ||
| fn match_dict_expr( | ||
| lhs: &ExprDict, |
There was a problem hiding this comment.
I think a match might be preferable In this case. I guess we either need to have
- A lot of smaller functions like this
- One huge function containing all the match arms
I don't think either great but if we had to choose one I think what we have here is better since at least we have named functions which describe what is happening. Overall there is a done of duplicate logic so it would be great to see if we could get rid of that if possible.
| name: String, | ||
| params: Vec<String>, | ||
| param_set: HashSet<String>, | ||
| param_counts: HashMap<String, usize>, |
This comment has been minimized.
This comment has been minimized.
|
@asukaminato0721 I did see an issue with import when using this functionality. It seems like we are using the full import name rather than just importing the function we need. It would be nice if we could fix this before we merge this in. import_issue_use_function.mov |
0895e58 to
35715f2
Compare
This comment has been minimized.
This comment has been minimized.
kinto0
left a comment
There was a problem hiding this comment.
Review automatically exported from Phabricator review in Meta.
|
@asukaminato0721 Once all the CI checks are passing I will continue to review this PR. |
the ci err is strange |
35715f2 to
3a05915
Compare
This comment has been minimized.
This comment has been minimized.
|
@asukaminato0721 I am noticing that this functionality is not working when I would expect it to. See the example video below. I am also wondering how this would work if we have multiple projects in a workspace. If we call Screen.Recording.2026-02-17.at.9.11.42.AM.mov |
8bd75d5 to
c4f9942
Compare
|
The “use function” code action now rewrites expression statements inside function bodies (like Added a regression test. |
This comment has been minimized.
This comment has been minimized.
|
@asukaminato0721 I ran into a few other issues when testing I created very simple function:
We also use it in places where it does not seem totally appropriate. In this case there was a hardcoded value of
Say for example I have two projects in the same VSCode workspace. One project is called In These issues are both pretty critical and would need to be fixed before moving forward with this change. |
I think skip trivial constant-return functions could solve this? |
|
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
Summary
Fixes part of #364
Pattern matcher and refactor edits targets single‑return, non‑decorated, non‑async top‑level functions with only positional params and return expressions that reference only those params (no and/or/ternary or chained comparisons to avoid evaluation‑order changes).
Hooked into LSP code actions and exposed as refactor.rewrite .
https://rope.readthedocs.io/en/latest/overview.html#use-function-refactoring
Test Plan
Added a multi‑module test and updated LSP capability expectations