Repository navigation
FindMissingTypes: flag a parameterized type or method return type left with a stale type - #9284
Draft
knutwannheden wants to merge 2 commits into
Draft
knutwannheden wants to merge 2 commits into
knutwannheden wants to merge 2 commits into
Conversation
A recipe that replaced only the clazz of a parameterized type, leaving getType() naming the old class, passed type validation. The stale type showed up only as a recipe needing a second cycle to converge. FindMissingTypes now reports a J.ParameterizedType whose clazz type and own type name different classes, comparing fully qualified names and skipping missing or unknown types.
… type A recipe that rewrote a method's return type expression, but not the return type of its JavaType.Method, passed type validation. Method matchers and later cycles kept seeing the old return type. FindMissingTypes now compares the return type expression's type with the method type's return type. C-style dimensions such as `byte toByteArray()[]` are stripped from the method type first, because they belong to the return type but follow the parameter list. The check found that ChangeType from a class to a primitive, such as java.lang.Integer to int, rewrote the LST but left every type naming Integer. ChangeType now maps the original type to the primitive target, except in declaring types, owners and supertypes, which must stay classes.
This was referenced Oct 20, 2026
knutwannheden
marked this pull request as draft
October 20, 2026 11:01
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.
In openrewrite/rewrite-micronaut,
SubclassesReturnedFromFactoriesNotInjectablerewrote a factory method's return type fromQi<String>toKq<String>withparameterizedType.withClazz(newIdentifier), leavinggetType()atQi<String>.RewriteTest's type validation accepted the result. The only symptom was the test needingexpectedCyclesThatMakeChanges(2), because the next cycle compared against the stale type. The same recipe also never updated the method'sJavaType.Method, which still returnedQi<String>.FindMissingTypesalready checks that a method's name and its method type agree. This adds two checks of the same kind:How each check compares
The
J.ParameterizedTypecheck compares fully qualified names, because a recipe may legitimately rebuild an equal type, for example as aShallowClass. AJavaType.Parameterizedreports its raw type's name. A side that is null orUnknownis skipped, since missing types are already reported elsewhere.The method check compares with
TypeUtils.isOfType, so type arguments count. C-style dimensions belong to the return type but follow the parameter list, so they are stripped from the method type before comparing:ChangeTypeto a primitive kept the old typesThe method check found one bug in this repository.
ChangeType("java.lang.Integer", "int")rewroteInteger getFoo()toint getFoo()but left every type namingInteger, becauseupdateTypeonly mapped fully qualified targets.It now maps the original type to a primitive target. Declaring types, field owners and supertypes keep the original class, since a primitive cannot fill them.
Stringstays excluded because the parser types it as thejava.lang.Stringclass.Hit counts
Before making the checks fail tests, a probe logged every disagreement across rewrite-java-test, rewrite-java, the TCK on Java 8/11/17/21/25, and the Java sources in the maven, gradle, kotlin, groovy, android and rewrite-test tests:
ChangeTypebug (fixed here)Downstream, the method check fails four
SubclassesReturnedFromFactoriesNotInjectabletests in rewrite-micronaut until that recipe updates its method type. Other recipe repositories that rewrite a return type expression by hand may need the same fix.Tests
FindMissingTypesTestgains one test per check, built from a recipe that changes the expression and leaves the type stale.ChangeTypeTest.classToPrimitivefails without theChangeTypefix.classToPrimitiveKeepsClassAsDeclaringTypefails with aClassCastExceptionif the owner guard is removed, which I checked by removing it.