gleam-lang/gleam · error
check to already be a choice
Error message
check to already be a choice
What it means
Panic from `self.choices.get_mut(*index).expect("check to already be a choice")` while folding a branch into overlapping earlier choices. The `overlapping` indices come from `check_overlaps`, which is documented to only report indices that already exist in `self.choices`; if an index is missing, the overlap bookkeeping is broken.
Solutions
- Recompute overlaps immediately before the merge loop so indices cannot go stale.
- Check any code between `check_overlaps` and this loop that mutates `self.choices` (push/pop/retain).
- Soften to a skip-with-warning (`continue` when lookup fails) while debugging, then fix the root cause in `check_overlaps`.
Example fix
// before
let (overlapping_check, branches) = self.choices.get_mut(*index).expect("check to already be a choice");
// after
let Some((overlapping_check, branches)) = self.choices.get_mut(*index) else {
panic!("overlap index {} not in {} choices", index, self.choices.len())
}; Defensive patterns
Strategy: validation
Validate before calling
for index in &overlapping {
assert!((*index as usize) < self.choices.len(), "stale overlap index");
} Type guard
fn choice_at(choices: &[(RuntimeCheck, Branches)], i: usize) -> Option<&(RuntimeCheck, Branches)> {
choices.get(i)
} Try / catch
std::panic::catch_unwind(|| merge_overlaps(overlapping)).unwrap_or_else(|_| recompute_overlaps_and_retry())
Prevention
- Compute overlaps immediately before merging; never cache indices across mutations.
- Audit any code that pushes/pops self.choices between check_overlaps and the merge loop.
- Use retain/filter APIs carefully since they invalidate positional indices.
When it happens
Trigger: Compiling a pattern that `check_overlaps` reported as overlapping some existing choice, then `self.choices.get_mut(index)` failing — e.g. choices were removed/reordered between overlap computation and this loop, or `check_overlaps` returned an index into the wrong vec.
Common situations: Hit by Gleam compiler contributors after changes to overlap detection, guard fall-through handling, or when choices are pruned before the merge step.
Related errors
- at least one choice
- bit array compilation with no 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/b7055abb22b35d48.
Report an issue: GitHub.
Appendix: source
Thrown at compiler-core/src/exhaustiveness.rs:3139
.to_runtime_check_kind()
.expect("no unconditional patterns left");
let CheckOverlaps {
overlapping,
needs_new_choice,
} = self.check_overlaps(&kind);
// The branch is relevant to every existing choice its check overlaps
// with, so we add it (together with any newly discovered checks) to all
// of those paths. For a string prefix this also includes any longer
// prefixes and exact literals that start with it. When one of those
// matched but its branch then failed, control falls through to this
// shorter prefix.
for index in overlapping.iter() {
let (overlapping_check, branches) = self
.choices
.get_mut(*index)
.expect("check to already be a choice");
let mut branch = branch.clone();
for new_check in compiler.new_checks(&pattern_check.var, &pattern, overlapping_check) {
branch.add_check(new_check);
}
branches.push_back(branch);
}
// Unless the check is fully subsumed by an existing choice, we also add
// it as a new path we might have to go down to in the decision tree.
// A shorter prefix still needs its own choice even when it overlaps
// earlier longer prefixes, so it can match values outside those.
if needs_new_choice {
self.save_index_of_new_choice(kind.clone());
let check = compiler.fresh_runtime_check(kind, branch_mode);
for new_check in compiler.new_checks(&pattern_check.var, &pattern, &check) {
branch.add_check(new_check);View on GitHub (pinned to 49f8762da5)