Skip to content

BlockStatementTemplateGenerator leaks unbound type variables from caller scope into synthetic template class #7407

@timtebeek

Description

@timtebeek

What version of OpenRewrite are you using?

rewrite 8.80.0-SNAPSHOT (rewrite-java / rewrite-kotlin)

How are you running OpenRewrite?

Via JavaTemplate.apply / KotlinTemplate.apply with an Expression parameter whose declared type is JavaType.Parameterized or JavaType.GenericTypeVariable bound to a caller-scope type variable.

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

In a recipe, pass an Expression parameter whose type carries an unbound type variable. For example, when replacing builder.default { ... } where builder has type PolymorphicModuleBuilder<T> (caller-side <T : Any>), the parameter is serialized by Substitutions/TypeUtils.toString() into:

class Template {
/*__TEMPLATE__*/__P__./*__p0__*/p<kotlinx.serialization.modules.PolymorphicModuleBuilder<T>>().defaultDeserializer(...)
;
}

The caller's type variable T is undeclared inside the synthetic class Template {}, so the parser rejects the template with Could not parse as Java: ....

What did you expect to see?

Template parameters whose declared type references unbound type variables should be erased (raw type, or bound) during synthetic template generation.

What did you see instead?

IllegalArgumentException: Could not parse as Java: ...

Suggested fix

In Substitutions.getTypeName() / TypeUtils.toString(JavaType) as used by the template parameter rendering path, apply type erasure:

  • JavaType.Parameterized → its raw getType()
  • JavaType.GenericTypeVariable → first bound (or java.lang.Object if empty)
  • Recurse for JavaType.Array element types

This mirrors the JVM bytecode erasure and keeps the synthetic template class self-contained.

Workaround

Strip generics from each Expression parameter before calling apply: https://github.com/moderneinc/rewrite-migrate-kotlin/commit/8f2ec1b — see withRawType / toRawType in ReplaceKotlinMethod.java.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions