gleam-lang/gleam · error

Expected t1 to be a direct link but found

Error message

Expected t1 to be a direct link but found: {t1:?}

What it means

A test-assertion panic in the Gleam type environment's unification test helpers (`environment.rs:1248`). The helper expects `t1` to be a `TypeVar::Link` directly pointing at a `TypeVar::Unbound` with a specific id (the result of unifying an unbound variable). If the structure differs — wrong shape, extra indirection, or a different id — it panics with a debug dump of `t1`.

Solutions

  1. Read the panic's `{t1:?}` dump to see the actual type structure and compare with the expected `Link(Unbound(id))` shape.
  2. Fix the unification code under test so unifying an unbound variable links it directly to the other variable, preserving the expected id.
  3. Update the test's expected id (`expect_id`) or the constructed input types if the test setup, not the unifier, is wrong.
  4. If `Type`/`TypeVar` variants were refactored, update the helper's pattern match to the new representation.

Example fix

// before (test asserting against the wrong id)
assert_unified(t1, unbound(7));

// after
assert_unified(t1, unbound(3)); // match the id actually produced by unification
Defensive patterns

Strategy: type-guard

Validate before calling

// Rust: verify the expected shape before asserting the id
fn is_direct_link_to_unbound(t: &Type) -> Option<u64> {
    match t {
        Type::Var { type_ } => match &*type_.borrow() {
            TypeVar::Link { type_: link } => match link.deref() {
                Type::Var { type_ } => match &*type_.borrow() {
                    TypeVar::Unbound { id } => Some(*id),
                    _ => None,
                },
                _ => None,
            },
            _ => None,
        },
        _ => None,
    }
}

Type guard

fn is_direct_link_to_unbound(t: &Type) -> Option<u64> { /* unwrap Link->Unbound id, else None */ }

Try / catch

// Panics abort tests; prefer a descriptive assertion
match is_direct_link_to_unbound(t1) {
    Some(id) => assert_eq!(id, expect_id, "unbound id mismatch"),
    None => panic!("t1 is not a direct link to unbound: {t1:?}"),
}

Prevention

When it happens

Trigger: Running environment/unification unit tests where `assert_unified` (the helper containing this panic) receives a `t1` that is not a direct `Link -> Unbound` chain, or whose unbound id does not match `expect_id`.

Common situations: Compiler contributors changing unification logic so variables end up linked indirectly or not at all; tests updated to construct types differently (e.g. passing a named type instead of a var); refactors of `Type`/`TypeVar` shapes that break the pattern the helper matches on.

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 gleam-lang/gleam@15b07c7830 (2026-09-14). Data as JSON: /api/errors/d02225215b0edf47. Report an issue: GitHub.

Appendix: source

Thrown at compiler-core/src/type_/environment.rs:1248

        assert!(unify(t1.clone(), t2).is_ok());
        assert_direct_link_to_unbound(t1.deref(), 2);

        // And this would add a link to the var of the new link
        // As unify is recursive, this eventually cause a stack overflow
        let t2 = unbound(3);
        assert!(unify(t1.clone(), t2).is_ok());
        assert_direct_link_to_unbound(t1.deref(), 3);
    }

    fn assert_direct_link_to_unbound(t1: &Type, expect_id: u64) {
        if let Type::Var { type_: var } = t1
            && let TypeVar::Link { type_: link } = var.borrow().deref()
            && let Type::Var { type_: var } = link.deref()
            && let TypeVar::Unbound { id } = var.borrow().deref()
        {
            assert_eq!(*id, expect_id, "Expected unbound id to be unified")
        } else {
            panic!("Expected t1 to be a direct link but found: {t1:?}")
        }
    }

    fn unbound(id: u64) -> Arc<Type> {
        Arc::new(Type::Var {
            type_: Arc::new(RefCell::new(TypeVar::Unbound { id })),
        })
    }
}

View on GitHub (pinned to 15b07c7830)