astral-sh/ruff · error

Expected `NamedTuple` definition to be an assignment

Error message

Expected `NamedTuple` definition to be an assignment

What it means

For functional NamedTuple syntax (`P = NamedTuple('P', [('x', int)])` resolved through the typing module), ty defers computing the field spec to `deferred_spec`. It re-reads the definition's value node and asserts the definition is an assignment; the expect fires when a `DynamicNamedTupleAnchor::TypingDefinition` points at a definition with no value expression in the module.

Source

Thrown at crates/ty_python_semantic/src/types/class/named_tuple.rs:453

                    TypeContext::default(),
                )
            })
        }
    }

    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)
    }

View on GitHub (pinned to d1087a4b9e)

Solutions

  1. Guard at anchor construction: only create `TypingDefinition` for definitions whose `.value()` is Some.
  2. Add an mdtest for the exact NamedTuple shape that panicked; run the ty_python_semantic mdtests.
  3. Verify the module is parsed from `definition.python_file(db)`.
  4. Report with a reproducer if stock ty panics.

Example fix

// before
let node = definition.kind(db).value(&module)
    .expect("Expected `NamedTuple` definition to be an assignment");

// after: bail out to the unknown spec instead of panicking
let Some(value) = definition.kind(db).value(&module) 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

When it happens

Trigger: Calling `spec()` on a `DynamicNamedTuple` anchored via `TypingDefinition(definition)` (named_tuple.rs match arm) where `definition.kind(db).value(&module)` is None - a non-assignment definition or one resolved against a different parse.

Common situations: Extending functional-NamedTuple detection (collections vs typing modules, bare call expressions, aliased assignments); refactors of `DefinitionKind`; anchors surviving a reparse in LSP mode.

Related errors


AI-assisted analysis of astral-sh/ruff@d1087a4b9e (2026-08-20). Data as JSON: /api/errors/32b0846d9de36f93. Report an issue: GitHub.