rectorphp/rector · error · InvalidEregException
"{" does not have a matching "}"
Error message
"{" does not have a matching "}" What it means
processCurlyBracket() is called when an atom is followed by '{' beginning a bound like 'a{2,3}'. It searches for the closing '}' from that position; if none exists in the rest of the pattern, the bound is unterminated and InvalidEregException('"{" does not have a matching "}"') is thrown (EregToPcreTransformer.php:255), aborting the ereg-to-preg conversion of that call.
Source
Thrown at rules/Php70/EregToPcreTransformer.php:255
}
private function _ere2pcre_escape(string $content): string
{
if ($content === "\x00") {
throw new InvalidEregException('a literal null byte in the regex');
}
if (strpos('\^$.[]|()?*+{}-/', $content) !== \false) {
return '\\' . $content;
}
return $content;
}
/**
* @param array<int, mixed> $r
*/
private function processCurlyBracket(string $s, int $i, array &$r, int $rr): int
{
$ii = strpos($s, '}', $i);
if ($ii === \false) {
throw new InvalidEregException('"{" does not have a matching "}"');
}
$start = $i + 1;
$length = $ii - ($i + 1);
$bound = Strings::substring($s, $start, $length);
$matches = Strings::match($bound, self::BOUND_REGEX);
if ($matches === null) {
throw new InvalidEregException('an invalid bound');
}
if (isset($matches[self::MAXIMAL_NUMBER_PART])) {
if ($matches[self::MINIMAL_NUMBER_PART] > $matches[self::MAXIMAL_NUMBER_PART]) {
throw new InvalidEregException('an invalid bound');
}
$r[$rr] .= '{' . $matches[self::MINIMAL_NUMBER_PART] . ',' . $matches[self::MAXIMAL_NUMBER_PART] . '}';
} elseif (isset($matches['comma'])) {
$r[$rr] .= '{' . $matches[self::MINIMAL_NUMBER_PART] . ',}';
} else {
$r[$rr] .= '{' . $matches[self::MINIMAL_NUMBER_PART] . '}';
}View on GitHub (pinned to 408fcb0ff1)
Solutions
- Close the bound in the flagged pattern: 'a{2' -> 'a{2}'.
- If the braces are literal placeholders, escape them ('a\{2\}') so they take the literal path.
- Re-run rector; preflight literal ereg patterns for '{' with a digit after it and no '}' to the end of the pattern.
Example fix
// before - unterminated bound
ereg('a{2', $subject);
// after - closed bound
ereg('a{2}', $subject); // rector: preg_match('#a{2}#m', $subject) Defensive patterns
Strategy: try-catch
Validate before calling
// quick lint: digit-led '{' must find a '}' later in the pattern
function boundClosed(string $p): bool {
return !preg_match('~\{\d~', $p) || (bool) preg_match('~\{\d[^}]*\}~', $p);
} Try / catch
try {
$pcre = $eregToPcreTransformer->transform($pattern, $ignoreCase);
} catch (InvalidEregException $e) {
// '{' bound never closed — add the '}' or escape the brace as literal
continue;
} Prevention
- Every '{' that starts a bound needs a matching '}' — check long alternations where the tail was edited.
- Escape braces you mean literally ('\{').
- Batch-validate literal patterns with the transformer before the full rector run.
When it happens
Trigger: Rector (Php70 set / EregToPregMatchRector, target PHP >= 7.0) processes an ereg-family call whose literal pattern has a '{' bound that is never closed, e.g. `ereg('a{2', $s)`, `split('x{1,2', $s)`, or a literal '{' that follows an atom and a digit but has no '}' anywhere after it (strpos returns false). Note: a literal '{' directly after an atom with no digit following takes the escape path, so this throw specifically needs a digit-led bound without '}'.
Common situations: Truncated quantifiers after edits; patterns mixing template placeholders with real bounds ('a{2{name}') where the first '}' was removed; hand-merged alternations that lost the closing brace.
Related errors
- an invalid bound
- unescaped metacharacter "%s"
- unescaped metacharacter ")"
- "[" does not have a matching "]"
- an invalid escape sequence at the end
AI-assisted analysis of rectorphp/rector@408fcb0ff1 (2026-08-21).
Data as JSON: /api/errors/4c64fb9e32bc8169.
Report an issue: GitHub.