bazelbuild/bazel · error · IllegalArgumentException

Class %s has two selfCall methods defined

Error message

Class %s has two selfCall methods defined

What it means

A Starlark value class may expose at most one @StarlarkMethod(selfCall = true) method, because selfCall defines what happens when the value itself is called like a function. CallUtils scans all annotated methods and throws this IllegalArgumentException if it finds two non-synthetic selfCall methods (synthetic bridge methods from covariant overrides are tolerated).

Source

Thrown at src/main/java/net/starlark/java/eval/CallUtils.java:367

      // enabled by semantics?
      if (!manager
          .getSemantics()
          .isFeatureEnabledBasedOnTogglingFlags(
              callable.enableOnlyWithFlag(), callable.disableWithFlag())) {
        continue;
      }

      MethodDescriptor descriptor = MethodDescriptor.of(manager, method, callable);

      // self-call method?
      if (callable.selfCall()) {
        if (selfCall == null) {
          selfCall = descriptor;
        } else if (!method.isSynthetic()) {
          // Two distinct selfCall methods. (A synthetic bridge of the same method -- e.g. from a
          // covariant return override -- is not a conflict and is simply ignored.)
          throw new IllegalArgumentException(
              String.format("Class %s has two selfCall methods defined", clazz.getName()));
        }
        continue;
      }

      // regular method
      methods.putIfAbsent(callable.name(), descriptor);
    }

    ClassDescriptor classDescriptor = new ClassDescriptor();
    classDescriptor.manager = manager;
    classDescriptor.selfCall = selfCall;
    classDescriptor.methods = ImmutableMap.copyOf(methods);
    classDescriptor.typeConstructor = associatedTypeConstructor;
    if (getFixedStarlarkType(clazz) == null && wantStarlarkBuiltinAutoType(clazz)) {
      if (classDescriptor.typeConstructor == null) {
        classDescriptor.typeConstructor =
            Types.wrapType(

View on GitHub (pinned to e6e199d060)

Solutions

  1. Keep exactly one @StarlarkMethod(selfCall = true) method; remove the annotation (or set selfCall = false) on the other.
  2. Delete the overriding duplicate and rely on the inherited selfCall method.
  3. If both operations must remain, expose the second as a regular named method.

Example fix

// before
class F implements StarlarkValue {
  @StarlarkMethod(selfCall = true) public Object call() {...}
  @StarlarkMethod(selfCall = true) public Object invoke() {...}
}

// after
class F implements StarlarkValue {
  @StarlarkMethod(selfCall = true) public Object call() {...}
  @StarlarkMethod(name="invoke") public Object invoke() {...}
}
Defensive patterns

Strategy: validation

Validate before calling

// Assert exactly one selfCall method before first use
static void checkSingleSelfCall(Class<?> c) {
  int n = 0;
  for (Method m : c.getMethods()) {
    StarlarkMethod a = m.getAnnotation(StarlarkMethod.class);
    if (a != null && a.selfCall()) n++;
  }
  if (n > 1) throw new IllegalArgumentException(c + " has " + n + " selfCall methods");
}

Prevention

When it happens

Trigger: Two methods annotated @StarlarkMethod(selfCall = true) on the same class, or one local plus one inherited annotated selfCall method; implementing a callable interface while also annotating the class's own call method as selfCall.

Common situations: Refactoring from a call() convention to selfCall without removing the old annotation; combining two callable base types; copy-pasting an annotated method into a subclass.

Related errors


AI-assisted analysis of bazelbuild/bazel@e6e199d060 (2026-08-14). Data as JSON: /api/errors/b0d2166e44e3d778. Report an issue: GitHub.