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
- Re-read the generated intermediate file and count the actual pattern occurrences, then update the checked_sub count= argument to match the new reality.
- Regenerate the full keyword pipeline from scratch (run the gperf step) to ensure the input is not stale or partial.
- 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
- Treat checked_sub counts as codegen invariants: when upgrading V8/keyword grammar, re-count and update count= immediately.
- Regenerate the full keyword pipeline (gperf step) before validating, so the input isn't partial.
- Add a test asserting the post-substitution output contains exactly N 'int' tokens.
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
- The operator "{expr.data}" is not supported
- Unsupported arity {len(expr.children)}
- Unimplemented platform: {platform.system()}
- gn gen failed
- This appears to not be called from a recent v8 checkout
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/b1efcaeddfa8b2fe.
Report an issue: GitHub.