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

  1. Fix the scope-analysis pass so names bound nowhere are reported as compile errors rather than predicted captures.
  2. Verify the enclosing-scope cell map and comprehension target lists are populated before build_free_var_slots runs.
  3. 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

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


AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13). Data as JSON: /api/errors/81feafa80637637d. Report an issue: GitHub.