gleam-lang/gleam · error
at least one choice
Error message
at least one choice
What it means
Panic from `choices.pop().expect("at least one choice")` while splitting a decision tree into a final `FallbackCheck::RuntimeCheck`. The algorithm assumes that when there is no catch-all fallback there is at least one remaining choice to promote to the final check; an empty choices vec breaks that invariant.
Solutions
- Guard with an explicit `match choices.pop()` and fall back to `FallbackCheck::CatchAll` when the list is empty instead of panicking.
- Trace why `choices` is empty: add a debug assert before the split logging the choice count for the failing pattern.
- Fix the upstream pruning step so it never removes the last non-catch-all check without producing a catch-all fallback.
Example fix
// before
let (last_check, last_choice) = choices.pop().expect("at least one choice");
// after
let Some((last_check, last_choice)) = choices.pop() else {
let fallback = FallbackCheck::CatchAll { ignored_checks: Vec::new() };
return (choices, splitter.fallback, fallback);
}; Defensive patterns
Strategy: validation
Validate before calling
if choices.is_empty() {
// use catch-all fallback instead of promoting the last choice
} Type guard
fn has_final_choice(choices: &[Choice]) -> bool { !choices.is_empty() } Try / catch
std::panic::catch_unwind(|| split_node(node)).unwrap_or_else(|_| fallback_to_catch_all(node))
Prevention
- Ensure overlap pruning never removes the last choice without emitting a catch-all.
- Assert choice counts are non-empty at node entry during development.
- Add tests for case expressions where all checks get deduplicated away.
When it happens
Trigger: Compiling a case expression where the choice list for a branch node was fully consumed (or was empty from the start) before this split, e.g. after a bug in overlap removal popped every choice, or a branch reached this code with only a catch-all.
Common situations: Compiler developers hit this after modifying how `remaining_choices`/overlaps prune checks, or on pathological case expressions where all checks were deduplicated away.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- bit array compilation with no choice
- check to already be a choice
- empty bit array test
- no unconditional patterns left
- pattern index
AI-assisted analysis of gleam-lang/gleam@49f8762da5 (2026-09-14).
Data as JSON: /api/errors/93936bc32d1bda82.
Report an issue: GitHub.
Appendix: source
Thrown at compiler-core/src/exhaustiveness.rs:2646
// matched on then we want to ignore all of those and clump them
// into a single "fallback" branch to avoid bloating the generated
// tree.
let mut ignored_checks = vec![];
let mut remaining_choices = vec![];
for choice in choices.into_iter() {
if choice.0.is_ignored() {
ignored_checks.push(choice.0);
} else {
remaining_choices.push(choice);
}
}
let fallback_check = FallbackCheck::CatchAll { ignored_checks };
(remaining_choices, splitter.fallback, fallback_check)
} else {
// Otherwise we just use the last check as the final one that
// can be outright skipped.
let (last_check, last_choice) = choices.pop().expect("at least one choice");
let fallback_check = FallbackCheck::RuntimeCheck {
check: Box::new(last_check),
};
(choices, last_choice, fallback_check)
};
let choices = self.compile_all_choices(choices);
let fallback = Box::new(self.compile(fallback));
Decision::Switch {
var,
choices,
fallback,
fallback_check,
}
}
fn compile_all_choices(
&mut self,View on GitHub (pinned to 49f8762da5)