astral-sh/ruff · error

TypedDictParams should be available for CodeGeneratorKind::T

Error message

TypedDictParams should be available for CodeGeneratorKind::TypedDict

What it means

`own_fields` reads the `total=` default via `typed_dict_params`, which returns Some only when `self.is_typed_dict(db)` holds (a TypedDict detected in the MRO). The expect fires when `own_fields` was called with `field_policy == CodeGeneratorKind::TypedDict` for a class that `is_typed_dict` does not recognize - two classification queries disagreeing about the same class.

Source

Thrown at crates/ty_python_semantic/src/types/class/static_literal.rs:2651

        let table = place_table(db, class_body_scope);

        let use_def = use_def_map(db, class_body_scope);

        // `own_fields(..., NamedTuple)` is called while constructing the class's MRO because the
        // field types determine the synthesized tuple base. `typed_dict_params` also queries the
        // class's MRO, so only read the `total` default when collecting `TypedDict` fields.
        let typed_dict_fields_are_required_by_default =
            if field_policy == CodeGeneratorKind::TypedDict {
                self.typed_dict_params(db)
                    .expect("TypedDictParams should be available for CodeGeneratorKind::TypedDict")
                    .contains(TypedDictParams::TOTAL)
            } else {
                false
            };
        let dataclass_kw_only_default = field_policy.is_dataclass_like().then(|| {
            let own_field_policy =
                CodeGeneratorKind::from_class(db, self.into()).unwrap_or(field_policy);
            self.has_dataclass_param(db, own_field_policy, DataclassFlags::KW_ONLY)
        });
        let mut kw_only_sentinel_field_seen = false;
        let mut field_declarations = Vec::new();

        for (symbol_id, declarations) in use_def.all_end_of_scope_symbol_declarations() {
            // Here, we exclude all declarations that are not annotated assignments. We need this because
            // things like function definitions and nested classes would otherwise be considered dataclass
            // fields. The check is too broad in the sense that it also excludes (weird) constructs where
            // a symbol would have multiple declarations, one of which is an annotated assignment. If we
            // want to improve this, we could instead pass a definition-kind filter to the use-def map
            // query, or to the `symbol_from_declarations` call below. Doing so would potentially require
            // us to generate a union of `__init__` methods.
            if declarations.clone().any_reachable(db, |declaration| {
                declaration.is_defined_and(|declaration| {
                    !matches!(
                        declaration.kind(db),
                        DefinitionKind::AnnotatedAssignment(..)
                    )

View on GitHub (pinned to 15f3fe6b15)

Solutions

  1. Derive both `CodeGeneratorKind::from_class` and `is_typed_dict`/`typed_dict_params` from a single classification query so they cannot disagree.
  2. Replace the expect with a let-else treating a missing params as non-total, and assert the classification invariant in a debug build.
  3. Add an mdtest reproducing the class shape (e.g. indirect TypedDict inheritance) and run the ty_python_semantic mdtests.
  4. Report upstream with the reproducer if stock ty is affected.

Example fix

// before
self.typed_dict_params(db).expect("TypedDictParams should be available for CodeGeneratorKind::TypedDict")

// after
let Some(params) = self.typed_dict_params(db) else {
    debug_assert!(false, "TypedDict classification mismatch");
    return Box::default();
};
Defensive patterns

Strategy: try-catch

Try / catch

let fields = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    class.fields(db, specialization, field_policy)
}))
.unwrap_or_else(|_| Box::default());

Prevention

When it happens

Trigger: Computing fields for a class whose code generator kind is TypedDict while `typed_dict_params` returns None because `is_typed_dict(db)` is false - e.g. TypedDict subclass chains, typing/typing_extensions mixes, or a classification refactor that updated one query but not the other.

Common situations: Modifying TypedDict detection in ty (adding decorator-based or dynamic TypedDicts); usually triggered by mdtests exercising exotic class statements shortly after such a change.

Related errors


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