From 1252f55e2be703019cf4e0a6901a2318dfb0baf5 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 24 Jan 2026 08:14:25 +0100 Subject: [PATCH] fixed escaped hash in regex extended mode \# was incorrectly treated as comment start in /x mode --- src/Type/Regex/RegexGroupParser.php | 3 ++- tests/PHPStan/Analyser/nsrt/preg_match_shapes.php | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 6259e7e87d..d78ebaaa8f 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -85,7 +85,8 @@ public function parseGroups(string $regex): ?RegexAstWalkResult if (str_contains($modifiers, 'x')) { // in freespacing mode the # character starts a comment and runs until the end of the line - $regex = preg_replace('/(?regexExpressionHelper->removeDelimitersAndModifiers($regex); diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index 545fd191f1..0a4cc7f6e1 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -1073,3 +1073,18 @@ function bug12792(string $string): void { assertType('array{string, non-empty-string}', $match); // could be array{'acd'|'ayd'|'bd', 'c'|'xb'|'y'} } } + +function testExtendedSyntaxEscapedHash(string $string): void { + // \# in extended mode should be treated as literal hash, not comment + if (preg_match('/^ ([\#.]) $/x', $string, $matches)) { + assertType("array{non-falsy-string, '#'|'.'}", $matches); + } + + // Real comment vs escaped hash + if (preg_match('/ + (\d+) # this is a comment + ([\#ab]+) # hash in character class (escaped) + /x', $string, $matches)) { + assertType('array{non-falsy-string, numeric-string, non-empty-string}', $matches); + } +}