{"record":{"id":"b03f95b94578533a","repo":"astral-sh/ruff","slug":"expected-class","errorCode":null,"errorMessage":"expected class","messagePattern":"expected class","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ty_python_core/src/scope.rs","lineNumber":424,"sourceCode":"            | Self::ClassTypeParameters(_)\n            | Self::TypeAliasTypeParameters(_) => ScopeKind::TypeParams,\n            Self::TypeAlias(_) => ScopeKind::TypeAlias,\n            Self::ListComprehension(_)\n            | Self::SetComprehension(_)\n            | Self::DictComprehension(_)\n            | Self::GeneratorExpression(_) => ScopeKind::Comprehension,\n        }\n    }\n\n    pub fn as_class(&self) -> Option<&AstNodeRef<ast::StmtClassDef>> {\n        match self {\n            Self::Class(class) => Some(class),\n            _ => None,\n        }\n    }\n\n    pub fn expect_class(&self) -> &AstNodeRef<ast::StmtClassDef> {\n        self.as_class().expect(\"expected class\")\n    }\n\n    pub fn as_function(&self) -> Option<&AstNodeRef<ast::StmtFunctionDef>> {\n        match self {\n            Self::Function(function) => Some(function),\n            _ => None,\n        }\n    }\n\n    pub fn expect_function(&self) -> &AstNodeRef<ast::StmtFunctionDef> {\n        self.as_function().expect(\"expected function\")\n    }\n\n    fn as_type_alias(&self) -> Option<&AstNodeRef<ast::StmtTypeAlias>> {\n        match self {\n            Self::TypeAlias(type_alias) => Some(type_alias),\n            _ => None,\n        }","sourceCodeStart":406,"sourceCodeEnd":442,"githubUrl":"https://github.com/astral-sh/ruff/blob/d1087a4b9e03d253a88703f34e0869ee4b805456/crates/ty_python_core/src/scope.rs#L406-L442","documentation":"Scope::expect_class unwraps as_class(): it returns the StmtClassDef node only when the scope was introduced by a class statement; any other scope kind (module, function, comprehension, type alias) panics with 'expected class'. It is a convenience for callers that already established the scope kind.","triggerScenarios":"Calling expect_class on a scope that is not a Class scope - either direct API misuse, or a caller whose kind check disagreed with the scope node actually looked up (e.g. assuming a definition's scope is a class scope when it resolved elsewhere).","commonSituations":"Contributors adding scope-based lookups (pytest class handling does exactly this call) and assuming the scope id always refers to a class; refactors that change which scope a definition resolves to.","solutions":["Use as_class() and handle None instead of expect_class when the kind is not statically guaranteed","Re-verify how the scope id was obtained; class scopes only wrap class-level definitions","If reached inside ty itself on valid Python, minimize the repro and file an issue"],"exampleFix":"// before\nlet class_node = scope.node().expect_class(); // panics if not a class scope\n\n// after\nlet Some(class_node) = scope.node().as_class() else { return None; };","handlingStrategy":"type-guard","validationCode":"if scope.kind() != ScopeKind::Class { return None; } // establish kind before unwrapping","typeGuard":"let is_class_scope = |scope: &Scope| matches!(scope.kind(), ScopeKind::Class);","tryCatchPattern":"match scope.node().as_class() { Some(class) => { /* ... */ }, None => { /* wrong scope kind: skip or report */ } }","preventionTips":["Prefer as_class() over expect_class unless the kind is proven","Match on ScopeKind before scope-node access","For runtime panics inside ty, capture the Python repro and file an issue"],"tags":["internal","scope","ast","panic","invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"d1087a4b9e03d253a88703f34e0869ee4b805456","analyzedAt":"2026-08-20T16:33:49.445Z","contentChangedAt":"2026-08-20T16:33:49.445Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}