Skip to content

Avoid traversing supertypes for non-generic assignability - #278

Draft
idanakav wants to merge 1 commit into
uber:mainfrom
idanakav:fix/ksp-non-generic-assignability
Draft

idanakav wants to merge 1 commit into
uber:mainfrom
idanakav:fix/ksp-non-generic-assignability

Conversation

@idanakav

@idanakav idanakav commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Description:

When KSP processes a Java class whose transitive supertypes are absent from its strict classpath, XProcessing can classify unresolved interfaces as classes.

For example:

public class Child extends Parent
    implements Target, Capabilities.Foo, Capabilities.Bar {}

Child is first compiled with Parent, Foo, and Bar available. The KSP consumer then receives only the direct artifact containing Child and Target.

When XProcessing computes Child.superTypes, it identifies all three unresolved transitive types as classes and throws:

java.lang.IllegalStateException:
Class test.Child should have only one super class.
Found 3 (test.Parent, test.Capabilities.Foo, test.Capabilities.Bar)

The exception comes from XProcessing KSP type handling, which partitions supertypes into classes and interfaces and requires a class to have at most one superclass.

CompilerType.isAssignableTo() has already confirmed assignability before reaching this traversal:

if (!env.typeUtils.isAssignable(mirror, baseMirror)) {
  return false
}

For a non-generic target, there are no type arguments to resolve through a matching supertype. Traversing the hierarchy cannot change the result and unnecessarily enters the failing XProcessing path.

This change moves the existing non-generic early return before getMatchingSuperType():

if (baseMirror.typeArguments.isEmpty()) {
  return true
}

val matchingType = getMatchingSuperType(baseMirror, mirror) ?: return false

Generic targets retain the existing matching-supertype and type-argument checks.

The regression test reproduces the strict-classpath arrangement using two compilation stages:

  1. Compile Parent, Foo, and Bar as an external dependency.
  2. Compile Child and Target against that dependency.
  3. Run KSP with only the direct Child/Target artifact.

With the original ordering, the test fails with the three-superclass exception shown above. With the early return, it passes.

The current room-compiler-processing:2.8.5 artifact retains the same superclass-count invariant, so updating XProcessing alone does not remove this unsafe traversal.

Test plan:

./gradlew :compiler-ast:test

Related issue(s):

None.

XProcessing can report unresolved Java interfaces as additional superclasses when transitive supertypes are absent from a strict KSP classpath. Once assignability is established, non-generic targets do not require matching-supertype traversal.

Return early for non-generic targets and add a two-stage classpath regression test that reproduces the XProcessing failure.
@idanakav
idanakav marked this pull request as draft September 10, 2026 22:51
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