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
- Read the panic's `{t1:?}` dump to see the actual type structure and compare with the expected `Link(Unbound(id))` shape.
- Fix the unification code under test so unifying an unbound variable links it directly to the other variable, preserving the expected id.
- Update the test's expected id (`expect_id`) or the constructed input types if the test setup, not the unifier, is wrong.
- 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
- Use the `{t1:?}` debug dump in the panic message to diagnose mismatches quickly.
- Keep test helper pattern matches in sync whenever `Type`/`TypeVar` variants change.
- After changing unification behaviour, run the environment test module to catch shape regressions early.
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)