From 63216003befc80712b927c54235dc70cd2c3d134 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 7 Aug 2024 11:28:00 +0200 Subject: [PATCH] RegexArrayShapeMatcher - Fix subject types --- src/Type/Php/RegexArrayShapeMatcher.php | 21 ++++++++++--------- .../Analyser/nsrt/preg_match_shapes.php | 14 +++++++++++++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index 7e48fa124c..fd0cacc17f 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -132,6 +132,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $onlyOptionalTopLevelGroup = $this->getOnlyOptionalTopLevelGroup($groupList); $onlyTopLevelAlternation = $this->getOnlyTopLevelAlternation($groupList); + $flags ??= 0; if ( !$matchesAll @@ -147,14 +148,14 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $groupList, $wasMatched, $trailingOptionals, - $flags ?? 0, + $flags, $markVerbs, $matchesAll, ); - if (!$this->containsUnmatchedAsNull($flags ?? 0, $matchesAll)) { + if (!$this->containsUnmatchedAsNull($flags, $matchesAll)) { $combiType = TypeCombinator::union( - new ConstantArrayType([new ConstantIntegerType(0)], [new StringType()], [0], [], true), + new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($flags, $matchesAll)], [0], [], true), $combiType, ); } @@ -180,7 +181,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $group->forceNonOptional(); } elseif ( $group->getAlternationId() === $onlyTopLevelAlternation->getId() - && !$this->containsUnmatchedAsNull($flags ?? 0, $matchesAll) + && !$this->containsUnmatchedAsNull($flags, $matchesAll) ) { unset($comboList[$groupId]); } @@ -190,7 +191,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $comboList, $wasMatched, $trailingOptionals, - $flags ?? 0, + $flags, $markVerbs, $matchesAll, ); @@ -203,8 +204,8 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched } } - if ($isOptionalAlternation && !$this->containsUnmatchedAsNull($flags ?? 0, $matchesAll)) { - $combiTypes[] = new ConstantArrayType([new ConstantIntegerType(0)], [new StringType()], [0], [], true); + if ($isOptionalAlternation && !$this->containsUnmatchedAsNull($flags, $matchesAll)) { + $combiTypes[] = new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($flags, $matchesAll)], [0], [], true); } return TypeCombinator::union(...$combiTypes); @@ -214,7 +215,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $groupList, $wasMatched, $trailingOptionals, - $flags ?? 0, + $flags, $markVerbs, $matchesAll, ); @@ -288,7 +289,7 @@ private function buildArrayType( // first item in matches contains the overall match. $builder->setOffsetValueType( $this->getKeyType(0), - $this->createSubjectValueType($wasMatched, $flags, $matchesAll), + $this->createSubjectValueType($flags, $matchesAll), $this->isSubjectOptional($wasMatched, $matchesAll), ); @@ -351,7 +352,7 @@ private function isSubjectOptional(TrinaryLogic $wasMatched, bool $matchesAll): return !$wasMatched->yes(); } - private function createSubjectValueType(TrinaryLogic $wasMatched, int $flags, bool $matchesAll): Type + private function createSubjectValueType(int $flags, bool $matchesAll): Type { $subjectValueType = TypeCombinator::removeNull($this->getValueType(new StringType(), $flags, $matchesAll)); diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index ecbc91e005..9cb44dbd0c 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -648,3 +648,17 @@ function (string $s): void { assertType('array{string, non-empty-string}', $matches); } }; + +function (string $value): void +{ + if (preg_match('/^(x)*$/', $value, $matches, PREG_OFFSET_CAPTURE)) { + assertType("array{0: array{string, int<0, max>}, 1?: array{non-empty-string, int<0, max>}}", $matches); + } +}; + +function (string $value): void +{ + if (preg_match('/^(?:(x)|(y))*$/', $value, $matches, PREG_OFFSET_CAPTURE)) { + assertType("array{0: array{string, int<0, max>}, 1?: array{non-empty-string, int<0, max>}, 2?: array{non-empty-string, int<0, max>}}", $matches); + } +};