Skip to content

UseVarForGenericMethodInvocations emits a type witness on an unqualified call, producing var x = <T>foo(...) #1219

Description

@jkschneider

What version of OpenRewrite are you using?

Moderne CLI 4.6.3, rewrite-migrate-java as resolved by org.openrewrite.java.migrate.UpgradeToJava25.

How are you running OpenRewrite?

mod run <org> --recipe <composite including UseVarForGenericMethodInvocations> over an organization of ten Java repositories.

What is the smallest, simplest way to reproduce the problem?

A local variable initialized from an unqualified call to a generic method, where the type argument is inferred from the target type:

public static <T extends HttpMessage> BodyReplacer<T> binary() {
    final Predicate<T> contentTypes = contentType(
            "application/octet-stream",
            "application/pdf");
    // ...
}

What did you expect to see?

The declaration left alone. var cannot preserve the meaning here: the type argument T was supplied
by the target type of the assignment, and removing the explicit type removes the only thing that
determined it.

What did you see instead?

final var contentTypes = <T>contentType(
        "application/octet-stream",
        "application/pdf");

The recipe moves the type argument into an explicit type witness, but a witness is only legal on a
qualified method invocation — this.<T>contentType(...), Foo.<T>contentType(...), or
super.<T>contentType(...). A bare <T>contentType(...) is a syntax error (JLS 15.12: the type
arguments follow the . of a qualified invocation).

What is the full stack trace of any errors you encountered?

No error; the recipe completes and emits invalid source.

Are you interested in contributing a fix to OpenRewrite?

Yes, if maintainers agree the right behaviour is to skip unqualified invocations rather than to
qualify them.

Additional context

Found while evaluating Java 25 idiom recipes across ten open source repositories. Reproduces in
zalando/logbook at
logbook-core/src/main/java/org/zalando/logbook/core/BodyReplacers.java:27 and :50, and once in
Yubico/java-webauthn-server.

Two candidate fixes: skip when the invocation has no select (cannot legally carry a witness), or
qualify it with this./the declaring type first. The skip is clearly correct; the qualify is only
correct when a receiver is actually in scope, so the guard is needed either way.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingrecipeRecipe requested

    Type

    No type

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions