Skip to content

Preserve comments and Groovy conversions when replacing wrapper constructors - #991

Draft
martinfrancois wants to merge 1 commit into
openrewrite:mainfrom
martinfrancois:repro/primitive-wrapper-comments-groovy
Draft

Preserve comments and Groovy conversions when replacing wrapper constructors#991
martinfrancois wants to merge 1 commit into
openrewrite:mainfrom
martinfrancois:repro/primitive-wrapper-comments-groovy

Conversation

@martinfrancois

@martinfrancois martinfrancois commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Suggested review order: 42 of 52 (Score: 2)
Review first: openrewrite/rewrite-migrate-java#1195

What's changed?

Adds 2 known-failing tests to PrimitiveWrapperClassConstructorToValueOfTest that reproduce two defects in PrimitiveWrapperClassConstructorToValueOf: the recipe drops comments written between new and the constructor arguments, and on Groovy it misses the (float) cast for a compound double argument to new Float(...). No recipe code changes. The tests are marked @ExpectedToFail so the suite stays green; removing the annotation shows the failures. The class had 7 tests on main, all passing, and has 9 with these additions.

What's your motivation?

Recipe: org.openrewrite.staticanalysis.PrimitiveWrapperClassConstructorToValueOf.

Case 1: preserve Java comments

Before

class A {
    Integer i = new /* keep me */ Integer(42);
}

Actual after the recipe

Using current main.

class A {
    Integer i = Integer.valueOf(42);
}

Expected after the recipe

Integer i = /* keep me */ Integer.valueOf(42);

The /* keep me */ comment is gone. The recipe replaces the whole J.NewClass with a template, and comments carried on the inner class identifier are discarded. This applies to every wrapper class the recipe handles, not just Integer. After the recipe runs, the code should keep the comment, for example Integer i = /* keep me */ Integer.valueOf(42);.

Case 2: preserve a compound double argument

Before

double d1 = 1.0d
double d2 = 2.0d
Float sum = new Float(d1 + d2)

Actual after the recipe

Using current main.

Float sum = Float.valueOf(d1 + d2)

Expected after the recipe

Float sum = Float.valueOf((float) (d1 + d2))

The Groovy parser attributes the type of d1 + d2 as java.lang.Object rather than double, so the recipe's argument type checks miss and it falls through to the plain Float.valueOf(#{any(float)}) template with no cast. There is no Float.valueOf(double) overload; the equivalent Java code does not compile ("no suitable method found for valueOf(double)", verified on Java 25). Dynamic Groovy coerces at runtime, so this is not a runtime crash there, but the output breaks under @CompileStatic and is inconsistent with what the recipe produces for the same Java code. The expected output is Float.valueOf((float) (d1 + d2)); I verified that new Float(d1 + d2) and Float.valueOf((float) (d1 + d2)) produce equal values.

Found while preparing #970, which fixes a related Float(double) cast defect in this recipe. Both defects are present on current main and stay present with the change from #970 applied. #970 keys its fix on JavaType.Primitive.Double, and the Groovy argument is attributed as Object, so #970 will not close the Groovy case.

Anything in particular you'd like reviewers to focus on?

I think both are genuine bugs: defect 1 silently loses source content, and defect 2 produces code that fails under static compilation. The Groovy root cause looks framework level, in rewrite-groovy type attribution of compound expressions, rather than in this recipe. 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 on the same recipe: #970 (it does not fix either defect here). openrewrite/rewrite#8170 documents a different Groovy type attribution gap and is adjacent context only. 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

…ped comments and missing Groovy cast

preserveCommentsWithinNewClass pins that comments between 'new' and the
argument list are dropped when the constructor is replaced with valueOf.
groovyCompoundDoubleToFloat pins that a compound double argument in Groovy
gets no (float) cast because its type is not resolved.
Both are marked @ExpectedToFail; related: openrewrite#970.
@github-project-automation github-project-automation Bot moved this to In Progress in OpenRewrite Aug 11, 2026
@martinfrancois martinfrancois changed the title PrimitiveWrapperClassConstructorToValueOf: add failing tests for dropped comments and missing Groovy cast Preserve comments and Groovy conversions when replacing wrapper constructors Aug 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

1 participant