Skip to content

Add return-type-widening guard to Refaster template engine#179

Merged
knutwannheden merged 4 commits intomainfrom
target-type-check
Feb 11, 2026
Merged

Add return-type-widening guard to Refaster template engine#179
knutwannheden merged 4 commits intomainfrom
target-type-check

Conversation

@knutwannheden
Copy link
Contributor

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:

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:

// 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 StringObject widening

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
Copy link
Member

@timtebeek timtebeek left a comment

Choose a reason for hiding this comment

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

Nice! Good to avoid changes where unsafe to do so.

@github-project-automation github-project-automation bot moved this from In Progress to Ready to Review in OpenRewrite Feb 11, 2026
@knutwannheden knutwannheden merged commit f074d48 into main Feb 11, 2026
2 checks passed
@knutwannheden knutwannheden deleted the target-type-check branch February 11, 2026 21:45
@github-project-automation github-project-automation bot moved this from Ready to Review to Done in OpenRewrite Feb 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants