nodejs/node · error · Exception

Expected %d and got %d replacement(s) for pattern: %s

Error message

Expected %d and got %d replacement(s) for pattern: %s

What it means

gen-keywords-gen-h.py uses checked_sub() as an assertion that a regex substitution hits an exact expected count of replacements. It runs re.subn and, if the number of matches differs from `count`, raises an Exception showing expected vs. actual. Callers like change_sizet_to_int expect exactly 4 `size_t` occurrences; the guard detects that the upstream-generated input (from a gperf/m4 pipeline) has drifted in structure.

Source

Thrown at deps/v8/tools/gen-keywords-gen-h.py:42


def call_with_input(cmd: List[Union[str, Path]], input_string: str = "") -> str:
  p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
  stdout, _ = p.communicate(input_string.encode())
  retcode = p.wait()
  if retcode != 0:
    raise subprocess.CalledProcessError(retcode, cmd)
  return stdout.decode()


def checked_sub(pattern: Union[str, re.Pattern[str]],
                sub: str,
                out: str,
                count: int = 1,
                flags: int = 0) -> str:
  out, n = re.subn(pattern, sub, out, flags=flags)
  if n != count:
    raise Exception("Expected %d and got %d replacement(s) for pattern: %s" %
                    (count, n, pattern))
  return out


def change_sizet_to_int(out: str) -> str:
  # Literal buffer lengths are given as ints, not size_t
  return checked_sub(r'\bsize_t\b', 'int', out, count=4)


def drop_line_directives(out: str) -> str:
  # #line causes gcov issue, so drop it
  return re.sub(r'^#\s*line .*$\n', '', out, flags=re.MULTILINE)


def trim_and_dcheck_char_table(out: str) -> str:
  # Potential keyword strings are known to be lowercase ascii, so chop off the
  # rest of the table and mask out the char

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Re-read the generated intermediate file and count the actual pattern occurrences, then update the checked_sub count= argument to match the new reality.
  2. Regenerate the full keyword pipeline from scratch (run the gperf step) to ensure the input is not stale or partial.
  3. If the drift is unintended, find the upstream change that altered the number of `size_t` (or other pattern) tokens and reconcile it.

Example fix

// before
def change_sizet_to_int(out: str) -> str:
  return checked_sub(r'\bsize_t\b', 'int', out, count=4)
// after
def change_sizet_to_int(out: str) -> str:
  return checked_sub(r'\bsize_t\b', 'int', out, count=3)
Defensive patterns

Strategy: validation

Validate before calling

import re
_count = len(re.findall(r'\bsize_t\b', generated_out))
if _count != 4:
    sys.exit(f'codegen invariant broken: found {_count} size_t tokens, expected 4; upstream template drifted')

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: The generated keyword header intermediate contains a different number of the pattern than the code asserts (e.g. change_sizet_to_int wants count=4 `size_t` tokens but a new V8 version produces 3 or 5). Any upstream template change to the keyword table generation breaks the invariant.

Common situations: Bumping V8 / the keyword grammar so gperf output shape changes; a partial regeneration that omitted or duplicated a section; editing the codegen template without updating the count parameter.

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/b1efcaeddfa8b2fe. Report an issue: GitHub.