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
- Make classification consistent: the same query that yields `CodeGeneratorKind::TypedDict` must also produce a `Type::TypedDict` instance for the class.
- 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.
- Add an mdtest for the class shape that panicked (subclass chains through dynamic TypedDicts are the usual trigger).
- 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
- Avoid exotic TypedDict inheritance (dynamic TypedDicts as bases of class statements) until a fix ships.
- When filing issues, attach the full class hierarchy of the repro, not just the access site.
- Contributors: treat any hit as a classification bug to fix, not to guard.
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
- TypedDictParams should be available for CodeGeneratorKind::T
- Expected `NamedTuple` definition r.h.s. to be a call express
- Will return Some() when called on class literal
- storage-free owned constraint sets must have terminal roots
- dynamic class definitions should only be used for assignment
AI-assisted analysis of astral-sh/ruff@15f3fe6b15 (2026-08-20).
Data as JSON: /api/errors/fb18c0f650b2c4c7.
Report an issue: GitHub.