Skip to content

FindMissingTypes: flag a parameterized type or method return type left with a stale type - #9284

Draft
knutwannheden wants to merge 2 commits into
mainfrom
findmissingtypes-flag-parameterizedtype-type/clazz-mismatch
Draft

knutwannheden wants to merge 2 commits into
mainfrom
findmissingtypes-flag-parameterizedtype-type/clazz-mismatch

Conversation

@knutwannheden

Copy link
Copy Markdown
Contributor

In openrewrite/rewrite-micronaut, SubclassesReturnedFromFactoriesNotInjectable rewrote a factory method's return type from Qi<String> to Kq<String> with parameterizedType.withClazz(newIdentifier), leaving getType() at Qi<String>. RewriteTest's type validation accepted the result. The only symptom was the test needing expectedCyclesThatMakeChanges(2), because the next cycle compared against the stale type. The same recipe also never updated the method's JavaType.Method, which still returned Qi<String>.

FindMissingTypes already checks that a method's name and its method type agree. This adds two checks of the same kind:

ParameterizedType#clazz type does not match the raw type of ParameterizedType#type
MethodDeclaration#returnTypeExpression type does not match the return type of the MethodType

How each check compares

The J.ParameterizedType check compares fully qualified names, because a recipe may legitimately rebuild an equal type, for example as a ShallowClass. A JavaType.Parameterized reports its raw type's name. A side that is null or Unknown is 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:

byte toByteArray()[]   // return type expression: byte, method type returns: byte[]

ChangeType to a primitive kept the old types

The method check found one bug in this repository. ChangeType("java.lang.Integer", "int") rewrote Integer getFoo() to int getFoo() but left every type naming Integer, because updateType only 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. String stays excluded because the parser types it as the java.lang.String class.

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:

Check Hits What they were
ParameterizedType 0 none
Method return type 7 5 C-style array methods (handled by stripping the dimensions), 1 missing type (skipped), 1 ChangeType bug (fixed here)

Downstream, the method check fails four SubclassesReturnedFromFactoriesNotInjectable tests 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

FindMissingTypesTest gains one test per check, built from a recipe that changes the expression and leaves the type stale. ChangeTypeTest.classToPrimitive fails without the ChangeType fix. classToPrimitiveKeepsClassAsDeclaringType fails with a ClassCastException if the owner guard is removed, which I checked by removing it.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants