Add return-type-widening guard to Refaster template engine#179
Merged
knutwannheden merged 4 commits intomainfrom Feb 11, 2026
Merged
Add return-type-widening guard to Refaster template engine#179knutwannheden merged 4 commits intomainfrom
knutwannheden merged 4 commits intomainfrom
Conversation
When a Refaster `@AfterTemplate` has a wider return type than the `@BeforeTemplate`, and the matched expression is used in a context that depends on the narrower type (e.g., as a receiver for a chained method call), the replacement can break compilation.
### Example
Given a Refaster rule where before returns `Integer` and after returns `Number`:
```java
class ToNumber {
@BeforeTemplate
Integer before(int value) {
return Integer.valueOf(value);
}
@AfterTemplate
Number after(int value) {
return value;
}
}
```
This input would be broken by the replacement:
```java
// Before: compiles fine — compareTo() exists on Integer
Integer.valueOf(42).compareTo(10);
// After: compile error — Number doesn't have compareTo()
((Number) 42).compareTo(10);
```
Error Prone Refaster has the same behavior, but it only presents suggestions for developer review. OpenRewrite applies fixes automatically, so it needs to be more conservative.
### Changes
- Added `isAssignableToTargetType(String)` helper on `AbstractRefasterJavaVisitor` that checks whether the after template's return type is compatible with the target type expected by the surrounding context (receiver of chained call, method argument, assignment target)
- `RecipeWriter.generateVisitMethod()` now detects return type widening at annotation-processor time using javac's `Types.isSubtype()` on erased types, and emits a guard that skips the match when the wider type would be incompatible with the context
- Updated `PreconditionsVerifierRecipes.java` expected test output to include the guard for templates with `String` → `Object` widening
src/main/java/org/openrewrite/java/template/processor/RecipeWriter.java
Outdated
Show resolved
Hide resolved
timtebeek
reviewed
Feb 1, 2026
timtebeek
approved these changes
Feb 11, 2026
Member
timtebeek
left a comment
There was a problem hiding this comment.
Nice! Good to avoid changes where unsafe to do so.
3 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.
When a Refaster
@AfterTemplatehas a wider return type than the@BeforeTemplate, and the matched expression is used in a context that depends on the narrower type (e.g., as a receiver for a chained method call), the replacement can break compilation.Example
Given a Refaster rule where before returns
Integerand after returnsNumber:This input would be broken by the replacement:
Error Prone Refaster has the same behavior, but it only presents suggestions for developer review. OpenRewrite applies fixes automatically, so it needs to be more conservative.
Changes
isAssignableToTargetType(String)helper onAbstractRefasterJavaVisitorthat checks whether the after template's return type is compatible with the target type expected by the surrounding context (receiver of chained call, method argument, assignment target)RecipeWriter.generateVisitMethod()now detects return type widening at annotation-processor time using javac'sTypes.isSubtype()on erased types, and emits a guard that skips the match when the wider type would be incompatible with the contextPreconditionsVerifierRecipes.javaexpected test output to include the guard for templates withString→Objectwidening