Preserve receiver effects when retargeting methods to static calls - #8467
Draft
martinfrancois wants to merge 1 commit into
Draft
Conversation
…st when dropping the select staticallyImportedFieldReceiverNotChanged pins that deleting a bare statically imported field receiver (INSTANCE.stat() -> B.stat()) loses the class initialization its read triggers per JLS 12.4.1. chainedCallsNotCollapsedWhenArgumentsWouldBeDropped pins that collapsing a chain matched at both calls drops the intermediate call's arguments, so argument() is never evaluated. Both tests are marked @ExpectedToFail as known failing.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Suggested review order: 38 of 52 (Score: 2)
Review first: openrewrite/rewrite-testing-frameworks#1083
What's changed?
Adds 2 known-failing tests to
ChangeMethodTargetToStaticTestthat reproduce two cases whereChangeMethodTargetToStaticdeletes a receiver expression whose evaluation has observable effects. No recipe code changes. The tests are marked@ExpectedToFail(junit-pioneer) so the suite stays green; removing the annotation shows the failures.What's your motivation?
Recipe:
org.openrewrite.java.ChangeMethodTargetToStatic.When the recipe rewrites a call to a static call, it drops the old receiver. Two shapes of receiver still carry behavior that gets lost.
Case 1: statically imported field receiver
Before
Test
staticallyImportedFieldReceiverNotChangeduses a bare statically imported field receiver. With the recipeChangeMethodTargetToStatic("a.A stat()", "b.B", null, null, false)and this input:Actual after the recipe
Using current main.
Expected after the recipe
(unchanged)The read of
INSTANCEis gone. Before the recipe runs, that read triggers class initialization ofHolder(JLS 12.4.1). I checked this with a compiled scratch program:INSTANCE.stat()printed "Holder initialized" then "A.stat called", while the rewrittenB.stat()printed only "B.stat called", soHolder's static initializer never runs. The bare name is aJ.Identifier, so the recipe treats it as a droppable variable read; a qualifiedHolder.INSTANCEreceiver is aJ.FieldAccessand already stops the rewrite. The test expects no change to the file.Case 2: nested matched call with an argument
Before
With
ChangeMethodTargetToStatic("a.A *(..)", "b.B", null, null, false), the input islegacy.combine(argument()).value().Actual after the recipe
B.value()Expected after the recipe
(unchanged)The intermediate matched call
combine(argument())is deleted together with its argument. Verified the same way: the original chain evaluatesargument(), the rewritten form never calls it. A pattern that matches onlyvalue()does not show this. The test expects no change here as well.Both tests fail on main with exact-diff assertion errors. All 11 pre-existing passing tests in the class still pass; the class has 12 tests on main (11 pass, 1 skipped by a pre-existing
@Disabled), and 14 with these additions.I found this while preparing #8444, which narrows receiver dropping in this recipe to forms without observable effects. That PR deliberately leaves both of these shapes unchanged and lists them as remaining defects; the tests here still fail on main and on top of #8444.
Anything in particular you'd like reviewers to focus on?
I think both are genuine bugs: the rewritten code silently loses evaluation that ran before, so program behavior changes even though the output compiles. If you agree this should change, I would gladly prepare the fix. If this behavior is intended, feel free to close this and I know it is settled.
Any additional context
Pre-existing tests changed: None.
Related open PR of mine touching the same recipe: #8444. It does not fix these two cases.
This reproduction was prepared with AI assistance (Claude Code). I reviewed the tests and this description.
The added reproduction tests and the existing suite together cover changed and unchanged behavior. The known-failing tests remain disabled until implementation. The formatter run was calibrated per file; untouched lines were not reformatted.
Checklist
./gradlew buildlocally, and committed any resulting changes torecipes.csv