diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 0383ea4c5a..2f90e08c22 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -110,7 +110,7 @@ public function parseGroups(string $regex): ?RegexAstWalkResult RegexGroupWalkResult::createEmpty(), ); - if (!$subjectAsGroupResult->mightContainEmptyStringLiteral()) { + if (!$subjectAsGroupResult->mightContainEmptyStringLiteral() && !$this->containsEscapeK($ast)) { // we could handle numeric-string, in case we know the regex is delimited by ^ and $ if ($subjectAsGroupResult->isNonFalsy()->yes()) { $astWalkResult = $astWalkResult->withSubjectBaseType( @@ -171,6 +171,21 @@ private function updateCapturingAstAddEmptyToken(TreeNode $ast): void $ast->setChildren([$emptyAlternationAst]); } + private function containsEscapeK(TreeNode $ast): bool + { + if ($ast->getId() === 'token' && $ast->getValueToken() === 'match_point_reset') { + return true; + } + + foreach ($ast->getChildren() as $child) { + if ($this->containsEscapeK($child)) { + return true; + } + } + + return false; + } + private function walkRegexAst( TreeNode $ast, ?RegexAlternation $alternation, diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index 5e87970c8e..545fd191f1 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -1011,7 +1011,65 @@ function bug12749f(string $str): void } } -function bug12397(string $string) : array { +function bug12397(string $string): void { $m = preg_match('#\b([A-Z]{2,})-(\d+)#', $string, $match); assertType('list{0?: string, 1?: non-falsy-string, 2?: numeric-string}', $match); } + +function bug12792(string $string): void { + if (preg_match('~a\Kb~', $string, $match) === 1) { + assertType('array{string}', $match); // could be array{'b'} + } + + if (preg_match('~a\K~', $string, $match) === 1) { + assertType('array{string}', $match); // could be array{''} + } + + if (preg_match('~a\K.+~', $string, $match) === 1) { + assertType('array{string}', $match); // could be array{non-empty-string} + } + + if (preg_match('~a\K.*~', $string, $match) === 1) { + assertType('array{string}', $match); + } + + if (preg_match('~a\K(.+)~', $string, $match) === 1) { + assertType('array{string, non-empty-string}', $match); // could be array{non-empty-string, non-empty-string} + } + + if (preg_match('~a\K(.*)~', $string, $match) === 1) { + assertType('array{string, string}', $match); + } + + if (preg_match('~a\K(.+?)~', $string, $match) === 1) { + assertType('array{string, non-empty-string}', $match); // could be array{non-empty-string, non-empty-string} + } + + if (preg_match('~a\K(.*?)~', $string, $match) === 1) { + assertType('array{string, string}', $match); + } + + if (preg_match('~a\K(?=.+)~', $string, $match) === 1) { + assertType('array{string}', $match); // could be array{''} + } + + if (preg_match('~a\K(?=.*)~', $string, $match) === 1) { + assertType('array{string}', $match); // could be array{''} + } + + if (preg_match('~a(?:x\Kb|c)~', $string, $match) === 1) { + assertType('array{string}', $match); // could be array{'ac'|'b'} + } + + if (preg_match('~a(?:c|x\Kb)~', $string, $match) === 1) { + assertType('array{string}', $match); // could be array{'ac'|'b'} + } + + if (preg_match('~a(y|(?:x\Kb|c))d~', $string, $match) === 1) { + assertType('array{string, non-empty-string}', $match); // could be array{'acd'|'ayd'|'bd', 'c'|'xb'|'y'} + } + + if (preg_match('~a((?:c|x\Kb)|y)d~', $string, $match) === 1) { + assertType('array{string, non-empty-string}', $match); // could be array{'acd'|'ayd'|'bd', 'c'|'xb'|'y'} + } +} diff --git a/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php index c6ba4824c2..7bd70492ee 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php @@ -45,3 +45,14 @@ function ($matches) { PREG_OFFSET_CAPTURE|PREG_UNMATCHED_AS_NULL ); }; + +function bug12792(string $string) : void { + preg_replace_callback( + '~\'(?:[^\']+|\'\')*+\'\K|\[(\w*)\]~', + function ($matches) { + assertType("array{0: string, 1?: string}", $matches); + return ''; + }, + $string + ); +}