facebook/flow · error · Error

Expected Program, got ${deserialized.type}

Error message

Expected Program, got ${deserialized.type}

What it means

In the same parse() entry point, when the `babel` option is not true, the parser expects the deserialized WASM output root to be an ESTree 'Program' node. Any other root type means the wire-format deserialization did not produce the promised AST shape, so parse() throws to avoid returning an invalid AST to consumers.

Source

Thrown at packages/flow-parser/oxidized-src/FlowParser.js:352

      throw syntaxError;
    }

    const deserialized = new FlowParserDeserializer(
      programBuffer,
      flowParseResult_getPositionBuffer(parseResult),
      flowParseResult_getPositionBufferSize(parseResult),
      flowParseResult_getStringBuffer(parseResult),
      FlowParserWASM,
      options,
    ).deserialize();
    if (options.babel === true) {
      if (deserialized.type !== 'File') {
        throw new Error(`Expected File, got ${deserialized.type}`);
      }
      return deserialized;
    }
    if (deserialized.type !== 'Program') {
      throw new Error(`Expected Program, got ${deserialized.type}`);
    }
    const ast = deserialized;

    // Wire→ESTree loc/range normalization. The wire format carries
    // `rangeStart` / `rangeEnd` on the loc object; ESTree consumers expect a
    // `range: [start, end]` array on the *node* itself, plus a clean
    // `loc: { source, start, end }`. This walk is the parser-level analog of
    // upstream HermesToESTreeAdapter.transform()'s mandatory range conversion
    // (HermesToESTreeAdapter.js:39 — `node.range = [loc.rangeStart, loc.rangeEnd]`).
    // Lives here (not in src/index.js) so callers that bypass the public
    // hermes-parser-compatibility wrapper (e.g. the wasm fixture runner that
    // tests raw Flow ESTree parity with the OCaml parser) still get the
    // canonical ESTree loc/range shape.
    //
    // Track visited nodes so a malformed AST with a cycle doesn't blow the
    // stack; track visited locs separately because two nodes can share a loc
    // reference and we must read `rangeStart`/`rangeEnd` before deleting them.
    const sourceFilename =

View on GitHub (pinned to 5c86586199)

Solutions

  1. Reinstall flow-parser so the wrapper and WASM binary are from the same package version.
  2. If you actually need the Babel 'File' root, pass { babel: true } to parse().
  3. Clear any cached deserialized ASTs produced by older parser versions and re-parse.

Example fix

// before
const ast = flow.parse(src); // expects Program, got File
// after (Babel-style consumers)
const ast = flow.parse(src, { babel: true }); // returns File node
Defensive patterns

Strategy: type-guard

Validate before calling

const ast = flow.parse(src);
if (ast.type !== 'Program') {
  throw new Error('Parser returned non-Program root; check flow-parser version integrity');
}

Type guard

const isProgram = (n) => n != null && typeof n === 'object' && n.type === 'Program';

Try / catch

try {
  const ast = flow.parse(src);
} catch (err) {
  if (String(err.message).startsWith('Expected Program')) {
    console.error('Use { babel: true } if you need a File root, or reinstall flow-parser');
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling parse() without babel:true while the deserialized root node's type is not 'Program' — typically caused by JS wrapper / FlowParserWASM build mismatch or a corrupted deserialized payload.

Common situations: Mismatched flow-parser build artifacts, post-processing the options object incorrectly, or consumers deserializing cached AST blobs from a different parser version.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


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