{"record":{"id":"b4efd34a45669c6e","repo":"astral-sh/ruff","slug":"typeddictparams-should-be-available-for-codegenera","errorCode":null,"errorMessage":"TypedDictParams should be available for CodeGeneratorKind::TypedDict","messagePattern":"TypedDictParams should be available for CodeGeneratorKind::TypedDict","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ty_python_semantic/src/types/class/static_literal.rs","lineNumber":2651,"sourceCode":"        let table = place_table(db, class_body_scope);\n\n        let use_def = use_def_map(db, class_body_scope);\n\n        // `own_fields(..., NamedTuple)` is called while constructing the class's MRO because the\n        // field types determine the synthesized tuple base. `typed_dict_params` also queries the\n        // class's MRO, so only read the `total` default when collecting `TypedDict` fields.\n        let typed_dict_fields_are_required_by_default =\n            if field_policy == CodeGeneratorKind::TypedDict {\n                self.typed_dict_params(db)\n                    .expect(\"TypedDictParams should be available for CodeGeneratorKind::TypedDict\")\n                    .contains(TypedDictParams::TOTAL)\n            } else {\n                false\n            };\n        let dataclass_kw_only_default = field_policy.is_dataclass_like().then(|| {\n            let own_field_policy =\n                CodeGeneratorKind::from_class(db, self.into()).unwrap_or(field_policy);\n            self.has_dataclass_param(db, own_field_policy, DataclassFlags::KW_ONLY)\n        });\n        let mut kw_only_sentinel_field_seen = false;\n        let mut field_declarations = Vec::new();\n\n        for (symbol_id, declarations) in use_def.all_end_of_scope_symbol_declarations() {\n            // Here, we exclude all declarations that are not annotated assignments. We need this because\n            // things like function definitions and nested classes would otherwise be considered dataclass\n            // fields. The check is too broad in the sense that it also excludes (weird) constructs where\n            // a symbol would have multiple declarations, one of which is an annotated assignment. If we\n            // want to improve this, we could instead pass a definition-kind filter to the use-def map\n            // query, or to the `symbol_from_declarations` call below. Doing so would potentially require\n            // us to generate a union of `__init__` methods.\n            if declarations.clone().any_reachable(db, |declaration| {\n                declaration.is_defined_and(|declaration| {\n                    !matches!(\n                        declaration.kind(db),\n                        DefinitionKind::AnnotatedAssignment(..)\n                    )","sourceCodeStart":2633,"sourceCodeEnd":2669,"githubUrl":"https://github.com/astral-sh/ruff/blob/15f3fe6b15a5f00172f34b0f542f8ea277f5a586/crates/ty_python_semantic/src/types/class/static_literal.rs#L2633-L2669","documentation":"`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.","triggerScenarios":"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.","commonSituations":"Modifying TypedDict detection in ty (adding decorator-based or dynamic TypedDicts); usually triggered by mdtests exercising exotic class statements shortly after such a change.","solutions":["Derive both `CodeGeneratorKind::from_class` and `is_typed_dict`/`typed_dict_params` from a single classification query so they cannot disagree.","Replace the expect with a let-else treating a missing params as non-total, and assert the classification invariant in a debug build.","Add an mdtest reproducing the class shape (e.g. indirect TypedDict inheritance) and run the ty_python_semantic mdtests.","Report upstream with the reproducer if stock ty is affected."],"exampleFix":"// before\nself.typed_dict_params(db).expect(\"TypedDictParams should be available for CodeGeneratorKind::TypedDict\")\n\n// after\nlet Some(params) = self.typed_dict_params(db) else {\n    debug_assert!(false, \"TypedDict classification mismatch\");\n    return Box::default();\n};","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let fields = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    class.fields(db, specialization, field_policy)\n}))\n.unwrap_or_else(|_| Box::default());","preventionTips":["Report the exact class statement and its bases; both classification queries must be shown the same code.","Simplify subclass chains through typing/typing_extensions TypedDict mixes when narrowing a repro.","Contributors: run the TypedDict mdtests after any change to classification queries."],"tags":["rust","panic","expect","ty","typeddict","classification","fields"],"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-14T00:17:10.932Z"}