pydantic/monty · error
free_var '{name_str}' not found in enclosing scope's cells,
Error message
free_var '{name_str}' not found in enclosing scope's cells, comprehension targets, or globals What it means
During bytecode preparation, a name captured as a free variable by a child scope must be resolvable in the enclosing scope as a cell, a comprehension target, or a global. If the interner lookup fails in all three places, preparation panics — this indicates a scope-analysis bug where a capture was predicted but the binding site was never recorded.
Source
Thrown at crates/monty/src/prepare.rs:397
for scope in self.comp_name_scopes.iter_mut().rev() {
if let Some(binding) = scope.get_mut(&name_id) {
binding.captured = true;
return CaptureSource::CompVar(binding.slot);
}
}
if let PrepareState::Function(state) = &self.state {
if let Some(&slot) = state.cell_var_map.get(&name_id) {
return CaptureSource::Namespace(slot);
}
if let Some(&slot) = state.free_var_map.get(&name_id) {
return CaptureSource::Namespace(slot);
}
}
if let Some(slot) = self.globals.globals.get(name_id) {
return CaptureSource::Namespace(slot);
}
let name_str = self.interner.get_str(name_id);
panic!("free_var '{name_str}' not found in enclosing scope's cells, comprehension targets, or globals");
}
/// Inner-to-outer scope hand-off when a just-prepared child scope reports a
/// capture that wasn't predicted by scope analysis.
///
/// The recursive [`collect_referenced_names_from_node`] pass below
/// pre-populates most transitively captured names before the body is
/// walked, but its `ClassDef` arm collects only decorators, nothing from the
/// class body — so for a capture chain that flows through a class body (a
/// method capturing an enclosing function's local), this bubble-up is
/// **load-bearing**, not a safety net: it is the only mechanism that
/// registers the intermediate scopes' cells. It classifies each late
/// discovery:
///
/// - Already a cell or free var here → nothing to do.
/// - Bound locally (params or body-assigned) → register as a cell var here.
/// - Bound in an ancestor scope (`enclosing_locals`) → register as a
/// pass-through free var here so the cell propagates upward.View on GitHub (pinned to adc986b362)
Solutions
- Fix the scope-analysis pass so names bound nowhere are reported as compile errors rather than predicted captures.
- Verify the enclosing-scope cell map and comprehension target lists are populated before build_free_var_slots runs.
- Reduce the failing Python snippet and check whether CPython raises a SyntaxError/NameError for it — match that behavior instead of panicking.
Example fix
// before
// scope analysis marks 'x' captured but never records its binding
panic!("free_var '{name_str}' not found...");
// after
// emit a proper compile error instead
return Err(PrepareError::unbound_free_variable(name_id, position)); Defensive patterns
Strategy: validation
Validate before calling
// pre-check capture resolution in scope analysis (Rust, before finalize)
if !state.cell_var_map.contains_key(&name_id)
&& !state.comprehension_targets.contains(&name_id)
&& !globals.contains_key(&name_id) {
return Err(PrepareError::unbound_free_variable(name_id));
} Prevention
- Compare capture behavior against CPython for tricky closures
- Add compile-error tests for names bound nowhere instead of panics
- Keep capture prediction and binding registration in one pass
When it happens
Trigger: Compiling Python code where a nested function/comprehension references a free variable that scope analysis flagged as captured but that is not bound anywhere in an enclosing scope, e.g. an inner function reading a name only bound in a sibling scope or deleted before preparation.
Common situations: Contributors modifying the scope-analysis pass (collect_referenced_names_from_node / build_free_var_slots); test code with unusual closure patterns exercising capture prediction edge cases.
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
- bubble-up captured '{name_str}' that is bound nowhere — scop
- class member '{member_name}' missing from class-body locals
- cycle values are not hashable
- {} python values are not hashable
- gather commit frame id is not a GatherFuture
AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13).
Data as JSON: /api/errors/81feafa80637637d.
Report an issue: GitHub.