BoundaryML/baml · error

interface `{iface_wire}` declares a non-runtime type: {e:?}

Error message

interface `{iface_wire}` declares a non-runtime type: {e:?}

What it means

While building an interface definition, each declared type is lowered to HIR, hole-checked, then converted to a runtime type via `baml_type::lower_to_runtime`. A `None`/`Err` result means the interface declares a type with no runtime representation (e.g., a purely compile-time constraint type such as a bare type-variable or function type). The code treats that as impossible for a valid interface and calls `unreachable!`, crashing with this message.

Source

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

        .with_frame(interface_frame_params.clone())
        .with_bounds(baml_compiler2_hir_ty::lower::interface_scope_bounds(
            db, iface_loc,
        ));

    // Lower a type ref in `ctx` and narrow to a runtime type. Diagnostics are
    // not collected: emit only runs on a checked program, so the declaration
    // is already validated. `lower_to_runtime` rejects only the
    // error-recovery sentinels, which a checked program cannot contain, so a
    // failure means a compiler bug and must not be papered over by dropping
    // the entry (that would renumber positional arguments).
    let lower_rt = |ctx: &baml_compiler2_hir_ty::lower::LowerCtx<'_>,
                    store: &TypeRefStore,
                    id: TypeRefId|
     -> bex_vm_types::RuntimeTy {
        let ty = baml_compiler2_hir_ty::lower::reject_holes(&ctx.lower_type_ref(store, id));
        let runtime = baml_type::lower_to_runtime(&ty, resolved.aliases)
            .unwrap_or_else(|e| {
                unreachable!("interface `{iface_wire}` declares a non-runtime type: {e:?}")
            })
            .map_heads(&mut |decl| resolved.wire(decl));
        bex_vm_types::anchor_runtime_ty(&runtime)
    };
    // Lower an interface bound / `requires` target / associated-type bound.
    // These are constraint heads: hir keeps written pins only (no eager
    // default realization). A target that is not an interface was rejected
    // upstream (E0145 / E0133) and yields `None`.
    let lower_iface = |ctx: &baml_compiler2_hir_ty::lower::LowerCtx<'_>,
                       store: &TypeRefStore,
                       id: TypeRefId|
     -> Option<bex_vm_types::RuntimeInterface> {
        // ConstraintHead, per the contract above: the default (existential)
        // position eagerly realizes omitted associated defaults and fills
        // the rest with Error sentinels — a bound like `type A extends
        // Iface` with unpinned associated types would panic `to_runtime`.
        let lowered = baml_compiler2_hir_ty::lower::reject_holes(&ctx.lower_type_ref_at(
            store,

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Change the interface declaration so every member type has a concrete runtime representation (replace type variables/function types with supported types)
  2. Ensure generic parameters are declared on the interface so heads resolve to runtime types
  3. If the type should be representable, fix `baml_type::lower_to_runtime` to handle it
  4. Replace the `unreachable!` with a proper diagnostic error if a user-facing case is found

Example fix

// before
interface Foo {
  fn op(cb: fn(int) -> string)
}
// after: use a runtime-representable shape
interface Foo {
  fn op(cb: (int) => string)  // or whatever callable type the runtime supports
}
Defensive patterns

Strategy: validation

Validate before calling

// before declaring the interface, ensure member types are runtime-representable
fn interface_types_are_runtime_representable(iface: &InterfaceDecl) -> Result<(), TypeError> {
    iface.fields.iter().chain(iface.methods.iter())
        .map(|m| lower_to_runtime(&lower(m.ty)?, &aliases))
        .collect::<Option<Vec<_>>>()
        .ok_or(TypeError::NonRuntimeTypeInInterface(iface.name.clone()))
}

Try / catch

std::panic::catch_unwind(|| build_interface_def(resolved, store))
    .unwrap_or_else(|_| report_compiler_bug("interface has non-runtime type"));

Prevention

When it happens

Trigger: Declaring an `interface` whose member/associated type lowers to a non-runtime-representable type (e.g., an unresolved generic head, a function type, or a type with holes that passed hole-checking but cannot be lowered to a runtime type).

Common situations: Writing an interface with an exotic type annotation (generics misuse, function-typed fields) that the type checker admits but the runtime lowering rejects; compiler regressions in `lower_to_runtime` after adding new `Ty` variants.

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/b2eb8d9bd620ba32. Report an issue: GitHub.