diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index 628bcb0d3c..fb94b88ab0 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -280,6 +280,10 @@ private function getOnlyTopLevelAlternation(array $captureGroups): ?RegexAlterna return null; } + if ($captureGroup->inOptionalQuantification()) { + return null; + } + if ($alternation === null) { $alternation = $captureGroup->getAlternation(); } elseif ($alternation->getId() !== $captureGroup->getAlternation()->getId()) { diff --git a/src/Type/Regex/RegexCapturingGroup.php b/src/Type/Regex/RegexCapturingGroup.php index 62708a2de3..51a1fc9d85 100644 --- a/src/Type/Regex/RegexCapturingGroup.php +++ b/src/Type/Regex/RegexCapturingGroup.php @@ -82,6 +82,11 @@ public function isOptional(): bool || $this->parent !== null && $this->parent->isOptional(); } + public function inOptionalQuantification(): bool + { + return $this->inOptionalQuantification; + } + public function inOptionalAlternation(): bool { if (!$this->inAlternation()) { diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index 694a7cc886..4b17f15ed4 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -753,3 +753,18 @@ function bug11622 (string $expression): void { assertType("array{string, string}", $matches); } } + +function bug11604 (string $string): void { + if (! preg_match('/(XX)|(YY)?ZZ/', $string, $matches)) { + return; + } + + assertType("array{0: string, 1?: ''|'XX', 2?: 'YY'}", $matches); + // could be array{string, '', 'YY'}|array{string, 'XX'}|array{string} +} + +function bug11604b (string $string): void { + if (preg_match('/(XX)|(YY)?(ZZ)/', $string, $matches)) { + assertType("array{0: string, 1?: ''|'XX', 2?: ''|'YY', 3?: 'ZZ'}", $matches); + } +}