Thanks to visit codestin.com
Credit goes to GitHub.com

Skip to content

impl use function#2035

Open
asukaminato0721 wants to merge 12 commits intofacebook:mainfrom
asukaminato0721:364-1
Open

impl use function#2035
asukaminato0721 wants to merge 12 commits intofacebook:mainfrom
asukaminato0721:364-1

Conversation

@asukaminato0721
Copy link
Contributor

@asukaminato0721 asukaminato0721 commented Jan 8, 2026

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

@meta-cla meta-cla bot added the cla signed label Jan 8, 2026
@asukaminato0721 asukaminato0721 marked this pull request as ready for review January 8, 2026 09:11
Copilot AI review requested due to automatic review settings January 8, 2026 09:11
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.rewrite code 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.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@meta-codesync
Copy link

meta-codesync bot commented Feb 3, 2026

@jvansch1 has imported this pull request. If you are a Meta employee, you can view this in D92157038.

Copy link
Contributor

@jvansch1 jvansch1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 @@
/*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@github-actions

This comment has been minimized.

@asukaminato0721
Copy link
Contributor Author

Screencast_20260204_042421_c.webm

@jvansch1
Copy link
Contributor

jvansch1 commented Feb 9, 2026

@asukaminato0721 Looks like there is a merge conflict here. Once this gets updated I will do another review of this.

@github-actions

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asukaminato0721 Still wondering about this.

let Stmt::Import(StmtImport { names, .. }) = stmt else {
continue;
};
for alias in names {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A single generic would likely recreate a big match internally

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a match might be preferable In this case. I guess we either need to have

  1. A lot of smaller functions like this
  2. 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.

@github-actions

This comment has been minimized.

Copy link
Contributor

@jvansch1 jvansch1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also add some tests for the following cases:

  1. A function with zero arguments
  2. A function that has an argument with a default value.


/// Matches dict expressions by comparing key/value pairs.
fn match_dict_expr(
lhs: &ExprDict,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a match might be preferable In this case. I guess we either need to have

  1. A lot of smaller functions like this
  2. 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>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asukaminato0721 Still wondering about this.

@github-actions

This comment has been minimized.

@jvansch1
Copy link
Contributor

@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

@github-actions

This comment has been minimized.

Copy link
Contributor

@kinto0 kinto0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review automatically exported from Phabricator review in Meta.

@jvansch1
Copy link
Contributor

@asukaminato0721 Once all the CI checks are passing I will continue to review this PR.

@asukaminato0721
Copy link
Contributor Author

thread 'tests::test_get_source_db_always_configures_paths' (6788) panicked at crates\pyrefly_build\src\lib.rs:209:9:
assertion `left == right` failed
  left: ["D:\\root\\path\\to\\project", "D:\\absolute\\path\\to\\project"]
 right: ["/root\\path/to/project", "/absolute/path/to/project"]
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

the ci err is strange

@github-actions

This comment has been minimized.

@jvansch1
Copy link
Contributor

@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 use_function in one project, will this also try to replace similar instances for another project in the workspace?

Screen.Recording.2026-02-17.at.9.11.42.AM.mov

@asukaminato0721
Copy link
Contributor Author

The “use function” code action now rewrites expression statements inside function bodies (like x ** 2 in other) by traversing expressions via statements instead of only visiting top-level expressions.

Added a regression test.

@github-actions

This comment has been minimized.

@jvansch1
Copy link
Contributor

@asukaminato0721 I ran into a few other issues when testing

I created very simple function:

def one():
    return 1
  1. We try to use this in several places where it does not seem appropriate including in typeshed stub files
Screenshot 2026-02-18 at 9 37 59 AM

We also use it in places where it does not seem totally appropriate. In this case there was a hardcoded value of 1 and we replaced it with a function. I have questions about how useful that actually is.
Screenshot 2026-02-18 at 9 41 37 AM

  1. This check happens across all projects in a workspace which I do not think is correct.

Say for example I have two projects in the same VSCode workspace. One project is called main and one is called other

In main I defined this one() function. When I call use_function on this it also replaces code inside the other project. This is incorrect since these are entirely different projects that just happen to be in the same workspace instance.

These issues are both pretty critical and would need to be fixed before moving forward with this change.

@asukaminato0721
Copy link
Contributor Author

In this case there was a hardcoded value of 1 and we replaced it with a function. I have questions about how useful that actually is.

I think skip trivial constant-return functions could solve this?

@github-actions
Copy link

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments