{"record":{"id":"fb18c0f650b2c4c7","repo":"astral-sh/ruff","slug":"typeddict-code-generation-should-use-a-typeddict-i","errorCode":null,"errorMessage":"TypedDict code generation should use a TypedDict instance","messagePattern":"TypedDict code generation should use a TypedDict instance","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ty_python_semantic/src/types/class/static_literal.rs","lineNumber":2196,"sourceCode":"                                db,\n                                env,\n                                slots.iter().map(|name| Type::string_literal(db, name)),\n                            );\n                        }\n\n                        let fields = self.fields(db, specialization, field_policy);\n                        let slots = fields.keys().map(|name| Type::string_literal(db, name));\n                        Type::heterogeneous_tuple(db, env, slots)\n                    })\n            }\n            (CodeGeneratorKind::TypedDict, name) => synthesize_typed_dict_method(\n                db,\n                env,\n                instance_ty\n                    .as_typed_dict()\n                    .expect(\"TypedDict code generation should use a TypedDict instance\"),\n                name,\n                || TypedDictFields::Static(self.fields(db, specialization, field_policy)),\n            ),\n            _ => None,\n        }\n    }\n\n    /// Synthesize a `__setattr__` or `__delattr__` view for an ordinary subclass of a frozen\n    /// dataclass.\n    ///\n    /// CPython's generated frozen-dataclass `__setattr__` and `__delattr__` reject all assignments\n    /// and deletions on exact instances of the frozen dataclass, but on subclass instances they\n    /// only reject assignments and deletions of that dataclass's fields before delegating to the\n    /// next method in the MRO.\n    fn own_frozen_dataclass_subclass_method(\n        self,\n        db: &'db dyn Db,\n        env: &ProgramEnvironment<'db>,\n        specialization: Option<Specialization<'db>>,\n        method: FrozenDataclassMethod,","sourceCodeStart":2178,"sourceCodeEnd":2214,"githubUrl":"https://github.com/astral-sh/ruff/blob/15f3fe6b15a5f00172f34b0f542f8ea277f5a586/crates/ty_python_semantic/src/types/class/static_literal.rs#L2178-L2214","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ninstance_ty.as_typed_dict().expect(\"TypedDict code generation should use a TypedDict instance\")\n\n// after\nlet Type::TypedDict(typed_dict) = instance_ty else {\n    return None;\n};","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let member = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    class_member(db, env, instance_ty, name)\n}))\n.ok()\n.flatten(); // treat as 'member not found' on panic","preventionTips":["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."],"tags":["rust","panic","expect","ty","typeddict","classification","invariant"],"backgroundTag":"rust-expect-panic","analyzedSha":"15f3fe6b15a5f00172f34b0f542f8ea277f5a586","analyzedAt":"2026-08-20T16:33:49.445Z","contentChangedAt":"2026-08-20T16:33:49.445Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}