{"record":{"id":"d4038e9be15f6994","repo":"astral-sh/ruff","slug":"expected-function","errorCode":null,"errorMessage":"expected function","messagePattern":"expected function","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ty_python_core/src/scope.rs","lineNumber":435,"sourceCode":"        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        }\n    }\n\n    pub fn expect_type_alias(&self) -> &AstNodeRef<ast::StmtTypeAlias> {\n        self.as_type_alias().expect(\"expected type alias\")\n    }\n\n    /// Returns the anchor node index for this scope, or `None` for the module scope.\n    ///\n    /// This is used to compute relative node indices for expressions within the scope,\n    /// providing a stable anchor that only changes when the scope-introducing node changes.\n    pub fn node_index(&self) -> Option<NodeIndex> {","sourceCodeStart":417,"sourceCodeEnd":453,"githubUrl":"https://github.com/astral-sh/ruff/blob/d1087a4b9e03d253a88703f34e0869ee4b805456/crates/ty_python_core/src/scope.rs#L417-L453","documentation":"Scope::expect_function unwraps as_function(): it returns the StmtFunctionDef node only when the scope was introduced by a function (or lambda) statement; any other scope kind panics with 'expected function'. Callers like function.rs use it on body scopes where the kind is known by construction.","triggerScenarios":"Calling expect_function on a module, class, comprehension, or type-alias scope - direct misuse, or a lookup path that fetched a different scope than assumed (e.g. a definition whose enclosing scope is not the function body).","commonSituations":"Contributors writing new scope-based queries without matching on ScopeKind first; semantic-model refactors shifting which scope a node belongs to.","solutions":["Use as_function() and handle None when the scope kind is not guaranteed","Match on the scope kind (ScopeKind::Function) before calling expect_*","If it fires inside ty on valid Python, minimize the repro and file an issue"],"exampleFix":"// before\nlet fn_node = body_scope.node(db).expect_function(); // panics if not a function scope\n\n// after\nlet Some(fn_node) = body_scope.node(db).as_function() else { return None; };","handlingStrategy":"type-guard","validationCode":"if scope.kind() != ScopeKind::Function { return None; } // establish kind before unwrapping","typeGuard":"let is_function_scope = |scope: &Scope| matches!(scope.kind(), ScopeKind::Function);","tryCatchPattern":"match scope.node().as_function() { Some(func) => { /* ... */ }, None => { /* wrong scope kind: skip or report */ } }","preventionTips":["Use as_function() when the scope provenance is not guaranteed","Derive scope ids from known constructs (e.g. function body scopes) rather than guesses","Keep expect_* calls co-located with the kind check that justifies them"],"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"}