facebook/flow · error
Internal Error: Tried to add_declared_private with outside o
Error message
Internal Error: Tried to add_declared_private with outside of class scope.
What it means
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.
Source
Thrown at rust_port/crates/flow_parser/src/parser_env.rs:657
}
n if n >= 2 => {
let (loc_declared_privates, loc_used_privates) = self.privates.pop().unwrap();
let unbound_privates =
get_unbound_privates(loc_declared_privates, loc_used_privates);
let (decl_head, mut used_head) = self.privates.pop().unwrap();
used_head.extend(unbound_privates);
self.privates.push((decl_head, used_head));
}
_ => panic!("Internal Error: `exit_class` called before a matching `enter_class`"),
}
Ok(())
}
pub fn add_declared_private(&mut self, name: String) {
let (declared, _) = self
.privates
.last_mut()
.expect("Internal Error: Tried to add_declared_private with outside of class scope.");
declared.insert(name);
}
pub(crate) fn add_used_private(&mut self, name: String, loc: Loc) -> Result<(), Rollback> {
match self.privates.last_mut() {
Some((_, used)) => used.push((name, loc)),
None => self.error_at(loc, ParseError::PrivateNotInClass)?,
}
Ok(())
}
pub(crate) fn consume_comments_until(&mut self, pos: Position) {
self.consumed_comments_pos = pos;
}
// lookaheads
pub(crate) fn lookahead_0(&mut self) -> &LexResult {View on GitHub (pinned to 5c86586199)
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
Example fix
// before (parser_env.rs:653): panics when no class scope is open
let (declared, _) = self.privates.last_mut()
.expect("Internal Error: Tried to add_declared_private with outside of class scope.");
declared.insert(name);
// after: degrade to a recoverable parse error like add_used_private
let Some((declared, _)) = self.privates.last_mut() else {
return Err(ParseError::PrivateNotInClass); // reported at the current loc
};
declared.insert(name); Defensive patterns
Strategy: try-catch
Try / catch
// Rust: isolate per-file parser panics so one input cannot kill the server
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
flow_parser::parse_program(src)
}));
match outcome {
Ok(parsed) => parsed,
Err(payload) => {
log::error!("flow_parser internal panic (add_declared_private): {payload:?}");
return FileResult::parser_bug(file);
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Popping lex mode from empty stack
- Unknown exception reading from the server: {}
- Error sending command to server: {}
- invalid line
- invalid column
AI-assisted analysis of facebook/flow@5c86586199 (2026-08-20).
Data as JSON: /api/errors/e07055e2caa7bba3.
Report an issue: GitHub.