{"record":{"id":"3471de941f6093bf","repo":"bazelbuild/bazel","slug":"expected-one-of-s-and-s-to-be-a-subclass-of-the","errorCode":null,"errorMessage":"Expected one of %s and %s to be a subclass of the other","messagePattern":"Expected one of (.+?) and (.+?) to be a subclass of the other","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"src/main/java/net/starlark/java/annot/StarlarkAnnotations.java","lineNumber":51,"sourceCode":"  private static Class<?> moreSpecific(Class<?> x, Class<?> y) {\n    if (x == null) {\n      return y;\n    } else if (y == null) {\n      return x;\n    } else if (x.isAssignableFrom(y)) {\n      return y;\n    } else if (y.isAssignableFrom(x)) {\n      return x;\n    } else {\n      // If this exception occurs, it indicates the following error scenario:\n      //\n      // Suppose class A is a subclass of both B and C, where B and C are annotated with\n      // @StarlarkBuiltin annotations (and are thus considered \"Starlark types\"). If B is not a\n      // subclass of C (nor vice versa), then it's impossible to resolve whether A is of type\n      // B or if A is of type C. It's both! The way to resolve this is usually to have A be its own\n      // type (annotated with @StarlarkBuiltin), and thus have the explicit type of A be\n      // semantically \"B and C\".\n      throw new IllegalArgumentException(\n          String.format(\"Expected one of %s and %s to be a subclass of the other\", x, y));\n    }\n  }\n\n  /**\n   * Information extracted by walking a class's ancestors' {@link StarlarkBuiltin} annotations.\n   *\n   * @param starlarkBuiltinAncestor the most-specified ancestor annotated with {@link\n   *     StarlarkBuiltin}. (It is guaranteed that if two ancestors both define the annotation, one\n   *     of them is a subtype of the other.)\n   * @param assignableToStructType whether any {@code StarlarkBuiltin}-annotated ancestor has set\n   *     {@link StarlarkBuiltin#isStructType} to true.\n   */\n  private record ClassInfo(Class<?> starlarkBuiltinAncestor, boolean assignableToStructType) {}\n\n  // A map from a class to its ClassInfo.\n  private static final ClassValue<ClassInfo> classInfos =\n      new ClassValue<ClassInfo>() {","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/bazelbuild/bazel/blob/e6e199d0601a244511b4cf18c8b2828aa73db1fd/src/main/java/net/starlark/java/annot/StarlarkAnnotations.java#L33-L69","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nclass MyValue implements FooType, BarType {} // both annotated, unrelated\n\n// after\n@StarlarkBuiltin(name = \"my_value\", doc = \"...\")\nclass MyValue implements FooType, BarType {}","handlingStrategy":"validation","validationCode":"// Detect the unrelated-ancestor diamond before registering the class\nstatic boolean hasUniqueBuiltinAncestor(Class<?> c) {\n  List<Class<?>> annotated = new ArrayList<>();\n  for (Class<?> k = c; k != null; k = k.getSuperclass()) {\n    if (k.isAnnotationPresent(StarlarkBuiltin.class)) annotated.add(k);\n    for (Class<?> i : k.getInterfaces()) {\n      if (i.isAnnotationPresent(StarlarkBuiltin.class) && !annotated.contains(i)) annotated.add(i);\n    }\n  }\n  for (int a = 0; a < annotated.size(); a++)\n    for (int b = a + 1; b < annotated.size(); b++)\n      if (!annotated.get(a).isAssignableFrom(annotated.get(b))\n          && !annotated.get(b).isAssignableFrom(annotated.get(a))) return false;\n  return true;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["starlark","annotations","type-system","java"],"backgroundTag":null,"analyzedSha":"e6e199d0601a244511b4cf18c8b2828aa73db1fd","analyzedAt":"2026-08-14T10:24:27.848Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}