Repository navigation
Description
Description
When using Inline Method on a method that is overridden in a subclass, VS Code Java performs the refactoring successfully, but the refactored program changes its runtime behavior.
The original program relies on dynamic dispatch. After inlining the superclass method body, the polymorphic method call is replaced with a fixed value, which breaks the original behavior.
Steps to reproduce
- Create the following Java code.
- Place the caret on method
fin classA. - Invoke Refactor -> Inline Method.
- Apply the inline refactoring.
- Run the program before and after refactoring.
Original code
package org.example;
public class Main {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
a1.test();
a2.test();
}
}
class A {
int f() {
return 1;
}
void test() {
int x = f(); // Inline target
System.out.println(x);
}
}
class B extends A {
@Override
int f() {
return 2;
}
}Actual behavior
VS Code Java performs the inline refactoring. The refactored program still compiles, but its runtime behavior changes.
Original output:
1
2
Refactored output:
1
1
Refactored code generated by VS Code Java:
package org.example;
public class Main {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
a1.test();
a2.test();
}
}
class A {
void test() {
int x = 1;
System.out.println(x);
}
}
class B extends A {
@Override
int f() {
return 2;
}
}The inline refactoring replaces the polymorphic method call f() with the body of A.f(). However, the call inside A.test() is dynamically dispatched at runtime. When test() is invoked on an instance of B, the original program calls B.f() and prints 2.
After refactoring, this dynamic dispatch is removed, so both calls print 1.
Expected behavior
Inline Method should preserve the runtime behavior of the original program.
The refactoring should either:
- reject the inline operation, or
- report a warning/error indicating that inlining this method may change behavior because the method is overridden and the call is dynamically dispatched.
It should not silently generate behavior-changing code.
Activity
chagong commentedon Jun 23, 2026
Thanks for the clear repro. I looked into the JDT LS inline-method path, and the behavior appears to come from how
InlineMethodRefactoringcondition results are handled when JDT LS turns the refactoring into a code action edit.JDT LS offers
Inline Methodby creating an Eclipse JDTInlineMethodRefactoringhere:The underlying JDT refactoring already has logic to detect this class of problem. During final condition checking,
InlineMethodRefactoringcallscheckOverridden(...), walks the type hierarchy, and reports an error when the method to inline is overridden, overrides another method, or implements an interface method:So the issue is not that JDT cannot recognize the polymorphic hazard. It can. The problem is that JDT LS can still produce a workspace edit while those non-fatal refactoring status entries are not surfaced to the user. In
RefactorProcessor, the change is created withRefactoringStatus.FATALas the failure threshold, so aRefactoringStatus.ERRORsuch as "Type B overrides method to be inlined" may not prevent a change from being created:From a refactoring UX perspective, silently applying this edit is risky because
Inline Methodis expected to preserve behavior. In this example, replacingf()insideA.test()removes a virtual dispatch point. Before inlining,a2.test()dispatchesf()toB.f()at runtime. After inliningA.f()directly, that dispatch no longer exists, so behavior changes.A potential fix would be:
InlineMethodRefactoringcondition errors when creating the inline-method edit.Inline Method, JDT LS should likely treatRefactoringStatus.ERRORas blocking, at least for the overridden/overriding/implements cases reported byInlineMethodRefactoring.checkOverridden(...).Inline Methodedit/action, or an explicit failure explaining that inlining may change behavior because the method participates in dynamic dispatch.org.eclipse.jdt.core.manipulation: single-call inline should not create a behavior-changing edit after detecting overridden methods. JDT LS should still guard this path because the code-action flow does not provide Eclipse IDE refactoring wizard UX for reviewing such status entries before applying the edit.