BoundaryML/baml · error

interface index {} is not an Object::Interface

Error message

interface index {} is not an Object::Interface

What it means

This is a Rust `unreachable!()` panic inside `apply_interface_default_backfill` in baml_compiler2_emit. The compiler recorded an `InterfaceDefaultBackfill` entry pointing at an object-pool slot that, when written back, no longer holds an `Object::Interface`. It means the object pool was mutated or the recorded `ObjectIndex` was stale/reused between the point the backfill entry was created (in `build_packages`) and the point it was applied. The library panics because this is an internal invariant that should be impossible; it is never a user-input error.

Source

Thrown at baml_language/crates/baml_compiler2_emit/src/lib.rs:1228

        pkg.canonicalize_impl_rules();
    }
    interface_default_backfill
}

/// One `InterfaceMethodDef::default` slot `build_packages` could not fill when
/// the interface was pooled (functions are pooled later), keyed by the pooled
/// interface and the method's name.
struct InterfaceDefaultBackfill {
    interface: ObjectIndex,
    method: Name,
    default: ObjectIndex,
}

/// Write each back-filled default into its pooled `Object::Interface`.
fn apply_interface_default_backfill(program: &mut Program, backfill: &[InterfaceDefaultBackfill]) {
    for entry in backfill {
        let Object::Interface(iface) = &mut program.objects[entry.interface] else {
            unreachable!(
                "interface index {} is not an Object::Interface",
                entry.interface.raw()
            )
        };
        let method = iface
            .methods
            .iter_mut()
            .find(|method| method.name == entry.method)
            .unwrap_or_else(|| {
                unreachable!(
                    "interface `{}` declares no method `{}` for its default",
                    iface.name, entry.method
                )
            });
        method.default = Some(entry.default);
    }
}

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Audit the code path that pushes to `interface_default_backfill` and confirm the recorded ObjectIndex is created after final pool assignment and never invalidated.
  2. Check whether any pass between backfill entry creation and `apply_interface_default_backfill` mutates `program.objects` (push, remove, or overwrite slots).
  3. Ensure `apply_interface_default_backfill` runs in the same compilation run/ordering as `generate_impl` and pool construction; no pool rebuild may occur in between.
  4. If triggered by an incremental/recompilation path, verify pooled object indices are rebuilt consistently and backfill entries are cleared when the pool is rebuilt.

Example fix

// before: backfill entries recorded before final pooling, indices go stale
let backfill = collect_backfill(&half_built_pool);
rebuild_pool(program);
apply_interface_default_backfill(program, &backfill);
// after: record backfill entries only after the pool is final
let backfill = collect_backfill(program); // pool final; indices stable
apply_interface_default_backfill(program, &backfill);
Defensive patterns

Strategy: validation

Validate before calling

// User code cannot pre-validate a compiler-internal invariant. If embedding the compiler,
// verify the pool slot before applying backfill:
fn backfill_target_is_interface(program: &Program, idx: ObjectIndex) -> bool {
    matches!(program.objects.get(idx), Some(Object::Interface(_)))
}

Type guard

fn is_interface(obj: &Object) -> bool {
    matches!(obj, Object::Interface(_))
}

Try / catch

// Rust panics are not catchable in user BAML code; when embedding the compiler, isolate
// the compile call and recover:
let result = std::panic::catch_unwind(|| compile(&source));
match result {
    Ok(artifacts) => artifacts,
    Err(_) => report_internal_compiler_error(),
}

Prevention

When it happens

Trigger: Only hit during compilation when the deferred interface-method-default backfill pass runs with an `InterfaceDefaultBackfill` whose `interface: ObjectIndex` resolves to an object that is not `Object::Interface` — e.g. the pool slot was overwritten by another pooled object (class/enum/type-alias) or the index was built against a different pool ordering.

Common situations: Compiler development, not end-user BAML code: refactoring object pooling in baml_compiler2_emit, changing pooled-object ordering between pass phases, or reusing/shuffling ObjectIndex slots without invalidating backfill entries.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/054ab10b334ad12b. Report an issue: GitHub.