From 4c9ea6a9feec7d215c5a10815ca14980dbdf0696 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 15 Jul 2024 09:47:29 +0200 Subject: [PATCH] RegexArrayShapeMatcher - Fix PREG_UNMATCHED_AS_NULL with top level alternation --- src/Type/Php/RegexArrayShapeMatcher.php | 4 ++-- tests/PHPStan/Analyser/nsrt/bug-11311.php | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index d5dbaa3533..f508c08639 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -170,7 +170,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $beforeCurrentCombo = false; } elseif ($beforeCurrentCombo && !$group->resetsGroupCounter()) { $group->forceNonOptional(); - } elseif ($group->getAlternationId() === $onlyTopLevelAlternationId) { + } elseif ($group->getAlternationId() === $onlyTopLevelAlternationId && !$this->containsUnmatchedAsNull($flags ?? 0)) { unset($comboList[$groupId]); } } @@ -191,7 +191,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched } } - if ($isOptionalAlternation) { + if ($isOptionalAlternation && !$this->containsUnmatchedAsNull($flags ?? 0)) { $combiTypes[] = new ConstantArrayType([new ConstantIntegerType(0)], [new StringType()], [0], [], true); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11311.php b/tests/PHPStan/Analyser/nsrt/bug-11311.php index 7d5a8d0c80..20d2a4e1bc 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11311.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11311.php @@ -65,3 +65,17 @@ function bug11331c(string $url):void { assertType('array{string, string|null, string|null, string, string}', $matches); } } + +class UnmatchedAsNullWithTopLevelAlternation { + function doFoo(string $s): void { + if (preg_match('/Price: (?:(£)|(€))\d+/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { + assertType('array{string, string|null, string|null}', $matches); // could be array{0: string, 1: null, 2: string}|array{0: string, 1: string, 2: null} + } + } + + function doBar(string $s): void { + if (preg_match('/Price: (?:(£)|(€))?\d+/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { + assertType('array{string, string|null, string|null}', $matches); + } + } +}