bazelbuild/bazel · error · IllegalArgumentException
Expected one of %s and %s to be a subclass of the other
Error message
Expected one of %s and %s to be a subclass of the other
What it means
Starlark's annotation machinery resolves a class's Starlark type by finding the most-specific @StarlarkBuiltin-annotated ancestor. If two annotated ancestors are unrelated (neither assignable to the other), there is no unique answer and this IllegalArgumentException is thrown. The fix, documented in the comment, is to annotate the class itself so it has its own explicit type.
Source
Thrown at src/main/java/net/starlark/java/annot/StarlarkAnnotations.java:51
private static Class<?> moreSpecific(Class<?> x, Class<?> y) {
if (x == null) {
return y;
} else if (y == null) {
return x;
} else if (x.isAssignableFrom(y)) {
return y;
} else if (y.isAssignableFrom(x)) {
return x;
} else {
// If this exception occurs, it indicates the following error scenario:
//
// Suppose class A is a subclass of both B and C, where B and C are annotated with
// @StarlarkBuiltin annotations (and are thus considered "Starlark types"). If B is not a
// subclass of C (nor vice versa), then it's impossible to resolve whether A is of type
// B or if A is of type C. It's both! The way to resolve this is usually to have A be its own
// type (annotated with @StarlarkBuiltin), and thus have the explicit type of A be
// semantically "B and C".
throw new IllegalArgumentException(
String.format("Expected one of %s and %s to be a subclass of the other", x, y));
}
}
/**
* Information extracted by walking a class's ancestors' {@link StarlarkBuiltin} annotations.
*
* @param starlarkBuiltinAncestor the most-specified ancestor annotated with {@link
* StarlarkBuiltin}. (It is guaranteed that if two ancestors both define the annotation, one
* of them is a subtype of the other.)
* @param assignableToStructType whether any {@code StarlarkBuiltin}-annotated ancestor has set
* {@link StarlarkBuiltin#isStructType} to true.
*/
private record ClassInfo(Class<?> starlarkBuiltinAncestor, boolean assignableToStructType) {}
// A map from a class to its ClassInfo.
private static final ClassValue<ClassInfo> classInfos =
new ClassValue<ClassInfo>() {View on GitHub (pinned to e6e199d060)
Solutions
- Annotate the class itself with @StarlarkBuiltin so its type is explicit and ancestor resolution never has to choose.
- Alternatively break the diamond: make the class inherit from only one @StarlarkBuiltin-annotated ancestor.
- If both interfaces must remain, verify one is annotated as a struct/subtype relationship or restructure so one extends the other.
- Re-run the Starlark integration test that registers the class to confirm resolution succeeds.
Example fix
// before
class MyValue implements FooType, BarType {} // both annotated, unrelated
// after
@StarlarkBuiltin(name = "my_value", doc = "...")
class MyValue implements FooType, BarType {} Defensive patterns
Strategy: validation
Validate before calling
// Detect the unrelated-ancestor diamond before registering the class
static boolean hasUniqueBuiltinAncestor(Class<?> c) {
List<Class<?>> annotated = new ArrayList<>();
for (Class<?> k = c; k != null; k = k.getSuperclass()) {
if (k.isAnnotationPresent(StarlarkBuiltin.class)) annotated.add(k);
for (Class<?> i : k.getInterfaces()) {
if (i.isAnnotationPresent(StarlarkBuiltin.class) && !annotated.contains(i)) annotated.add(i);
}
}
for (int a = 0; a < annotated.size(); a++)
for (int b = a + 1; b < annotated.size(); b++)
if (!annotated.get(a).isAssignableFrom(annotated.get(b))
&& !annotated.get(b).isAssignableFrom(annotated.get(a))) return false;
return true;
} Prevention
- Annotate every Starlark-facing concrete class with @StarlarkBuiltin.
- Before implementing an extra Starlark interface, check whether it is @StarlarkBuiltin-annotated and unrelated to the current base.
- Cover new value classes with a test that builds their Starlark descriptor.
When it happens
Trigger: A Java class extends/implements two distinct @StarlarkBuiltin-annotated types (diamond inheritance of Starlark types) without itself being @StarlarkBuiltin-annotated; StarlarkAnnotations.getStarlarkBuiltinAncestor (or CallUtils descriptor building) is invoked on such a class.
Common situations: Making a Starlark value class implement an extra Starlark interface (e.g. adding Sequence or Comparable besides its builtin base); merging two type hierarchies during a refactor; registering the class in a Starlark module.
Related errors
- Class %s has an incompatible overload of annotated method %s
- Class %s has multiple overloaded methods named '%s' annotate
- Class %s has two selfCall methods defined
- %s missing getStarlarkType(StarlarkSemantics) method
- Class %s has multiple methods named getAssociatedTypeConstru
AI-assisted analysis of bazelbuild/bazel@e6e199d060 (2026-08-14).
Data as JSON: /api/errors/3471de941f6093bf.
Report an issue: GitHub.