bazelbuild/bazel · error · EvalException

unsupported comparison: %s <=> %s

Error message

unsupported comparison: %s <=> %s

What it means

Thrown by the built-in min()/max() when the elements being ranked are mutually incomparable. The ordering used by maxOrdering.max(items) (or by the comparison-key comparator when key= is given) throws ClassCastException, which MethodLibrary catches and converts to EvalException with the message 'unsupported comparison: T1 <=> T2'.

Source

Thrown at src/main/java/net/starlark/java/eval/MethodLibrary.java:139

    try {
      EvalUtils.addIterator(items); // to prevent keyFn from mutating items
      if (keyFn.isPresent()) {
        try {
          return stream(items)
              .map(value -> ValueWithComparisonKey.make(value, keyFn.get(), thread))
              .max(comparing(ValueWithComparisonKey::getComparisonKey, maxOrdering))
              .get()
              .getValue();
        } catch (ValueWithComparisonKey.KeyCallException ex) {
          Throwables.throwIfInstanceOf(ex.getCause(), EvalException.class);
          Throwables.throwIfInstanceOf(ex.getCause(), InterruptedException.class);
          throw new AssertionError("Got invalid ValueWithComparisonKey.KeyCallException", ex);
        }
      } else {
        return maxOrdering.max(items);
      }
    } catch (ClassCastException ex) {
      throw new EvalException(ex.getMessage()); // e.g. unsupported comparison: int <=> string
    } catch (NoSuchElementException ex) {
      throw new EvalException("expected at least one item", ex);
    } finally {
      EvalUtils.removeIterator(items);
    }
  }

  /**
   * Original value decorated with its comparison key; storing the comparison key alongside the
   * value ensures that we call the comparison key computation function only once per original value
   * (which is important in case the function has side effects).
   */
  private static final class ValueWithComparisonKey {
    private final Object value;
    private final Object comparisonKey;

    private ValueWithComparisonKey(Object value, Object comparisonKey) {
      this.value = value;

View on GitHub (pinned to e6e199d060)

Solutions

  1. Normalize elements (or key results) to one type before calling min/max: max(items, key=lambda x: int(x)).
  2. Make the key= function total: map None/missing sentinels to a sortable fallback such as float("-inf") or "".
  3. Pre-filter or pre-validate the list so all elements share a type.
  4. Catch EvalException around the min()/max() call and degrade gracefully.

Example fix

# before
m = max(vals, key=lambda v: v.score)  # score is None for some items

# after
m = max(vals, key=lambda v: v.score if v.score != None else float("-inf"))
Defensive patterns

Strategy: validation

Validate before calling

# ensure key function yields one uniform type before min/max
keys = [k(v) for v in items]
if len({type(k) for k in keys}) > 1:
    fail("min/max over mixed key types")
m = max(items, key=k)

Type guard

def uniform_types(items):
    t = type(items[0]) if items else None
    return t != None and all(type(i) == t for i in items)

Prevention

When it happens

Trigger: max([1, "a", 2]), min((None, 1)), or max(items, key=lambda x: x.attr) where the key function returns mixed types for different elements (e.g. int for one element, string for another).

Common situations: Computing a max over user-supplied or file-derived data whose types are not guaranteed uniform; a key= function that returns None or an empty string as a sentinel for some elements; aggregating over a list built by concatenating heterogeneous sources.

Related errors


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