From 0e614b0c14616493390600d7917f1e89f3238ae3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 19 Aug 2024 21:38:15 +0200 Subject: [PATCH 1/4] Add failing test for matchAllStrictGroups --- tests/PHPStanTests/nsrt/preg-match.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/PHPStanTests/nsrt/preg-match.php b/tests/PHPStanTests/nsrt/preg-match.php index c71875f..38957c7 100644 --- a/tests/PHPStanTests/nsrt/preg-match.php +++ b/tests/PHPStanTests/nsrt/preg-match.php @@ -110,6 +110,10 @@ function doMatchAllStrictGroups(string $s): void assertType('array{}', $matches); } assertType('array{}|array{0: list, test: list, 1: list}', $matches); + + if (Preg::isMatchAllStrictGroups('/Price: (?£|€)?\d+/', $s, $matches)) { + assertType('array{0: list, test: list, 1: list}', $matches); + } } // disabled until https://github.com/phpstan/phpstan-src/pull/3185 can be resolved From 8b8de9d57df9e561d5ae1a1a42c96cc2d64511a1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 26 Aug 2024 08:30:02 +0200 Subject: [PATCH 2/4] Fix PHPStan extension for matchAllStrictGroups --- src/PHPStan/PregMatchFlags.php | 33 +++++++++++++++++++ .../PregMatchTypeSpecifyingExtension.php | 14 ++------ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/PHPStan/PregMatchFlags.php b/src/PHPStan/PregMatchFlags.php index ace3e8a..824482b 100644 --- a/src/PHPStan/PregMatchFlags.php +++ b/src/PHPStan/PregMatchFlags.php @@ -3,11 +3,16 @@ namespace Composer\Pcre\PHPStan; use PHPStan\Analyser\Scope; +use PHPStan\Type\ArrayType; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\IntersectionType; use PHPStan\Type\TypeCombinator; use PHPStan\Type\Type; use PhpParser\Node\Arg; use PHPStan\Type\Php\RegexArrayShapeMatcher; +use PHPStan\Type\TypeTraverser; +use PHPStan\Type\UnionType; final class PregMatchFlags { @@ -34,4 +39,32 @@ static public function getType(?Arg $flagsArg, Scope $scope): ?Type } return TypeCombinator::union(...$internalFlagsTypes); } + + static public function removeNullFromMatches(Type $matchesType): Type + { + return TypeTraverser::map($matchesType, static function (Type $type, callable $traverse): Type { + if ($type instanceof UnionType || $type instanceof IntersectionType) { + return $traverse($type); + } + + if ($type instanceof ConstantArrayType) { + return new ConstantArrayType( + $type->getKeyTypes(), + array_map(static function (Type $valueType) use ($traverse): Type { + return $traverse($valueType); + }, $type->getValueTypes()), + $type->getNextAutoIndexes(), + [], + $type->isList() + ); + } + + if ($type instanceof ArrayType) { + return new ArrayType($type->getKeyType(), TypeCombinator::removeNull($type->getItemType())); + } + + return TypeCombinator::removeNull($type); + }); + } + } diff --git a/src/PHPStan/PregMatchTypeSpecifyingExtension.php b/src/PHPStan/PregMatchTypeSpecifyingExtension.php index 6d602bb..cf22f60 100644 --- a/src/PHPStan/PregMatchTypeSpecifyingExtension.php +++ b/src/PHPStan/PregMatchTypeSpecifyingExtension.php @@ -82,19 +82,9 @@ public function specifyTypes(MethodReflection $methodReflection, StaticCall $nod } if ( - in_array($methodReflection->getName(), ['matchStrictGroups', 'isMatchStrictGroups'], true) - && count($matchedType->getConstantArrays()) === 1 + in_array($methodReflection->getName(), ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true) ) { - $matchedType = $matchedType->getConstantArrays()[0]; - $matchedType = new ConstantArrayType( - $matchedType->getKeyTypes(), - array_map(static function (Type $valueType): Type { - return TypeCombinator::removeNull($valueType); - }, $matchedType->getValueTypes()), - $matchedType->getNextAutoIndexes(), - [], - $matchedType->isList() - ); + $matchedType = PregMatchFlags::removeNullFromMatches($matchedType); } $overwrite = false; From 9d5050c5a9189bf1f3f9744749fa814d3477be7f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 26 Aug 2024 08:36:40 +0200 Subject: [PATCH 3/4] Update UnsafeStrictGroupsCallRuleTest.php --- tests/PHPStanTests/UnsafeStrictGroupsCallRuleTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/PHPStanTests/UnsafeStrictGroupsCallRuleTest.php b/tests/PHPStanTests/UnsafeStrictGroupsCallRuleTest.php index 2264efd..3ebb382 100644 --- a/tests/PHPStanTests/UnsafeStrictGroupsCallRuleTest.php +++ b/tests/PHPStanTests/UnsafeStrictGroupsCallRuleTest.php @@ -44,6 +44,10 @@ public function testRule(): void 'The isMatchStrictGroups call is potentially unsafe as $matches\' type could not be inferred.', 86, ], + [ + 'The isMatchAllStrictGroups call is unsafe as match groups "test", "1" are optional and may be null.', + 114 + ] ]); } From 3a1397636af6ec9eeb92d776f25f6b99f0a8ea57 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 26 Aug 2024 08:39:07 +0200 Subject: [PATCH 4/4] Update PregMatchFlags.php --- src/PHPStan/PregMatchFlags.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PHPStan/PregMatchFlags.php b/src/PHPStan/PregMatchFlags.php index 824482b..3daf8a4 100644 --- a/src/PHPStan/PregMatchFlags.php +++ b/src/PHPStan/PregMatchFlags.php @@ -60,7 +60,7 @@ static public function removeNullFromMatches(Type $matchesType): Type } if ($type instanceof ArrayType) { - return new ArrayType($type->getKeyType(), TypeCombinator::removeNull($type->getItemType())); + return new ArrayType($type->getKeyType(), $traverse($type->getItemType())); } return TypeCombinator::removeNull($type);