{"record":{"id":"c563c360807e2999","repo":"oxc-project/oxc","slug":"function-and-catch-parameter-scopes-always-have-a","errorCode":null,"errorMessage":"function and catch parameter scopes always have a parent","messagePattern":"function and catch parameter scopes always have a parent","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/oxc_semantic/src/builder.rs","lineNumber":751,"sourceCode":"    /// so a nested function parameter can still resolve to a later declaration in an enclosing\n    /// function body while skipping declarations in its own body.\n    ///\n    /// This is a workaround until function bodies have separate scopes:\n    /// <https://github.com/oxc-project/backlog/issues/176>.\n    ///\n    /// Resolved references are removed. Unresolved references stay in the flat\n    /// list for later resolution by `resolve_all_references` (which handles\n    /// forward references to declarations not yet visited).\n    fn resolve_references_for_current_scope(&mut self, unresolved_start: usize) {\n        if self.unresolved_references.len() == unresolved_start {\n            return;\n        }\n\n        let current_scope_id = self.current_scope_id;\n        let parent_scope_id = self\n            .scoping\n            .scope_parent_id(current_scope_id)\n            .expect(\"function and catch parameter scopes always have a parent\");\n\n        // Take the list out of `self` while resolving, so the closure can call `&mut self`\n        // methods. Resolution never pushes new unresolved references, so nothing is lost.\n        let mut unresolved_references = mem::take(&mut self.unresolved_references);\n        unresolved_references.retain_from(unresolved_start, |unresolved| {\n            // Parameter decorators are visited in an outer class scope. Leave those references\n            // for final resolution because the current function is not on their scope chain.\n            let lookup_scope_id = unresolved.lookup_scope_id;\n            if lookup_scope_id != current_scope_id\n                && !self.scoping.scope_is_descendant_of(lookup_scope_id, current_scope_id)\n            {\n                return true;\n            }\n            if self.walk_up_resolve_reference(*unresolved, current_scope_id) {\n                return false;\n            }\n            // Skip this function body during final resolution. An enclosing parameter resolution\n            // may still resolve the reference before advancing the boundary again.","sourceCodeStart":733,"sourceCodeEnd":769,"githubUrl":"https://github.com/oxc-project/oxc/blob/a3d33dda7cb69da23db4fcaa2c0c05de61e760a1/crates/oxc_semantic/src/builder.rs#L733-L769","documentation":"This is an internal invariant panic in oxc_semantic's scope builder. `resolve_references_for_current_scope` early-resolves references collected while visiting a function or catch parameter scope by looking up that scope's parent via `scope_parent_id(...).expect(...)`. The library asserts the scope current at that point must be a function or catch parameter scope, which by construction is always nested inside another scope, so the parent must exist. If the parent is `None`, the builder entered the function with a malformed/missing scope tree, so it panics instead of silently mis-resolving references.","triggerScenarios":"Calling `resolve_references_for_current_scope` (from visit_function, visit_arrow_function_expression, or visit_catch_parameter) while `self.current_scope_id` refers to a scope with no parent in `Scoping` — i.e. the root/global scope, or a scope whose parent edge was never pushed. Only reachable when `unresolved_references.len() != unresolved_start`, meaning parameter-scope unresolved references exist when the function/catch parameter visit ends.","commonSituations":"Custom AST visits or embedding code that calls `SemanticBuilder` visit methods out of order and leaves `current_scope_id` at the program root; builder refactors that skip the `enter_scope`/`leave_scope` pairs for function or catch clauses; external crates constructing a `Scoping` scope tree manually and calling resolution on a parentless scope.","solutions":["Ensure every function/arrow/catch visit is wrapped in matching enter_scope/leave_scope calls so the parameter scope is a child of an enclosing scope","Verify you are not invoking the builder's internal visit methods on a manually built scope tree; use the standard `SemanticBuilder::build` entry point","Update oxc to the latest version — this invariant was added while function-body scoping is being reworked (oxc-project/backlog#176), and internal scope construction has changed across versions","If you hit this on a reproducible input, file an oxc issue with the snippet; it indicates a scope-tree construction bug, not a recoverable error"],"exampleFix":"// before (external code driving the builder)\nlet mut builder = SemanticBuilder::new();\nbuilder.visit_program_without_scoping(&program); // current_scope_id stays root\n// after\nlet semantic = SemanticBuilder::new().build(&program); // standard entry, scopes pushed correctly","handlingStrategy":"validation","validationCode":"// Before resolving, confirm the scope has a parent and unresolved refs exist\nif builder.unresolved_references.len() == unresolved_start {\n    return; // nothing to resolve, no panic path\n}\ndebug_assert!(\n    scoping.scope_parent_id(current_scope_id).is_some(),\n    \"function/catch parameter scope must have a parent before resolution\"\n);","typeGuard":"fn has_parent_scope(scoping: &Scoping, scope_id: ScopeId) -> bool {\n    scoping.scope_parent_id(scope_id).is_some()\n}","tryCatchPattern":null,"preventionTips":["Always use `SemanticBuilder::build` rather than calling internal visit_* methods directly","Keep enter_scope/leave_scope calls paired when customizing AST traversal","Run `cargo test -p oxc_semantic` on any change touching scope construction in the builder","Pin and regularly update oxc versions, since the parameter-scope resolution workaround (backlog#176) is actively changing"],"tags":["rust","panic","internal-invariant","scope-resolution","oxc-semantic"],"backgroundTag":"assertion-panic-invariant-violation","analyzedSha":"a3d33dda7cb69da23db4fcaa2c0c05de61e760a1","analyzedAt":"2026-08-31T13:44:24.511Z","contentChangedAt":"2026-08-31T13:44:24.511Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}