astral-sh/ruff · error

TypedDict code generation should use a TypedDict instance

Error message

TypedDict code generation should use a TypedDict instance

What it means

When ty synthesizes methods for a class whose code generator is TypedDict (e.g. `__init__`, `__contains__` and friends), it downcasts the class's instance type to a TypedDict before calling `synthesize_typed_dict_method`. The expect fires when member synthesis reached the `(CodeGeneratorKind::TypedDict, name)` match arm while `instance_ty` is not a TypedDict - the class's classification and its instance type disagree.

Source

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

                                db,
                                env,
                                slots.iter().map(|name| Type::string_literal(db, name)),
                            );
                        }

                        let fields = self.fields(db, specialization, field_policy);
                        let slots = fields.keys().map(|name| Type::string_literal(db, name));
                        Type::heterogeneous_tuple(db, env, slots)
                    })
            }
            (CodeGeneratorKind::TypedDict, name) => synthesize_typed_dict_method(
                db,
                env,
                instance_ty
                    .as_typed_dict()
                    .expect("TypedDict code generation should use a TypedDict instance"),
                name,
                || TypedDictFields::Static(self.fields(db, specialization, field_policy)),
            ),
            _ => None,
        }
    }

    /// Synthesize a `__setattr__` or `__delattr__` view for an ordinary subclass of a frozen
    /// dataclass.
    ///
    /// CPython's generated frozen-dataclass `__setattr__` and `__delattr__` reject all assignments
    /// and deletions on exact instances of the frozen dataclass, but on subclass instances they
    /// only reject assignments and deletions of that dataclass's fields before delegating to the
    /// next method in the MRO.
    fn own_frozen_dataclass_subclass_method(
        self,
        db: &'db dyn Db,
        env: &ProgramEnvironment<'db>,
        specialization: Option<Specialization<'db>>,
        method: FrozenDataclassMethod,

View on GitHub (pinned to 15f3fe6b15)

Solutions

  1. Make classification consistent: the same query that yields `CodeGeneratorKind::TypedDict` must also produce a `Type::TypedDict` instance for the class.
  2. Replace the expect with a let-else returning None so a mismatch skips synthesis instead of crashing, then fix the classification bug the let-else exposes.
  3. Add an mdtest for the class shape that panicked (subclass chains through dynamic TypedDicts are the usual trigger).
  4. Run the full ty_python_semantic suite after the change.

Example fix

// before
instance_ty.as_typed_dict().expect("TypedDict code generation should use a TypedDict instance")

// after
let Type::TypedDict(typed_dict) = instance_ty else {
    return None;
};
Defensive patterns

Strategy: try-catch

Try / catch

let member = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    class_member(db, env, instance_ty, name)
}))
.ok()
.flatten(); // treat as 'member not found' on panic

Prevention

When it happens

Trigger: Attribute access on an instance of a class that was classified with TypedDict code generation but whose instance type is a plain ClassType - e.g. inheriting from a dynamic TypedDict without re-specialization, or a refactor that changed TypedDict classification without changing instance construction.

Common situations: Contributions touching TypedDict classification (`code_generator`, `is_typed_dict`), dynamic TypedDict subclasses, or instance-type construction; typically seen in mdtest/LSP runs right after such a change rather than in released builds.

Related errors


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