Skip to content

Support raw generic types in native headers - #193

Open
kindlich wants to merge 2 commits into
fix/mod-type-inference-1-2-expectationsfrom
fix/raw-types-in-headers
Open

kindlich wants to merge 2 commits into
fix/mod-type-inference-1-2-expectationsfrom
fix/raw-types-in-headers

Conversation

@kindlich

@kindlich kindlich commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Raw types in native headers: a raw class reference such as a GenClass parameter used to crash with Could not find type variable T. The converter looked up the class's own type variables, which are only in scope when the class refers to itself. Raw types are now erased the way Java does it: each type argument becomes out <erasure of its first bound>. So a raw Something<T extends Parent> exposes Parent's members, and an unbounded T becomes out Object. Bounds that lead back to the class being loaded (e.g. T extends Node<T>) become Object. Like JavaRuntimeClass.translateTypeParameters, this uses the TypeVariableContext as the guard: the type variables are put into a child context before their bounds are resolved.
  • Backend wildcard fix: JavaTypeInternalNameVisitor mapped every out X wildcard to java/lang/Object, so values typed out Parent were checkcast to Object, and calling Parent methods on them failed bytecode verification (VerifyError). This also affected explicit ? extends Parent parameters, not just raw types. JavaTypeDescriptorVisitor.visitWildcardOut also called itself instead of the bound. Both now use the upper bound. Both fixes are needed for bounded raw types to work end-to-end, so they're in one PR.

Testing

  • RawTypeInHeaderTest.testRawTypeErrors1/2 (added in e0851bc as a known failure) now pass.
  • New RawBoundedTypeInHeaderTest covers a raw bounded parameter accepting BoundedGenClass<Child> and a raw bounded return type exposing the bound's members at runtime.
  • New RawRecursiveBoundTypeInHeaderTest round-trips a raw self-bounded type. It's a separate class so that a failure points only at the recursion guard. Mutation-checked: with the guard disabled, only this test fails.
  • ./gradlew test --rerun-tasks: 1,043 tests, 0 failures.

Limitation: only the first bound is used, so T extends A & B becomes out A, matching Java's erasure.

Stacked on #190 (which is stacked on #191).

🤖 Generated with Claude Code

kindlich and others added 2 commits October 18, 2026 12:15
JavaTypeInternalNameVisitor mapped every `out X` wildcard to
java/lang/Object, so values typed `out Parent` were checkcast to Object
and calling Parent's methods on them failed bytecode verification.
JavaTypeDescriptorVisitor recursed into itself instead of the bound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A raw class reference (e.g. a `GenClass` parameter) was translated by
looking up the class's own type variables in the current context, which
only works when the class refers to itself. From anywhere else this threw
"Could not find type variable T".

Raw types are now erased the way Java does it: each type argument becomes
`out <erasure of its first bound>`, so a raw `Something<T extends Parent>`
exposes Parent's members. As in JavaRuntimeClass.translateTypeParameters,
the type variables are put into a child TypeVariableContext before their
bounds are resolved, so a bound leading back to the class being loaded
(e.g. `T extends Node<T>`) is detected and becomes Object.

Fixes RawTypeInHeaderTest; adds RawBoundedTypeInHeaderTest and
RawRecursiveBoundTypeInHeaderTest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kindlich
kindlich force-pushed the fix/raw-types-in-headers branch from 86b7e35 to e303d2e Compare October 18, 2026 12:36
@kindlich
kindlich marked this pull request as ready for review October 18, 2026 12:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant