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

  1. Minimize the offending source file and capture the exact panicking input, then report it as a flow_parser bug with the stack trace
  2. 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)
  3. If you embed the parser, wrap parse calls in std::panic::catch_unwind so one bad file cannot take down a whole server
  4. 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

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


AI-assisted analysis of facebook/flow@5c86586199 (2026-08-20). Data as JSON: /api/errors/e07055e2caa7bba3. Report an issue: GitHub.