{"record":{"id":"e07055e2caa7bba3","repo":"facebook/flow","slug":"internal-error-tried-to-add-declared-private-with","errorCode":null,"errorMessage":"Internal Error: Tried to add_declared_private with outside of class scope.","messagePattern":"Internal Error: Tried to add_declared_private with outside of class scope\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"rust_port/crates/flow_parser/src/parser_env.rs","lineNumber":657,"sourceCode":"            }\n            n if n >= 2 => {\n                let (loc_declared_privates, loc_used_privates) = self.privates.pop().unwrap();\n                let unbound_privates =\n                    get_unbound_privates(loc_declared_privates, loc_used_privates);\n                let (decl_head, mut used_head) = self.privates.pop().unwrap();\n                used_head.extend(unbound_privates);\n                self.privates.push((decl_head, used_head));\n            }\n            _ => panic!(\"Internal Error: `exit_class` called before a matching `enter_class`\"),\n        }\n        Ok(())\n    }\n\n    pub fn add_declared_private(&mut self, name: String) {\n        let (declared, _) = self\n            .privates\n            .last_mut()\n            .expect(\"Internal Error: Tried to add_declared_private with outside of class scope.\");\n        declared.insert(name);\n    }\n\n    pub(crate) fn add_used_private(&mut self, name: String, loc: Loc) -> Result<(), Rollback> {\n        match self.privates.last_mut() {\n            Some((_, used)) => used.push((name, loc)),\n            None => self.error_at(loc, ParseError::PrivateNotInClass)?,\n        }\n        Ok(())\n    }\n\n    pub(crate) fn consume_comments_until(&mut self, pos: Position) {\n        self.consumed_comments_pos = pos;\n    }\n\n    // lookaheads\n\n    pub(crate) fn lookahead_0(&mut self) -> &LexResult {","sourceCodeStart":639,"sourceCodeEnd":675,"githubUrl":"https://github.com/facebook/flow/blob/5c865861998a8ccb7dbc82b0c1f511e9ef60c3d9/rust_port/crates/flow_parser/src/parser_env.rs#L639-L675","documentation":"Internal invariant assertion in the Flow parser's private-name tracking. enter_class pushes a (declared, used) frame onto ParserEnv::privates (rust_port/crates/flow_parser/src/parser_env.rs:617) and exit_class pops it; add_declared_private (parser_env.rs:653) inserts a declared `#field` into the innermost frame and expects that frame to exist. The panic means a declared private name was registered while the privates stack was empty, i.e. no class scope was open. Unlike its sibling add_used_private, which converts the same condition into the recoverable ParseError::PrivateNotInClass, this path has no graceful fallback, so when user source text reaches it, it is a parser bug.","triggerScenarios":"The parser records a declared private (#field in a class body) after the class frame was already popped: an unbalanced enter_class/exit_class pair in an error-recovery branch, a `#private` element accepted outside a class parse context, or a regression in the Rust port's class parsing. Reached only through the internal parse flow (ParserEnv::add_declared_private), never directly by library callers.","commonSituations":"Fuzzing corpora or machine-generated JS/Flow hitting an untested class/private-field combination; a flow_parser crate upgrade that changed class or private-field parsing; custom parser extensions that skip enter_class on certain class-like constructs.","solutions":["Minimize the offending source file and capture the exact panicking input, then report it as a flow_parser bug with the stack trace","Upgrade or pin the flow_parser crate to a version whose class/private-field parsing handles your input (check the changelog for error-recovery fixes)","If you embed the parser, wrap parse calls in std::panic::catch_unwind so one bad file cannot take down a whole server","If you maintain parser code, make add_declared_private return a ParseError like add_used_private does instead of expecting"],"exampleFix":"// before (parser_env.rs:653): panics when no class scope is open\nlet (declared, _) = self.privates.last_mut()\n    .expect(\"Internal Error: Tried to add_declared_private with outside of class scope.\");\ndeclared.insert(name);\n\n// after: degrade to a recoverable parse error like add_used_private\nlet Some((declared, _)) = self.privates.last_mut() else {\n    return Err(ParseError::PrivateNotInClass); // reported at the current loc\n};\ndeclared.insert(name);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Rust: isolate per-file parser panics so one input cannot kill the server\nlet outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    flow_parser::parse_program(src)\n}));\nmatch outcome {\n    Ok(parsed) => parsed,\n    Err(payload) => {\n        log::error!(\"flow_parser internal panic (add_declared_private): {payload:?}\");\n        return FileResult::parser_bug(file);\n    }\n}","preventionTips":["Pin the flow_parser version and fuzz-test upgrades against your corpus before rolling out","Run untrusted or machine-generated source through the parser in a subprocess or worker that can crash safely","Keep a regression corpus of inputs that previously panicked internal invariants"],"tags":["flow-parser","internal-invariant","panic","class-scope","private-fields"],"backgroundTag":"parser-internal-invariant","analyzedSha":"5c865861998a8ccb7dbc82b0c1f511e9ef60c3d9","analyzedAt":"2026-08-20T10:41:37.992Z","contentChangedAt":"2026-08-20T10:41:37.992Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}