Skip to content

Conversation

@asukaminato0721
Copy link
Contributor

Summary

Fixes part of #364

Implemented an “Invert boolean” refactor code action that flips a local variable’s stored boolean value and updates all load sites (adds/removes not) across the file, with conservative guards against multi-target assignments and deletes. Wired it into the LSP code action pipeline as a refactor.rewrite.

Test Plan

added focused LSP code action tests.

@meta-cla meta-cla bot added the cla signed label Jan 11, 2026
@asukaminato0721 asukaminato0721 marked this pull request as ready for review January 11, 2026 11:16
Copilot AI review requested due to automatic review settings January 11, 2026 11:16
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 pull request implements an "Invert boolean" refactoring feature that flips a local variable's stored boolean value and updates all usages across the file. The refactor adds/removes not operators at load sites and inverts boolean literals in assignments, with conservative guards against multi-target assignments and deletions.

Changes:

  • Implemented core invert boolean refactoring logic with support for boolean literals, unary not expressions, and complex expressions
  • Added LSP integration to expose the refactoring as a REFACTOR_REWRITE code action
  • Added basic test coverage for the new refactoring feature

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pyrefly/lib/state/lsp/quick_fixes/invert_boolean.rs New module implementing the invert boolean refactoring logic including AST traversal, edit collection, and safety checks
pyrefly/lib/state/lsp/quick_fixes/mod.rs Registers the new invert_boolean module
pyrefly/lib/state/lsp.rs Adds public API method for accessing invert boolean code actions
pyrefly/lib/lsp/non_wasm/server.rs Integrates invert boolean into LSP code action handler and registers REFACTOR_REWRITE capability
pyrefly/lib/test/lsp/code_actions.rs Adds test helper functions and three test cases for invert boolean functionality

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

range: unary.range(),
replacement: name.id.to_string(),
});
return;
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

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

The early return on line 265 prevents the visitor from walking nested expressions when a "not variable" pattern is found. This means that if the variable appears within nested expressions under a unary not operation, those occurrences won't be processed. The walk_expr call should be executed before the return statement, or the return should be removed to allow proper tree traversal.

Copilot uses AI. Check for mistakes.
Comment on lines +863 to +1088
#[test]
fn invert_boolean_basic_refactor() {
let code = r#"
def foo():
abc = True
return abc
"#;
let selection = find_nth_range(code, "abc", 2);
let updated =
apply_first_invert_boolean_action(code, selection).expect("expected invert-boolean action");
let expected = r#"
def foo():
abc = False
return (not abc)
"#;
assert_eq!(expected.trim(), updated.trim());
}

#[test]
fn invert_boolean_removes_not() {
let code = r#"
def foo():
abc = False
if not abc:
return 1
return 0
"#;
let selection = find_nth_range(code, "abc", 2);
let updated =
apply_first_invert_boolean_action(code, selection).expect("expected invert-boolean action");
let expected = r#"
def foo():
abc = True
if abc:
return 1
return 0
"#;
assert_eq!(expected.trim(), updated.trim());
}

#[test]
fn invert_boolean_rejects_multi_target_assignment() {
let code = r#"
def foo():
abc = other = True
return abc
"#;
let selection = find_nth_range(code, "abc", 2);
assert_no_invert_boolean_action(code, selection);
}
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

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

Consider adding test coverage for additional scenarios: 1) annotated assignments (e.g., abc: bool = True), 2) variables that are deleted (the code rejects these but there's no test), 3) multiple assignments to the same variable, 4) nested expressions containing the target variable (e.g., return not (abc and other)), and 5) assignments with complex expressions like abc = not other_var. These tests would help ensure the implementation handles all code paths correctly.

Copilot uses AI. Check for mistakes.
@github-actions

This comment has been minimized.

1 similar comment
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@jvansch1
Copy link

@asukaminato0721 I am going to review this but I also wanted to flag that we just did a large refactor of code action code in Pyrefly. It may be worth it to rebase this on main and see if there is any logic that we added that you can re-use and make sure there is not anything conflicting.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

.

fmt
@github-actions
Copy link

Diff from mypy_primer, showing the effect of this PR on open source code:

openlibrary (https://github.com/internetarchive/openlibrary)
- ERROR openlibrary/plugins/openlibrary/lists.py:811:43-813:18: Cannot set item in `dict[str, list[dict[Unknown, Unknown]]]` [unsupported-operation]
- ERROR openlibrary/plugins/openlibrary/lists.py:818:40-88: Cannot set item in `dict[str, list[dict[Unknown, Unknown]]]` [unsupported-operation]
- ERROR openlibrary/plugins/openlibrary/lists.py:823:42-825:18: Cannot set item in `dict[str, list[dict[Unknown, Unknown]]]` [unsupported-operation]
- ERROR openlibrary/plugins/upstream/tests/test_merge_authors.py:142:12-52: Object of class `NoneType` has no attribute `key` [missing-attribute]
- ::error file=openlibrary/plugins/openlibrary/lists.py,line=811,col=43,endLine=813,endColumn=18,title=Pyrefly unsupported-operation::Cannot set item in `dict[str, list[dict[Unknown, Unknown]]]`%0A  Argument `list[Thing | Unknown]` is not assignable to parameter `value` with type `list[dict[Unknown, Unknown]]` in function `dict.__setitem__`
- ::error file=openlibrary/plugins/openlibrary/lists.py,line=818,col=40,endLine=818,endColumn=88,title=Pyrefly unsupported-operation::Cannot set item in `dict[str, list[dict[Unknown, Unknown]]]`%0A  Argument `list[Thing | Unknown]` is not assignable to parameter `value` with type `list[dict[Unknown, Unknown]]` in function `dict.__setitem__`
- ::error file=openlibrary/plugins/openlibrary/lists.py,line=823,col=42,endLine=825,endColumn=18,title=Pyrefly unsupported-operation::Cannot set item in `dict[str, list[dict[Unknown, Unknown]]]`%0A  Argument `list[Thing | Unknown]` is not assignable to parameter `value` with type `list[dict[Unknown, Unknown]]` in function `dict.__setitem__`
- ::error file=openlibrary/plugins/upstream/tests/test_merge_authors.py,line=37,col=9,endLine=37,endColumn=12,title=Pyrefly bad-override::Class member `MockSite.get` overrides parent class `Site` in an inconsistent manner%0A  `MockSite.get` has type `BoundMethod[MockSite, (self: MockSite, key: Unknown) -> Thing | Unknown]`, which is not assignable to `BoundMethod[MockSite, (self: MockSite, key: Unknown, revision: Unknown | None = None, lazy: bool | Unknown = False) -> Thing | Unknown | None]`, the type of `Site.get`
+ ::error file=openlibrary/plugins/upstream/tests/test_merge_authors.py,line=37,col=9,endLine=37,endColumn=12,title=Pyrefly bad-override::Class member `MockSite.get` overrides parent class `Site` in an inconsistent manner%0A  `MockSite.get` has type `BoundMethod[MockSite, (self: MockSite, key: Unknown) -> Unknown]`, which is not assignable to `BoundMethod[MockSite, (self: MockSite, key: Unknown, revision: Unknown | None = None, lazy: bool | Unknown = False) -> Unknown | None]`, the type of `Site.get`
- ::error file=openlibrary/plugins/upstream/tests/test_merge_authors.py,line=142,col=12,endLine=142,endColumn=52,title=Pyrefly missing-attribute::Object of class `NoneType` has no attribute `key`

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.

2 participants