pydantic/monty · error

validated gather awaitable is heap-backed

Error message

validated gather awaitable is heap-backed

What it means

`gather` validates its awaitable arguments earlier (rejecting non-heap objects), so `into_ref_id` is expected to succeed; this `expect` panics if a validated argument is not a heap reference. It encodes the invariant 'validation already ran, so every remaining item is a Ref'.

Solutions

  1. Check the validation block above this line covers every accepted awaitable type.
  2. Convert the expect into an explicit internal error if new value kinds are added.
  3. Report as a bug with the gather call that triggered it.

Example fix

// before
.map(|arg| arg.into_ref_id().expect("validated gather awaitable is heap-backed"))
// after
.map(|arg| arg.into_ref_id().map_err(|_| VmError::internal("gather awaitable not heap-backed")))
.collect::<RunResult<Vec<_>>>()?;
Defensive patterns

Strategy: try-catch

Validate before calling

# In Monty Python code, only pass real awaitables to asyncio.gather
assert all(hasattr(a, '__await__') for a in awaitables)

Try / catch

// Host-side (Rust embedding monty)
match result {
    Err(MontyError::Panic(msg)) if msg.contains("heap-backed") => log_internal_bug(msg),
    other => other?,
}

Prevention

When it happens

Trigger: Calling `asyncio.gather` in Monty code with an awaitable whose underlying `Value` is not a heap-backed `Value::Ref` but somehow passed the preceding validation — an interpreter bug, not user-reachable through normal Python code.

Common situations: Only from internal validation/refactoring drift: someone changes or reorders the validation loop and misses a non-ref case.

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/ba75046859ded46b. Report an issue: GitHub.

Appendix: source

Thrown at crates/monty/src/modules/asyncio.rs:120

    // so an invalid later argument needs no raw-HeapId rollback.
    for arg in awaitables.iter() {
        if !matches!(
            arg,
            Value::Ref(id)
                if matches!(
                    vm.heap.get(*id),
                    HeapData::Coroutine(_) | HeapData::ExternalFuture(_) | HeapData::GatherFuture(_)
                )
        ) {
            return Err(ExcType::type_error(
                "An asyncio.Future, a coroutine or an awaitable is required",
            ));
        }
    }

    let items = awaitables
        .drain(..)
        .map(|arg| arg.into_ref_id().expect("validated gather awaitable is heap-backed"))
        .collect();
    let gather_future = GatherFuture::new(items);
    let id = vm.heap.allocate(HeapData::GatherFuture(Box::new(gather_future)));
    Ok(Value::Ref(id))
}

/// `asyncio.gather(*awaitables)` — variadic positional, no kwargs accepted.
///
/// `kwargs_not_supported_yet` rejects any kwarg with the macro's
/// "not yet implemented" `NotImplementedError`, replacing the previous
/// `TypeError: gather() takes no keyword arguments` from `into_pos_only`.
/// When CPython's `return_exceptions=False` is wired up this becomes a
/// regular `kw_only` slot and the flag goes away.
#[derive(FromArgs)]
#[from_args(name = "gather", kwargs_not_supported_yet)]
struct GatherArgs {
    #[from_args(varargs)]
    awaitables: Vec<Value>,

View on GitHub (pinned to adc986b362)