astral-sh/ruff · error
Expected `NamedTuple` definition r.h.s. to be a call express
Error message
Expected `NamedTuple` definition r.h.s. to be a call expression
What it means
The second assertion in `deferred_spec`: after confirming the definition is an assignment, the value must be the `NamedTuple(...)` call expression, because the code then reads `node.arguments.args[1]` (the fields list). The `as_call_expr().expect(...)` fires when the RHS is not a call.
Source
Thrown at crates/ty_python_semantic/src/types/class/named_tuple.rs:455
})
}
}
fn spec(self, db: &'db dyn Db) -> NamedTupleSpec<'db> {
#[salsa::tracked(
returns(copy),
cycle_initial=|db, _, _| NamedTupleSpec::unknown(db),
heap_size=ruff_memory_usage::heap_size
)]
fn deferred_spec<'db>(db: &'db dyn Db, definition: Definition<'db>) -> NamedTupleSpec<'db> {
let python_file = definition.python_file(db);
let module = parsed_module(db, python_file).load(db);
let node = definition
.kind(db)
.value(&module)
.expect("Expected `NamedTuple` definition to be an assignment")
.as_call_expr()
.expect("Expected `NamedTuple` definition r.h.s. to be a call expression");
match definition_expression_type(db, definition, &node.arguments.args[1]) {
Type::KnownInstance(KnownInstanceType::NamedTupleSpec(spec)) => spec,
_ => NamedTupleSpec::unknown(db),
}
}
match self.anchor(db) {
DynamicNamedTupleAnchor::CollectionsDefinition { spec, .. }
| DynamicNamedTupleAnchor::ScopeOffset { spec, .. } => *spec,
DynamicNamedTupleAnchor::TypingDefinition(definition) => deferred_spec(db, *definition),
}
}
fn fields(self, db: &'db dyn Db) -> &'db [NamedTupleField<'db>] {
self.spec(db).fields(db)
}
/// Returns the field declared directly on this dynamic named tuple, if any.View on GitHub (pinned to d1087a4b9e)
Solutions
- Anchor only after the RHS matches a `NamedTuple`/`collections.namedtuple` call with the expected arity.
- Use `as_call_expr()` with a let-else returning `NamedTupleSpec::unknown(db)` so malformed anchors degrade instead of panicking.
- Add mdtests covering `P = NamedTuple`, calls missing the fields argument, and keyword forms.
- Report upstream if reachable without source changes.
Example fix
// before
.as_call_expr()
.expect("Expected `NamedTuple` definition r.h.s. to be a call expression");
// after
let Some(call) = value.as_call_expr() else {
return NamedTupleSpec::unknown(db);
}; Defensive patterns
Strategy: try-catch
Try / catch
let spec = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
named_tuple.spec(db)
}))
.unwrap_or_else(|_| NamedTupleSpec::unknown(db)); Prevention
- Prefer the two-argument functional form `P = NamedTuple('P', [...])` in reproducers; degenerate forms stress adjacent panics (args[1] indexing).
- Report bare-alias (`P = NamedTuple`) or rebound cases with a minimal file.
- Run the NamedTuple mdtests after touching detection code.
When it happens
Trigger: Resolving a typing-module NamedTuple spec where the anchored definition's value is not an `ast::ExprCall` - e.g. `P = NamedTuple` (bare alias) or a rebinding got anchored by mistake. Note the same code also indexes `args[1]`, so zero/one-argument calls are a neighboring crash risk.
Common situations: Broadening NamedTuple detection to bare aliases or star-call forms; refactors that anchor definitions before validating the call shape; stale anchors after edits.
Related errors
- Definition value should be a call expression
- Expected `NamedTuple` definition to be an assignment
- storage-free owned constraint sets must have terminal roots
- dynamic class definitions should only be used for assignment
- anchor should not be NodeIndex::NONE
AI-assisted analysis of astral-sh/ruff@d1087a4b9e (2026-08-20).
Data as JSON: /api/errors/af1938eba7e93daa.
Report an issue: GitHub.