From 8a6bc34da3efe58d1c3bf2608ba1bea8ed45d61c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 13 Aug 2023 13:08:25 +0200 Subject: [PATCH 01/55] Implement array shapes for preg_match() fix n modifier support PREG_OFFSET_CAPTURE cs separated 8.2 tests support PREG_OFFSET_CAPTURE|PREG_UNMATCHED_AS_NULL fix --- conf/config.neon | 8 ++ .../Php/PregMatchTypeSpecifyingExtension.php | 89 ++++++++++++++ src/Type/Php/RegexShapeMatcher.php | 111 ++++++++++++++++++ .../Analyser/NodeScopeResolverTest.php | 15 +++ .../Analyser/data/preg_match_shapes.php | 65 ++++++++++ .../Analyser/data/preg_match_shapes_php82.php | 13 ++ 6 files changed, 301 insertions(+) create mode 100644 src/Type/Php/PregMatchTypeSpecifyingExtension.php create mode 100644 src/Type/Php/RegexShapeMatcher.php create mode 100644 tests/PHPStan/Analyser/data/preg_match_shapes.php create mode 100644 tests/PHPStan/Analyser/data/preg_match_shapes_php82.php diff --git a/conf/config.neon b/conf/config.neon index 061a65eb02..bd1b75e949 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1465,6 +1465,14 @@ services: tags: - phpstan.dynamicFunctionThrowTypeExtension + - + class: PHPStan\Type\Php\PregMatchTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.functionTypeSpecifyingExtension + + - + class: PHPStan\Type\Php\RegexShapeMatcher + - class: PHPStan\Type\Php\ReflectionClassConstructorThrowTypeExtension tags: diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php new file mode 100644 index 0000000000..05c77cfc6d --- /dev/null +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -0,0 +1,89 @@ +typeSpecifier = $typeSpecifier; + } + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return in_array(strtolower($functionReflection->getName()), ['preg_match'], true); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + $args = $node->getArgs(); + if (count($args) < 2) { + return new SpecifiedTypes(); + } + + $patternArg = $args[0] ?? null; + $matchesArg = $args[2] ?? null; + $flagsArg = $args[3] ?? null; + + if ($patternArg === null || $matchesArg === null) { + return new SpecifiedTypes(); + } + + $patternType = $scope->getType($patternArg->value); + $constantStrings = $patternType->getConstantStrings(); + if (count($constantStrings) === 0) { + return new SpecifiedTypes(); + } + + $flags = null; + if ($flagsArg !== null) { + $flagsType = $scope->getType($flagsArg->value); + + if ( + !$flagsType instanceof ConstantIntegerType + || !in_array($flagsType->getValue(), [PREG_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL, PREG_OFFSET_CAPTURE | PREG_UNMATCHED_AS_NULL], true) + ) { + return new SpecifiedTypes(); + } + + $flags = $flagsType->getValue(); + } + + $matchedTypes = []; + foreach ($constantStrings as $constantString) { + $matchedTypes[] = $this->regexShapeMatcher->matchType($constantString->getValue(), $flags, $context); + } + + return $this->typeSpecifier->create( + $matchesArg->value, + TypeCombinator::union(...$matchedTypes), + $context, + false, + $scope, + ); + } + +} diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexShapeMatcher.php new file mode 100644 index 0000000000..aac7b827cc --- /dev/null +++ b/src/Type/Php/RegexShapeMatcher.php @@ -0,0 +1,111 @@ +|null $flags + */ + public function matchType(string $regex, ?int $flags, TypeSpecifierContext $context): Type + { + if ($flags !== null) { + $trickFlags = PREG_UNMATCHED_AS_NULL | $flags; + } else { + $trickFlags = PREG_UNMATCHED_AS_NULL; + } + + // add one capturing group to the end so all capture group keys + // are present in the $matches + // see https://3v4l.org/sOXbn, https://3v4l.org/3SdDM + $regex = preg_replace('~^(.)(.*)\K(\1\w*$)~', '|(?)$3', $regex); + + if ( + $regex === null + || @preg_match($regex, '', $matches, $trickFlags) === false + ) { + return new ArrayType(new MixedType(), new StringType()); + } + unset($matches[array_key_last($matches)]); + unset($matches['phpstan_named_capture_group_last']); + + $builder = ConstantArrayTypeBuilder::createEmpty(); + foreach (array_keys($matches) as $key) { + // atm we can't differentiate optional from mandatory groups based on the pattern. + // So we assume all are optional + $optional = true; + + $keyType = $this->getKeyType($key); + $valueType = $this->getValueType($flags ?? 0); + + if ($context->true() && $key === 0) { + $optional = false; + } + + $builder->setOffsetValueType( + $keyType, + $valueType, + $optional, + ); + } + + return $builder->getArray(); + } + + private function getKeyType(int|string $key): Type + { + if (is_string($key)) { + return new ConstantStringType($key); + } + + return new ConstantIntegerType($key); + } + + private function getValueType(int $flags): Type + { + $valueType = new StringType(); + $offsetType = IntegerRangeType::fromInterval(0, null); + if (($flags & PREG_UNMATCHED_AS_NULL) !== 0) { + $valueType = TypeCombinator::addNull($valueType); + // unmatched groups return -1 as offset + $offsetType = IntegerRangeType::fromInterval(-1, null); + } + + if (($flags & PREG_OFFSET_CAPTURE) !== 0) { + $builder = ConstantArrayTypeBuilder::createEmpty(); + + $builder->setOffsetValueType( + new ConstantIntegerType(0), + $valueType, + ); + $builder->setOffsetValueType( + new ConstantIntegerType(1), + $offsetType, + ); + + return $builder->getArray(); + } + + return $valueType; + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 3a1bf98033..80e9416042 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -179,6 +179,21 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-9542.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-9803.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/PhpDoc/data/bug-10594.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/set-type-type-specifying.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/mysqli_fetch_object.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10468.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6613.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10187.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10834.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10952.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10952b.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/case-insensitive-parent.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10893.php'); + + if (PHP_VERSION_ID >= 80200) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_match_shapes_php82.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_match_shapes.php'); } /** diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php new file mode 100644 index 0000000000..4de066a846 --- /dev/null +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -0,0 +1,65 @@ +', $matches); + + if (preg_match('/Price: (£|€)(\d+)/i', $s, $matches)) { + assertType('array{0: string, 1?: string, 2?: string}', $matches); + } + assertType('array', $matches); + + if (preg_match('/(a)(b)*(c)(d)*/', $s, $matches)) { + assertType('array{0: string, 1?: string, 2?: string, 3?: string, 4?: string}', $matches); + } + assertType('array', $matches); +} + +function doNonCapturingGroup(string $s): void { + if (preg_match('/Price: (?:£|€)(\d+)/', $s, $matches)) { + assertType('array{0: string, 1?: string}', $matches); + } + assertType('array', $matches); +} + +function doNamedSubpattern(string $s): void { + if (preg_match('/\w-(?P\d+)-(\w)/', $s, $matches)) { + assertType('array{0: string, num?: string, 1?: string, 2?: string}', $matches); + } + assertType('array', $matches); +} + +function doOffsetCapture(string $s): void { + if (preg_match('/(foo)(bar)(baz)/', $s, $matches, PREG_OFFSET_CAPTURE)) { + assertType('array{0: array{string, int<0, max>}, 1?: array{string, int<0, max>}, 2?: array{string, int<0, max>}, 3?: array{string, int<0, max>}}', $matches); + } + assertType('array}>', $matches); +} + +function doUnmatchedAsNull(string $s): void { + if (preg_match('/(foo)?(bar)?(baz)?/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { + assertType('array{0: string|null, 1?: string|null, 2?: string|null, 3?: string|null}', $matches); + } + assertType('array', $matches); +} + +function doOffsetCaptureWithUnmatchedNull(string $s): void { + // see https://3v4l.org/07rBO#v8.2.9 + if (preg_match('/(foo)(bar)(baz)/', $s, $matches, PREG_OFFSET_CAPTURE|PREG_UNMATCHED_AS_NULL)) { + assertType('array{0: array{null, -1}|array{string, int<0, max>}, 1?: array{null, -1}|array{string, int<0, max>}, 2?: array{null, -1}|array{string, int<0, max>}, 3?: array{null, -1}|array{string, int<0, max>}}', $matches); + } + assertType('array}>', $matches); +} + +function doUnknownFlags(string $s, int $flags): void { + if (preg_match('/(foo)(bar)(baz)/', 'foobarbaz', $matches, $flags)) { + assertType('array}|string|null>', $matches); + } + assertType('array}|string|null>', $matches); +} diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php b/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php new file mode 100644 index 0000000000..39e9af79f2 --- /dev/null +++ b/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php @@ -0,0 +1,13 @@ +\d+)-(\w)/n', $s, $matches)) { + assertType('array{0: string, num?: string, 1?: string}', $matches); + } + assertType('array', $matches); +} From 633a6617e1952be92183e6084e4a69fac34be794 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 15 May 2024 20:14:21 +0200 Subject: [PATCH 02/55] added tests --- tests/PHPStan/Analyser/data/preg_match_shapes.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index 4de066a846..a47675e3a8 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -15,6 +15,21 @@ function doMatch(string $s): void { } assertType('array', $matches); + if (preg_match(' /Price: (£|€)\d+/ i u', $s, $matches)) { + assertType('array{0: string, 1?: string}', $matches); + } + assertType('array', $matches); + + if (preg_match('(Price: (£|€))i', $s, $matches)) { + assertType('array{0: string, 1?: string}', $matches); + } + assertType('array', $matches); + + if (preg_match('_foo(.)\_i_i', $s, $matches)) { + assertType('array{0: string, 1?: string}', $matches); + } + assertType('array', $matches); + if (preg_match('/(a)(b)*(c)(d)*/', $s, $matches)) { assertType('array{0: string, 1?: string, 2?: string, 3?: string, 4?: string}', $matches); } From d922a1bc9f6bba9813c4ffffc447ac75e7ca555e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 15 May 2024 20:24:43 +0200 Subject: [PATCH 03/55] fix regex pattern Co-authored-by: Gregor Harlan --- src/Type/Php/RegexShapeMatcher.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexShapeMatcher.php index aac7b827cc..a2fd2066aa 100644 --- a/src/Type/Php/RegexShapeMatcher.php +++ b/src/Type/Php/RegexShapeMatcher.php @@ -37,7 +37,7 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont // add one capturing group to the end so all capture group keys // are present in the $matches // see https://3v4l.org/sOXbn, https://3v4l.org/3SdDM - $regex = preg_replace('~^(.)(.*)\K(\1\w*$)~', '|(?)$3', $regex); + $regex = preg_replace('~.[a-z\s]*$~i', '|(?)$0', $regex); if ( $regex === null From 1775ccecfaa6fd557009a355904a0fa43c1c2daf Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 15 May 2024 20:25:06 +0200 Subject: [PATCH 04/55] fix group name --- src/Type/Php/RegexShapeMatcher.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexShapeMatcher.php index a2fd2066aa..cc3b581785 100644 --- a/src/Type/Php/RegexShapeMatcher.php +++ b/src/Type/Php/RegexShapeMatcher.php @@ -37,7 +37,7 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont // add one capturing group to the end so all capture group keys // are present in the $matches // see https://3v4l.org/sOXbn, https://3v4l.org/3SdDM - $regex = preg_replace('~.[a-z\s]*$~i', '|(?)$0', $regex); + $regex = preg_replace('~.[a-z\s]*$~i', '|(?)$0', $regex); if ( $regex === null @@ -46,7 +46,7 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont return new ArrayType(new MixedType(), new StringType()); } unset($matches[array_key_last($matches)]); - unset($matches['phpstan_named_capture_group_last']); + unset($matches['phpstanNamedCaptureGroupLast']); $builder = ConstantArrayTypeBuilder::createEmpty(); foreach (array_keys($matches) as $key) { From 32b3a9c4c2c2e6b9c832709ac825b206ba14be50 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 16 May 2024 11:39:06 +0200 Subject: [PATCH 05/55] Fix PHP < 7.4 --- src/Php/PhpVersion.php | 8 ++++++++ src/Type/Php/PregMatchTypeSpecifyingExtension.php | 8 ++++++-- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 3 +++ .../Analyser/data/preg_match_shapes_php73.php | 12 ++++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/preg_match_shapes_php73.php diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 6a1fe74bf2..86f8c29195 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -287,4 +287,12 @@ public function supportsNeverReturnTypeInArrowFunction(): bool return $this->versionId >= 80200; } + // see https://www.php.net/manual/en/migration74.incompatible.php#migration74.incompatible.pcre + public function returnsPregUnmatchedCapturingGroups(): bool + { + // When PREG_UNMATCHED_AS_NULL mode is used, trailing unmatched capturing groups will now also be set to null (or [null, -1] if offset capture is enabled). + // This means that the size of the $matches will always be the same. + return $this->versionId >= 70400; + } + } diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php index 05c77cfc6d..e42444755c 100644 --- a/src/Type/Php/PregMatchTypeSpecifyingExtension.php +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -8,6 +8,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\FunctionTypeSpecifyingExtension; @@ -23,7 +24,10 @@ final class PregMatchTypeSpecifyingExtension implements FunctionTypeSpecifyingEx private TypeSpecifier $typeSpecifier; - public function __construct(private RegexShapeMatcher $regexShapeMatcher) + public function __construct( + private RegexShapeMatcher $regexShapeMatcher, + private PhpVersion $phpVersion, + ) { } @@ -48,7 +52,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $matchesArg = $args[2] ?? null; $flagsArg = $args[3] ?? null; - if ($patternArg === null || $matchesArg === null) { + if ($patternArg === null || $matchesArg === null || !$this->phpVersion->returnsPregUnmatchedCapturingGroups()) { return new SpecifiedTypes(); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 80e9416042..0faed007bf 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -190,6 +190,9 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/case-insensitive-parent.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10893.php'); + if (PHP_VERSION_ID < 70400) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_match_shapes_php73.php'); + } if (PHP_VERSION_ID >= 80200) { yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_match_shapes_php82.php'); } diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes_php73.php b/tests/PHPStan/Analyser/data/preg_match_shapes_php73.php new file mode 100644 index 0000000000..8c5728bc8e --- /dev/null +++ b/tests/PHPStan/Analyser/data/preg_match_shapes_php73.php @@ -0,0 +1,12 @@ +', $matches); + } + assertType('array', $matches); +} From a15d5bdade51b452277fb8094ca025af326e2fab Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 16 May 2024 11:43:33 +0200 Subject: [PATCH 06/55] fix tests per php version --- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 8 +++++--- tests/PHPStan/Analyser/data/preg_match_shapes.php | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 0faed007bf..48158ef4cf 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -188,15 +188,17 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10952.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10952b.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/case-insensitive-parent.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10893.php'); - + if (PHP_VERSION_ID < 70400) { yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_match_shapes_php73.php'); + } else { + yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_match_shapes.php'); } if (PHP_VERSION_ID >= 80200) { yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_match_shapes_php82.php'); } - yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_match_shapes.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10893.php'); } /** diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index a47675e3a8..2bfa1b25ad 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -73,7 +73,7 @@ function doOffsetCaptureWithUnmatchedNull(string $s): void { } function doUnknownFlags(string $s, int $flags): void { - if (preg_match('/(foo)(bar)(baz)/', 'foobarbaz', $matches, $flags)) { + if (preg_match('/(foo)(bar)(baz)/', $s, $matches, $flags)) { assertType('array}|string|null>', $matches); } assertType('array}|string|null>', $matches); From 6ae963952961e7972fa022831f8afd9794ca51ae Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 16 May 2024 12:21:16 +0200 Subject: [PATCH 07/55] add more tests Co-Authored-By: PrinsFrank <25006490+PrinsFrank@users.noreply.github.com> --- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 2 +- tests/PHPStan/Analyser/data/preg_match_shapes.php | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 48158ef4cf..76b4de6201 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -188,7 +188,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10952.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10952b.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/case-insensitive-parent.php'); - + if (PHP_VERSION_ID < 70400) { yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_match_shapes_php73.php'); } else { diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index 2bfa1b25ad..67c2dd01d7 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -78,3 +78,17 @@ function doUnknownFlags(string $s, int $flags): void { } assertType('array}|string|null>', $matches); } + +function doNonAutoCapturingFlag(string $s): void { + if (preg_match('/(\d+)/n', $s, $matches)) { + assertType('array{string}', $matches); + } + assertType('array', $matches); +} + +function doNonAutoCapturingModifier(string $s): void { + if (preg_match('/(?n)(\d+)/', $s, $matches)) { + assertType('array{string}', $matches); + } + assertType('array', $matches); +} From 3df46e84f6f8e5d014bd0154bda483a6ea89e0b8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 16 May 2024 13:21:22 +0200 Subject: [PATCH 08/55] added more tests Co-Authored-By: PrinsFrank <25006490+PrinsFrank@users.noreply.github.com> --- tests/PHPStan/Analyser/data/preg_match_shapes.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index 67c2dd01d7..5e23c0fde3 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -92,3 +92,17 @@ function doNonAutoCapturingModifier(string $s): void { } assertType('array', $matches); } + +function doMultipleAlternativeCaptureGroupsWithSameNameWithModifier(string $s): void { + if (preg_match('/(?J)(?[a-z]+)|(?[0-9]+)/', $s, $matches)) { + assertType('array{0: string, Foo?: string, 1?: string, 2?: string}', $matches); + } + assertType('array', $matches); +} + +function doMultipleConsecutiveCaptureGroupsWithSameNameWithModifier(string $s): void { + if (preg_match('/(?J)(?[a-z]+)|(?[0-9]+)/', $s, $matches)) { + assertType('array{0: string, Foo?: string, 1?: string, 2?: string}', $matches); + } + assertType('array', $matches); +} From c58a990abfb0ea5e729d8a017eb5371fbe92e833 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 16 May 2024 13:35:09 +0200 Subject: [PATCH 09/55] moved php 8.2 only test --- tests/PHPStan/Analyser/data/preg_match_shapes.php | 7 ------- .../PHPStan/Analyser/data/preg_match_shapes_php82.php | 10 ++++++++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index 5e23c0fde3..550c3f1402 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -79,13 +79,6 @@ function doUnknownFlags(string $s, int $flags): void { assertType('array}|string|null>', $matches); } -function doNonAutoCapturingFlag(string $s): void { - if (preg_match('/(\d+)/n', $s, $matches)) { - assertType('array{string}', $matches); - } - assertType('array', $matches); -} - function doNonAutoCapturingModifier(string $s): void { if (preg_match('/(?n)(\d+)/', $s, $matches)) { assertType('array{string}', $matches); diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php b/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php index 39e9af79f2..ae887fbdc7 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php @@ -11,3 +11,13 @@ function doOnlyNamedSubpattern(string $s): void { } assertType('array', $matches); } + +// n modifier captures only named groups +// https://php.watch/versions/8.2/preg-n-no-capture-modifier +function doNonAutoCapturingFlag(string $s): void { + if (preg_match('/(\d+)/n', $s, $matches)) { + assertType('array{string}', $matches); + } + assertType('array', $matches); +} + From 68ada66fa0a534d0f6b521077d792180afe64d55 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 08:55:21 +0200 Subject: [PATCH 10/55] patch Grammer.pp to support capturing groups --- composer.json | 3 +++ patches/Grammar.patch | 11 +++++++++++ 2 files changed, 14 insertions(+) create mode 100644 patches/Grammar.patch diff --git a/composer.json b/composer.json index ceb38f0794..77fca1bab8 100644 --- a/composer.json +++ b/composer.json @@ -82,6 +82,9 @@ "composer/ca-bundle": [ "patches/cloudflare-ca.patch" ], + "hoa/regex": [ + "patches/Grammar.patch" + ], "hoa/iterator": [ "patches/Buffer.patch", "patches/Lookahead.patch" diff --git a/patches/Grammar.patch b/patches/Grammar.patch new file mode 100644 index 0000000000..8baf670fb6 --- /dev/null +++ b/patches/Grammar.patch @@ -0,0 +1,11 @@ +--- Grammar.pp 2017-01-13 17:10:24 ++++ Grammar.pp 2024-05-18 08:49:19 +@@ -178,7 +178,7 @@ + capturing() + | literal() + +-capturing: ++#capturing: + ::comment_:: ? ::_comment:: #comment + | ( + ::named_capturing_:: ::_named_capturing:: #namedcapturing From 4f03514831a59a3f615f1dac44fa9a394618fea1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 08:57:48 +0200 Subject: [PATCH 11/55] ast --- src/Type/Php/RegexShapeMatcher.php | 57 ++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexShapeMatcher.php index cc3b581785..bee0567720 100644 --- a/src/Type/Php/RegexShapeMatcher.php +++ b/src/Type/Php/RegexShapeMatcher.php @@ -108,4 +108,61 @@ private function getValueType(int $flags): Type return $valueType; } + private function countNonOptionalGroups(string $regex):int { +// 1. Read the grammar. + $grammar = new Hoa\File\Read(__DIR__.'/conf/RegexGrammar.pp'); + +// 2. Load the compiler. + $compiler = Hoa\Compiler\Llk\Llk::load($grammar); + +// 3. Lex, parse and produce the AST. + $ast = $compiler->parse($regex); + + echo "-------------------\n\n\n"; + + var_dump($regex); + + // 4. Dump the result. + $dump = new Hoa\Compiler\Visitor\Dump(); + echo $dump->visit($ast); + + $groups = []; + return $this->walk($ast, $groups, 0, 0); + } + + private function walk(\Hoa\Compiler\Llk\TreeNode $ast, int $inAlternation, int $inOptionalQuantification): int + { + if ( + $ast->getId() === '#capturing' + && !($inAlternation > 0 || $inOptionalQuantification > 0) + ) { + return 1; + } + + if ($ast->getId() === '#alternation') { + $inAlternation++; + } + + if ($ast->getId() === '#quantification') { + $lastChild = $ast->getChild($ast->getChildrenNumber() - 1); + $value = $lastChild->getValue(); + + if ($value['token'] === 'n_to_m' && str_contains($value['value'], '{0,')) { + $inOptionalQuantification++; + } elseif ($value['token'] === 'zero_or_one') { + $inOptionalQuantification++; + } elseif ($value['token'] === 'zero_or_more') { + $inOptionalQuantification++; + } + } + + $count = 0; + foreach ($ast->getChildren() as $child) { + $count += $this->walk($child, $inAlternation, $inOptionalQuantification); + } + + return $count; + + } + } From 53ec8149fe76189ab9a2a8f91d259304a54a2940 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 09:26:52 +0200 Subject: [PATCH 12/55] fix --- .../Php/PregMatchTypeSpecifyingExtension.php | 10 +++- src/Type/Php/RegexShapeMatcher.php | 52 +++++++++---------- .../Analyser/data/preg_match_shapes.php | 14 +++-- 3 files changed, 44 insertions(+), 32 deletions(-) diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php index e42444755c..726ecd1734 100644 --- a/src/Type/Php/PregMatchTypeSpecifyingExtension.php +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -10,8 +10,11 @@ use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\FunctionTypeSpecifyingExtension; +use PHPStan\Type\MixedType; +use PHPStan\Type\StringType; use PHPStan\Type\TypeCombinator; use function count; use function in_array; @@ -78,7 +81,12 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $matchedTypes = []; foreach ($constantStrings as $constantString) { - $matchedTypes[] = $this->regexShapeMatcher->matchType($constantString->getValue(), $flags, $context); + $matched = $this->regexShapeMatcher->matchType($constantString->getValue(), $flags, $context); + if ($matched === null) { + return new SpecifiedTypes(); + } + + $matchedTypes[] = $matched; } return $this->typeSpecifier->create( diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexShapeMatcher.php index bee0567720..9118ce42cc 100644 --- a/src/Type/Php/RegexShapeMatcher.php +++ b/src/Type/Php/RegexShapeMatcher.php @@ -2,13 +2,13 @@ namespace PHPStan\Type\Php; +use Hoa\Compiler\Llk\TreeNode; +use Hoa\Exception\Exception as HoaException; use PHPStan\Analyser\TypeSpecifierContext; -use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\IntegerRangeType; -use PHPStan\Type\MixedType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -26,7 +26,7 @@ final class RegexShapeMatcher /** * @param int-mask|null $flags */ - public function matchType(string $regex, ?int $flags, TypeSpecifierContext $context): Type + public function matchType(string $regex, ?int $flags, TypeSpecifierContext $context): ?Type { if ($flags !== null) { $trickFlags = PREG_UNMATCHED_AS_NULL | $flags; @@ -37,28 +37,36 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont // add one capturing group to the end so all capture group keys // are present in the $matches // see https://3v4l.org/sOXbn, https://3v4l.org/3SdDM - $regex = preg_replace('~.[a-z\s]*$~i', '|(?)$0', $regex); + $captureGroupsRegex = preg_replace('~.[a-z\s]*$~i', '|(?)$0', $regex); if ( - $regex === null - || @preg_match($regex, '', $matches, $trickFlags) === false + $captureGroupsRegex === null + || @preg_match($captureGroupsRegex, '', $matches, $trickFlags) === false ) { - return new ArrayType(new MixedType(), new StringType()); + return null; } unset($matches[array_key_last($matches)]); unset($matches['phpstanNamedCaptureGroupLast']); + try { + // XXX hoa/regex throws on named capturing groups + $remainingNonOptionalGroupCount = $this->countNonOptionalGroups($regex); + } catch (HoaException $e) { + return null; + } + $builder = ConstantArrayTypeBuilder::createEmpty(); foreach (array_keys($matches) as $key) { - // atm we can't differentiate optional from mandatory groups based on the pattern. - // So we assume all are optional - $optional = true; - $keyType = $this->getKeyType($key); $valueType = $this->getValueType($flags ?? 0); - if ($context->true() && $key === 0) { - $optional = false; + // first item in matches contains the overall match. + if ($key === 0) { + $optional = $context->true(); + $valueType = TypeCombinator::removeNull($valueType); + } else { + $optional = $remainingNonOptionalGroupCount > 0; + $remainingNonOptionalGroupCount--; } $builder->setOffsetValueType( @@ -108,29 +116,21 @@ private function getValueType(int $flags): Type return $valueType; } + /** @throws HoaException */ private function countNonOptionalGroups(string $regex):int { // 1. Read the grammar. - $grammar = new Hoa\File\Read(__DIR__.'/conf/RegexGrammar.pp'); + $grammar = new \Hoa\File\Read('hoa://Library/Regex/Grammar.pp'); // 2. Load the compiler. - $compiler = Hoa\Compiler\Llk\Llk::load($grammar); + $compiler = \Hoa\Compiler\Llk\Llk::load($grammar); // 3. Lex, parse and produce the AST. $ast = $compiler->parse($regex); - echo "-------------------\n\n\n"; - - var_dump($regex); - - // 4. Dump the result. - $dump = new Hoa\Compiler\Visitor\Dump(); - echo $dump->visit($ast); - - $groups = []; - return $this->walk($ast, $groups, 0, 0); + return $this->walk($ast, 0, 0); } - private function walk(\Hoa\Compiler\Llk\TreeNode $ast, int $inAlternation, int $inOptionalQuantification): int + private function walk(TreeNode $ast, int $inAlternation, int $inOptionalQuantification): int { if ( $ast->getId() === '#capturing' diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index 550c3f1402..23ac939007 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -4,24 +4,27 @@ use function PHPStan\Testing\assertType; + function doMatch(string $s): void { if (preg_match('/Price: (£|€)\d+/', $s, $matches)) { - assertType('array{0: string, 1?: string}', $matches); + assertType('array{0: string, 1: string}', $matches); + } else { + assertType('array', $matches); } assertType('array', $matches); if (preg_match('/Price: (£|€)(\d+)/i', $s, $matches)) { - assertType('array{0: string, 1?: string, 2?: string}', $matches); + assertType('array{0: string, 1: string, 2: string}', $matches); } assertType('array', $matches); if (preg_match(' /Price: (£|€)\d+/ i u', $s, $matches)) { - assertType('array{0: string, 1?: string}', $matches); + assertType('array{0: string, 1: string}', $matches); } assertType('array', $matches); if (preg_match('(Price: (£|€))i', $s, $matches)) { - assertType('array{0: string, 1?: string}', $matches); + assertType('array{0: string, 1: string}', $matches); } assertType('array', $matches); @@ -95,7 +98,8 @@ function doMultipleAlternativeCaptureGroupsWithSameNameWithModifier(string $s): function doMultipleConsecutiveCaptureGroupsWithSameNameWithModifier(string $s): void { if (preg_match('/(?J)(?[a-z]+)|(?[0-9]+)/', $s, $matches)) { - assertType('array{0: string, Foo?: string, 1?: string, 2?: string}', $matches); + // could be assertType('array{0: string, Foo: string, 1: string}', $matches); + assertType('array', $matches); } assertType('array', $matches); } From d4b7cd0cdcd8650260799ce8646d0162c645767b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 09:34:10 +0200 Subject: [PATCH 13/55] fix --- tests/PHPStan/Analyser/data/preg_match_shapes.php | 5 +++-- tests/PHPStan/Analyser/data/preg_match_shapes_php82.php | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index 23ac939007..74d6997dfb 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -62,7 +62,7 @@ function doOffsetCapture(string $s): void { function doUnmatchedAsNull(string $s): void { if (preg_match('/(foo)?(bar)?(baz)?/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType('array{0: string|null, 1?: string|null, 2?: string|null, 3?: string|null}', $matches); + assertType('array{0: string, 1?: string|null, 2?: string|null, 3?: string|null}', $matches); } assertType('array', $matches); } @@ -91,7 +91,8 @@ function doNonAutoCapturingModifier(string $s): void { function doMultipleAlternativeCaptureGroupsWithSameNameWithModifier(string $s): void { if (preg_match('/(?J)(?[a-z]+)|(?[0-9]+)/', $s, $matches)) { - assertType('array{0: string, Foo?: string, 1?: string, 2?: string}', $matches); + // could be assertType('array{0: string, Foo: string, 1: string}', $matches); + assertType('array', $matches); } assertType('array', $matches); } diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php b/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php index ae887fbdc7..3ff6c855c1 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php @@ -7,7 +7,8 @@ function doOnlyNamedSubpattern(string $s): void { // n modifier captures only named groups if (preg_match('/(\w)-(?P\d+)-(\w)/n', $s, $matches)) { - assertType('array{0: string, num?: string, 1?: string}', $matches); + // could be assertType('array{0: string, num: string, 1: string, 2: string, 3: string}', $matches); + assertType('array', $matches); } assertType('array', $matches); } From 43d496df8b5b88aa6479d71acb920908fed5f20b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 10:00:38 +0200 Subject: [PATCH 14/55] fix --- src/Type/Php/RegexShapeMatcher.php | 23 ++++++++++++------- .../Analyser/data/preg_match_shapes.php | 15 ++++++------ .../Analyser/data/preg_match_shapes_php82.php | 8 +++---- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexShapeMatcher.php index 9118ce42cc..d37952229d 100644 --- a/src/Type/Php/RegexShapeMatcher.php +++ b/src/Type/Php/RegexShapeMatcher.php @@ -33,6 +33,7 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont } else { $trickFlags = PREG_UNMATCHED_AS_NULL; } + $trickFlags = PREG_UNMATCHED_AS_NULL; // add one capturing group to the end so all capture group keys // are present in the $matches @@ -56,21 +57,27 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont } $builder = ConstantArrayTypeBuilder::createEmpty(); + $valueType = $this->getValueType($flags ?? 0); + + // first item in matches contains the overall match. + $builder->setOffsetValueType( + $this->getKeyType(0), + $valueType, + !$context->true() + ); + foreach (array_keys($matches) as $key) { - $keyType = $this->getKeyType($key); - $valueType = $this->getValueType($flags ?? 0); + if ($key === 0) continue; - // first item in matches contains the overall match. - if ($key === 0) { - $optional = $context->true(); - $valueType = TypeCombinator::removeNull($valueType); + if (!$context->true()) { + $optional = true; } else { - $optional = $remainingNonOptionalGroupCount > 0; + $optional = $remainingNonOptionalGroupCount <= 0; $remainingNonOptionalGroupCount--; } $builder->setOffsetValueType( - $keyType, + $this->getKeyType($key), $valueType, $optional, ); diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index 74d6997dfb..801945bb73 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -7,19 +7,19 @@ function doMatch(string $s): void { if (preg_match('/Price: (£|€)\d+/', $s, $matches)) { - assertType('array{0: string, 1: string}', $matches); + assertType('array{string, string}', $matches); } else { assertType('array', $matches); } assertType('array', $matches); if (preg_match('/Price: (£|€)(\d+)/i', $s, $matches)) { - assertType('array{0: string, 1: string, 2: string}', $matches); + assertType('array{string, string, string}', $matches); } assertType('array', $matches); if (preg_match(' /Price: (£|€)\d+/ i u', $s, $matches)) { - assertType('array{0: string, 1: string}', $matches); + assertType('array{string, string}', $matches); } assertType('array', $matches); @@ -34,28 +34,29 @@ function doMatch(string $s): void { assertType('array', $matches); if (preg_match('/(a)(b)*(c)(d)*/', $s, $matches)) { - assertType('array{0: string, 1?: string, 2?: string, 3?: string, 4?: string}', $matches); + assertType('array{0: string, 1: string, 2: string, 3?: string, 4?: string}', $matches); } assertType('array', $matches); } function doNonCapturingGroup(string $s): void { if (preg_match('/Price: (?:£|€)(\d+)/', $s, $matches)) { - assertType('array{0: string, 1?: string}', $matches); + assertType('array{0: string, 1: string}', $matches); } assertType('array', $matches); } function doNamedSubpattern(string $s): void { if (preg_match('/\w-(?P\d+)-(\w)/', $s, $matches)) { - assertType('array{0: string, num?: string, 1?: string, 2?: string}', $matches); + // could be assertType('array{0: string, num: string, 1: string, 2: string, 3: string}', $matches); + assertType('array', $matches); } assertType('array', $matches); } function doOffsetCapture(string $s): void { if (preg_match('/(foo)(bar)(baz)/', $s, $matches, PREG_OFFSET_CAPTURE)) { - assertType('array{0: array{string, int<0, max>}, 1?: array{string, int<0, max>}, 2?: array{string, int<0, max>}, 3?: array{string, int<0, max>}}', $matches); + assertType('array{array{string, int<0, max>}, array{string, int<0, max>}, array{string, int<0, max>}, array{string, int<0, max>}}', $matches); } assertType('array}>', $matches); } diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php b/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php index 3ff6c855c1..014ff19c26 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php @@ -15,10 +15,10 @@ function doOnlyNamedSubpattern(string $s): void { // n modifier captures only named groups // https://php.watch/versions/8.2/preg-n-no-capture-modifier -function doNonAutoCapturingFlag(string $s): void { - if (preg_match('/(\d+)/n', $s, $matches)) { - assertType('array{string}', $matches); +function doOffsetCapture(string $s): void { + if (preg_match('/(foo)(bar)(baz)/', $s, $matches, PREG_OFFSET_CAPTURE)) { + assertType('array{array{string, int<0, max>}, array{string, int<0, max>}, array{string, int<0, max>}, array{string, int<0, max>}}', $matches); } - assertType('array', $matches); + assertType('array}>', $matches); } From 887e6a527ecdae9d7ae115beea067ac9ad7baaea Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 10:05:25 +0200 Subject: [PATCH 15/55] fix --- src/Type/Php/RegexShapeMatcher.php | 2 +- tests/PHPStan/Analyser/data/preg_match_shapes.php | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexShapeMatcher.php index d37952229d..45d9ff0671 100644 --- a/src/Type/Php/RegexShapeMatcher.php +++ b/src/Type/Php/RegexShapeMatcher.php @@ -62,7 +62,7 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont // first item in matches contains the overall match. $builder->setOffsetValueType( $this->getKeyType(0), - $valueType, + TypeCombinator::removeNull($valueType), !$context->true() ); diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index 801945bb73..c45b79cea9 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -24,12 +24,12 @@ function doMatch(string $s): void { assertType('array', $matches); if (preg_match('(Price: (£|€))i', $s, $matches)) { - assertType('array{0: string, 1: string}', $matches); + assertType('array{string, string}', $matches); } assertType('array', $matches); if (preg_match('_foo(.)\_i_i', $s, $matches)) { - assertType('array{0: string, 1?: string}', $matches); + assertType('array{string, string}', $matches); } assertType('array', $matches); @@ -41,7 +41,7 @@ function doMatch(string $s): void { function doNonCapturingGroup(string $s): void { if (preg_match('/Price: (?:£|€)(\d+)/', $s, $matches)) { - assertType('array{0: string, 1: string}', $matches); + assertType('array{string, string}', $matches); } assertType('array', $matches); } @@ -71,7 +71,7 @@ function doUnmatchedAsNull(string $s): void { function doOffsetCaptureWithUnmatchedNull(string $s): void { // see https://3v4l.org/07rBO#v8.2.9 if (preg_match('/(foo)(bar)(baz)/', $s, $matches, PREG_OFFSET_CAPTURE|PREG_UNMATCHED_AS_NULL)) { - assertType('array{0: array{null, -1}|array{string, int<0, max>}, 1?: array{null, -1}|array{string, int<0, max>}, 2?: array{null, -1}|array{string, int<0, max>}, 3?: array{null, -1}|array{string, int<0, max>}}', $matches); + assertType('array{array{null, -1}|array{string, int<0, max>}, array{null, -1}|array{string, int<0, max>}, array{null, -1}|array{string, int<0, max>}, array{null, -1}|array{string, int<0, max>}}', $matches); } assertType('array}>', $matches); } @@ -85,7 +85,8 @@ function doUnknownFlags(string $s, int $flags): void { function doNonAutoCapturingModifier(string $s): void { if (preg_match('/(?n)(\d+)/', $s, $matches)) { - assertType('array{string}', $matches); + // could be assertType('array{string}', $matches); + assertType('array', $matches); } assertType('array', $matches); } From c361855d854650cc941a48803574abbe4dbbc47c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 10:07:53 +0200 Subject: [PATCH 16/55] fix --- .../Php/PregMatchTypeSpecifyingExtension.php | 3 -- src/Type/Php/RegexShapeMatcher.php | 36 +++++++++---------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php index 726ecd1734..0983a7ae10 100644 --- a/src/Type/Php/PregMatchTypeSpecifyingExtension.php +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -10,11 +10,8 @@ use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\FunctionTypeSpecifyingExtension; -use PHPStan\Type\MixedType; -use PHPStan\Type\StringType; use PHPStan\Type\TypeCombinator; use function count; use function in_array; diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexShapeMatcher.php index 45d9ff0671..24ccd3b137 100644 --- a/src/Type/Php/RegexShapeMatcher.php +++ b/src/Type/Php/RegexShapeMatcher.php @@ -2,8 +2,10 @@ namespace PHPStan\Type\Php; +use Hoa\Compiler\Llk\Llk; use Hoa\Compiler\Llk\TreeNode; use Hoa\Exception\Exception as HoaException; +use Hoa\File\Read; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantIntegerType; @@ -17,6 +19,7 @@ use function is_string; use function preg_match; use function preg_replace; +use function str_contains; use const PREG_OFFSET_CAPTURE; use const PREG_UNMATCHED_AS_NULL; @@ -28,13 +31,6 @@ final class RegexShapeMatcher */ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $context): ?Type { - if ($flags !== null) { - $trickFlags = PREG_UNMATCHED_AS_NULL | $flags; - } else { - $trickFlags = PREG_UNMATCHED_AS_NULL; - } - $trickFlags = PREG_UNMATCHED_AS_NULL; - // add one capturing group to the end so all capture group keys // are present in the $matches // see https://3v4l.org/sOXbn, https://3v4l.org/3SdDM @@ -42,7 +38,7 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont if ( $captureGroupsRegex === null - || @preg_match($captureGroupsRegex, '', $matches, $trickFlags) === false + || @preg_match($captureGroupsRegex, '', $matches, PREG_UNMATCHED_AS_NULL) === false ) { return null; } @@ -52,7 +48,7 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont try { // XXX hoa/regex throws on named capturing groups $remainingNonOptionalGroupCount = $this->countNonOptionalGroups($regex); - } catch (HoaException $e) { + } catch (HoaException) { return null; } @@ -63,11 +59,13 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont $builder->setOffsetValueType( $this->getKeyType(0), TypeCombinator::removeNull($valueType), - !$context->true() + !$context->true(), ); foreach (array_keys($matches) as $key) { - if ($key === 0) continue; + if ($key === 0) { + continue; + } if (!$context->true()) { $optional = true; @@ -124,15 +122,16 @@ private function getValueType(int $flags): Type } /** @throws HoaException */ - private function countNonOptionalGroups(string $regex):int { -// 1. Read the grammar. - $grammar = new \Hoa\File\Read('hoa://Library/Regex/Grammar.pp'); + private function countNonOptionalGroups(string $regex): int + { + // 1. Read the grammar. + $grammar = new Read('hoa://Library/Regex/Grammar.pp'); -// 2. Load the compiler. - $compiler = \Hoa\Compiler\Llk\Llk::load($grammar); + // 2. Load the compiler. + $compiler = Llk::load($grammar); -// 3. Lex, parse and produce the AST. - $ast = $compiler->parse($regex); + // 3. Lex, parse and produce the AST. + $ast = $compiler->parse($regex); return $this->walk($ast, 0, 0); } @@ -169,7 +168,6 @@ private function walk(TreeNode $ast, int $inAlternation, int $inOptionalQuantifi } return $count; - } } From 25e7940f41f417d72dad5eb24a71356994fe8598 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 10:11:51 +0200 Subject: [PATCH 17/55] refactor --- src/Type/Php/RegexShapeMatcher.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexShapeMatcher.php index 24ccd3b137..a532238dfd 100644 --- a/src/Type/Php/RegexShapeMatcher.php +++ b/src/Type/Php/RegexShapeMatcher.php @@ -55,15 +55,15 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont $builder = ConstantArrayTypeBuilder::createEmpty(); $valueType = $this->getValueType($flags ?? 0); - // first item in matches contains the overall match. - $builder->setOffsetValueType( - $this->getKeyType(0), - TypeCombinator::removeNull($valueType), - !$context->true(), - ); - foreach (array_keys($matches) as $key) { if ($key === 0) { + // first item in matches contains the overall match. + $builder->setOffsetValueType( + $this->getKeyType($key), + TypeCombinator::removeNull($valueType), + !$context->true(), + ); + continue; } @@ -133,10 +133,10 @@ private function countNonOptionalGroups(string $regex): int // 3. Lex, parse and produce the AST. $ast = $compiler->parse($regex); - return $this->walk($ast, 0, 0); + return $this->walkRegexAst($ast, 0, 0); } - private function walk(TreeNode $ast, int $inAlternation, int $inOptionalQuantification): int + private function walkRegexAst(TreeNode $ast, int $inAlternation, int $inOptionalQuantification): int { if ( $ast->getId() === '#capturing' @@ -164,7 +164,7 @@ private function walk(TreeNode $ast, int $inAlternation, int $inOptionalQuantifi $count = 0; foreach ($ast->getChildren() as $child) { - $count += $this->walk($child, $inAlternation, $inOptionalQuantification); + $count += $this->walkRegexAst($child, $inAlternation, $inOptionalQuantification); } return $count; From 8c619689528127d779d445b22c7e7100db1f7ee2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 10:20:06 +0200 Subject: [PATCH 18/55] Update preg_match_shapes_php82.php --- .../Analyser/data/preg_match_shapes_php82.php | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php b/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php index 014ff19c26..70e668717c 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes_php82.php @@ -4,21 +4,17 @@ use function PHPStan\Testing\assertType; -function doOnlyNamedSubpattern(string $s): void { - // n modifier captures only named groups - if (preg_match('/(\w)-(?P\d+)-(\w)/n', $s, $matches)) { - // could be assertType('array{0: string, num: string, 1: string, 2: string, 3: string}', $matches); - assertType('array', $matches); +// n modifier captures only named groups +// https://php.watch/versions/8.2/preg-n-no-capture-modifier +function doNonAutoCapturingFlag(string $s): void { + if (preg_match('/(\d+)/n', $s, $matches)) { + assertType('array{string}', $matches); } assertType('array', $matches); -} -// n modifier captures only named groups -// https://php.watch/versions/8.2/preg-n-no-capture-modifier -function doOffsetCapture(string $s): void { - if (preg_match('/(foo)(bar)(baz)/', $s, $matches, PREG_OFFSET_CAPTURE)) { - assertType('array{array{string, int<0, max>}, array{string, int<0, max>}, array{string, int<0, max>}, array{string, int<0, max>}}', $matches); + if (preg_match('/(\d+)(?P\d+)/n', $s, $matches)) { + // could be assertType('array{0: string, num: string, 1: string}', $matches); + assertType('array', $matches); } - assertType('array}>', $matches); + assertType('array', $matches); } - From 1c52274d0ab5e4a10298734dc694a64bde6b6bf7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 10:28:24 +0200 Subject: [PATCH 19/55] fix build --- src/Type/Php/RegexShapeMatcher.php | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexShapeMatcher.php index a532238dfd..8b6eaace93 100644 --- a/src/Type/Php/RegexShapeMatcher.php +++ b/src/Type/Php/RegexShapeMatcher.php @@ -2,9 +2,9 @@ namespace PHPStan\Type\Php; +use Hoa\Compiler\Exception\Exception; use Hoa\Compiler\Llk\Llk; use Hoa\Compiler\Llk\TreeNode; -use Hoa\Exception\Exception as HoaException; use Hoa\File\Read; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; @@ -45,10 +45,10 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont unset($matches[array_key_last($matches)]); unset($matches['phpstanNamedCaptureGroupLast']); - try { - // XXX hoa/regex throws on named capturing groups - $remainingNonOptionalGroupCount = $this->countNonOptionalGroups($regex); - } catch (HoaException) { + // XXX hoa/regex does not support named capturing groups + $remainingNonOptionalGroupCount = $this->countNonOptionalGroups($regex); + if ($remainingNonOptionalGroupCount === null) { + // regex could not be parsed by Hoa/Regex return null; } @@ -121,17 +121,15 @@ private function getValueType(int $flags): Type return $valueType; } - /** @throws HoaException */ - private function countNonOptionalGroups(string $regex): int + private function countNonOptionalGroups(string $regex): ?int { - // 1. Read the grammar. - $grammar = new Read('hoa://Library/Regex/Grammar.pp'); - - // 2. Load the compiler. - $compiler = Llk::load($grammar); - - // 3. Lex, parse and produce the AST. - $ast = $compiler->parse($regex); + /** @throws void */ + $parser = Llk::load(new Read('hoa://Library/Regex/Grammar.pp')); + try { + $ast = $parser->parse($regex); + } catch ( Exception) { // @phpstan-ignore catch.notThrowable + return null; + } return $this->walkRegexAst($ast, 0, 0); } From 96780ce3a3fbefede50d4acb32f4d58e331ec354 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 11:08:09 +0200 Subject: [PATCH 20/55] Update composer.lock --- composer.lock | 691 +++++++++++++++++++++++++++++--------------------- 1 file changed, 396 insertions(+), 295 deletions(-) diff --git a/composer.lock b/composer.lock index f18481ad71..5667f7eade 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "db09e230b5029b6247349873fc13819a", + "content-hash": "954daf42f3b55135a22511443c88b3ec", "packages": [ { "name": "clue/ndjson-react", @@ -72,28 +72,28 @@ }, { "name": "composer/ca-bundle", - "version": "1.5.0", + "version": "1.3.7", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99" + "reference": "76e46335014860eec1aa5a724799a00a2e47cc85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", - "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/76e46335014860eec1aa5a724799a00a2e47cc85", + "reference": "76e46335014860eec1aa5a724799a00a2e47cc85", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^7.2 || ^8.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.10", + "phpstan/phpstan": "^0.12.55", "psr/log": "^1.0", "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0" }, "type": "library", "extra": { @@ -128,7 +128,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.0" + "source": "https://github.com/composer/ca-bundle/tree/1.3.7" }, "funding": [ { @@ -144,7 +144,7 @@ "type": "tidelift" } ], - "time": "2024-03-15T14:00:32+00:00" + "time": "2023-08-30T09:31:38+00:00" }, { "name": "composer/pcre", @@ -217,87 +217,6 @@ ], "time": "2024-03-19T10:26:25+00:00" }, - { - "name": "composer/semver", - "version": "3.4.0", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.0" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2023-08-31T09:50:34+00:00" - }, { "name": "composer/xdebug-handler", "version": "3.0.5", @@ -1434,12 +1353,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "cf7e447ddfa7f0cbab0c1dd38392f0cb05f9881c" + "reference": "80e1e4810b9b72f2ce3871a344762e3a0ea87423" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/cf7e447ddfa7f0cbab0c1dd38392f0cb05f9881c", - "reference": "cf7e447ddfa7f0cbab0c1dd38392f0cb05f9881c", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/80e1e4810b9b72f2ce3871a344762e3a0ea87423", + "reference": "80e1e4810b9b72f2ce3871a344762e3a0ea87423", "shasum": "" }, "require-dev": { @@ -1474,7 +1393,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-06-17T19:18:18+00:00" + "time": "2024-04-29T13:21:12+00:00" }, { "name": "nette/bootstrap", @@ -2176,16 +2095,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.25.0.12", + "version": "6.25.0.11", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "e8f880bf854d9a6737ac49ce67f58952d3b59c3c" + "reference": "7fcef699523b59a2bc6cfd7966ee7c17d285b9ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/e8f880bf854d9a6737ac49ce67f58952d3b59c3c", - "reference": "e8f880bf854d9a6737ac49ce67f58952d3b59c3c", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/7fcef699523b59a2bc6cfd7966ee7c17d285b9ae", + "reference": "7fcef699523b59a2bc6cfd7966ee7c17d285b9ae", "shasum": "" }, "require": { @@ -2242,9 +2161,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.12" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.11" }, - "time": "2024-06-04T07:28:56+00:00" + "time": "2024-05-13T13:09:26+00:00" }, { "name": "phpstan/php-8-stubs", @@ -2280,16 +2199,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.29.1", + "version": "1.29.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" + "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/536889f2b340489d328f5ffb7b02bb6b183ddedc", + "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc", "shasum": "" }, "require": { @@ -2321,9 +2240,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.0" }, - "time": "2024-05-31T08:52:43+00:00" + "time": "2024-05-06T12:04:23+00:00" }, { "name": "psr/container", @@ -2478,16 +2397,16 @@ }, { "name": "react/async", - "version": "v3.2.0", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/reactphp/async.git", - "reference": "bc3ef672b33e95bf814fe8377731e46888ed4b54" + "reference": "3c3b812be77aec14bf8300b052ba589c9a5bc95b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/async/zipball/bc3ef672b33e95bf814fe8377731e46888ed4b54", - "reference": "bc3ef672b33e95bf814fe8377731e46888ed4b54", + "url": "https://api.github.com/repos/reactphp/async/zipball/3c3b812be77aec14bf8300b052ba589c9a5bc95b", + "reference": "3c3b812be77aec14bf8300b052ba589c9a5bc95b", "shasum": "" }, "require": { @@ -2496,8 +2415,7 @@ "react/promise": "^3.0 || ^2.8 || ^1.2.1" }, "require-dev": { - "phpstan/phpstan": "1.10.39 || 1.4.10", - "phpunit/phpunit": "^9.6 || ^7.5" + "phpunit/phpunit": "^9.3 || ^7.5" }, "type": "library", "autoload": { @@ -2538,15 +2456,19 @@ ], "support": { "issues": "https://github.com/reactphp/async/issues", - "source": "https://github.com/reactphp/async/tree/v3.2.0" + "source": "https://github.com/reactphp/async/tree/v3.0.0" }, "funding": [ { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" + "url": "https://github.com/WyriHaximus", + "type": "github" + }, + { + "url": "https://github.com/clue", + "type": "github" } ], - "time": "2023-11-22T16:21:11+00:00" + "time": "2022-07-11T14:17:23+00:00" }, { "name": "react/cache", @@ -2701,33 +2623,33 @@ }, { "name": "react/dns", - "version": "v1.13.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/reactphp/dns.git", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" + "reference": "a5427e7dfa47713e438016905605819d101f238c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "url": "https://api.github.com/repos/reactphp/dns/zipball/a5427e7dfa47713e438016905605819d101f238c", + "reference": "a5427e7dfa47713e438016905605819d101f238c", "shasum": "" }, "require": { "php": ">=5.3.0", "react/cache": "^1.0 || ^0.6 || ^0.5", "react/event-loop": "^1.2", - "react/promise": "^3.2 || ^2.7 || ^1.2.1" + "react/promise": "^3.0 || ^2.7 || ^1.2.1", + "react/promise-timer": "^1.9" }, "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4.3 || ^3 || ^2", - "react/promise-timer": "^1.11" + "phpunit/phpunit": "^9.3 || ^4.8.35", + "react/async": "^4 || ^3 || ^2" }, "type": "library", "autoload": { "psr-4": { - "React\\Dns\\": "src/" + "React\\Dns\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2765,28 +2687,32 @@ ], "support": { "issues": "https://github.com/reactphp/dns/issues", - "source": "https://github.com/reactphp/dns/tree/v1.13.0" + "source": "https://github.com/reactphp/dns/tree/v1.10.0" }, "funding": [ { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" + "url": "https://github.com/WyriHaximus", + "type": "github" + }, + { + "url": "https://github.com/clue", + "type": "github" } ], - "time": "2024-06-13T14:18:03+00:00" + "time": "2022-09-08T12:22:46+00:00" }, { "name": "react/event-loop", - "version": "v1.5.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/reactphp/event-loop.git", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + "reference": "6e7e587714fff7a83dcc7025aee42ab3b265ae05" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/6e7e587714fff7a83dcc7025aee42ab3b265ae05", + "reference": "6e7e587714fff7a83dcc7025aee42ab3b265ae05", "shasum": "" }, "require": { @@ -2837,7 +2763,7 @@ ], "support": { "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" + "source": "https://github.com/reactphp/event-loop/tree/v1.4.0" }, "funding": [ { @@ -2845,20 +2771,20 @@ "type": "open_collective" } ], - "time": "2023-11-13T13:48:05+00:00" + "time": "2023-05-05T10:11:24+00:00" }, { "name": "react/http", - "version": "v1.10.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/reactphp/http.git", - "reference": "8111281ee57f22b7194f5dba225e609ba7ce4d20" + "reference": "bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/http/zipball/8111281ee57f22b7194f5dba225e609ba7ce4d20", - "reference": "8111281ee57f22b7194f5dba225e609ba7ce4d20", + "url": "https://api.github.com/repos/reactphp/http/zipball/bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0", + "reference": "bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0", "shasum": "" }, "require": { @@ -2869,13 +2795,14 @@ "react/event-loop": "^1.2", "react/promise": "^3 || ^2.3 || ^1.2.1", "react/socket": "^1.12", - "react/stream": "^1.2" + "react/stream": "^1.2", + "ringcentral/psr7": "^1.2" }, "require-dev": { "clue/http-proxy-react": "^1.8", "clue/reactphp-ssh-proxy": "^1.4", "clue/socks-react": "^1.4", - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", "react/async": "^4 || ^3 || ^2", "react/promise-stream": "^1.4", "react/promise-timer": "^1.9" @@ -2928,7 +2855,7 @@ ], "support": { "issues": "https://github.com/reactphp/http/issues", - "source": "https://github.com/reactphp/http/tree/v1.10.0" + "source": "https://github.com/reactphp/http/tree/v1.9.0" }, "funding": [ { @@ -2936,28 +2863,27 @@ "type": "open_collective" } ], - "time": "2024-03-27T17:20:46+00:00" + "time": "2023-04-26T10:29:24+00:00" }, { "name": "react/promise", - "version": "v3.2.0", + "version": "v2.10.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + "reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "url": "https://api.github.com/repos/reactphp/promise/zipball/f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38", + "reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38", "shasum": "" }, "require": { - "php": ">=7.1.0" + "php": ">=5.4.0" }, "require-dev": { - "phpstan/phpstan": "1.10.39 || 1.4.10", - "phpunit/phpunit": "^9.6 || ^7.5" + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.36" }, "type": "library", "autoload": { @@ -3001,7 +2927,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.2.0" + "source": "https://github.com/reactphp/promise/tree/v2.10.0" }, "funding": [ { @@ -3009,40 +2935,123 @@ "type": "open_collective" } ], - "time": "2024-05-24T10:39:05+00:00" + "time": "2023-05-02T15:15:43+00:00" + }, + { + "name": "react/promise-timer", + "version": "v1.9.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise-timer.git", + "reference": "aa7a73c74b8d8c0f622f5982ff7b0351bc29e495" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/aa7a73c74b8d8c0f622f5982ff7b0351bc29e495", + "reference": "aa7a73c74b8d8c0f622f5982ff7b0351bc29e495", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/event-loop": "^1.2", + "react/promise": "^3.0 || ^2.7.0 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\Timer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.", + "homepage": "https://github.com/reactphp/promise-timer", + "keywords": [ + "async", + "event-loop", + "promise", + "reactphp", + "timeout", + "timer" + ], + "support": { + "issues": "https://github.com/reactphp/promise-timer/issues", + "source": "https://github.com/reactphp/promise-timer/tree/v1.9.0" + }, + "funding": [ + { + "url": "https://github.com/WyriHaximus", + "type": "github" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-06-13T13:41:03+00:00" }, { "name": "react/socket", - "version": "v1.15.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/reactphp/socket.git", - "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038" + "reference": "81e1b4d7f5450ebd8d2e9a95bb008bb15ca95a7b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/216d3aec0b87f04a40ca04f481e6af01bdd1d038", - "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038", + "url": "https://api.github.com/repos/reactphp/socket/zipball/81e1b4d7f5450ebd8d2e9a95bb008bb15ca95a7b", + "reference": "81e1b4d7f5450ebd8d2e9a95bb008bb15ca95a7b", "shasum": "" }, "require": { "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "php": ">=5.3.0", - "react/dns": "^1.11", + "react/dns": "^1.8", "react/event-loop": "^1.2", "react/promise": "^3 || ^2.6 || ^1.2.1", + "react/promise-timer": "^1.9", "react/stream": "^1.2" }, "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", "react/async": "^4 || ^3 || ^2", - "react/promise-stream": "^1.4", - "react/promise-timer": "^1.10" + "react/promise-stream": "^1.4" }, "type": "library", "autoload": { "psr-4": { - "React\\Socket\\": "src/" + "React\\Socket\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3081,28 +3090,32 @@ ], "support": { "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.15.0" + "source": "https://github.com/reactphp/socket/tree/v1.12.0" }, "funding": [ { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" + "url": "https://github.com/WyriHaximus", + "type": "github" + }, + { + "url": "https://github.com/clue", + "type": "github" } ], - "time": "2023-12-15T11:02:10+00:00" + "time": "2022-08-25T12:32:25+00:00" }, { "name": "react/stream", - "version": "v1.3.0", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/reactphp/stream.git", - "reference": "6fbc9672905c7d5a885f2da2fc696f65840f4a66" + "reference": "7a423506ee1903e89f1e08ec5f0ed430ff784ae9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/6fbc9672905c7d5a885f2da2fc696f65840f4a66", - "reference": "6fbc9672905c7d5a885f2da2fc696f65840f4a66", + "url": "https://api.github.com/repos/reactphp/stream/zipball/7a423506ee1903e89f1e08ec5f0ed430ff784ae9", + "reference": "7a423506ee1903e89f1e08ec5f0ed430ff784ae9", "shasum": "" }, "require": { @@ -3112,12 +3125,12 @@ }, "require-dev": { "clue/stream-filter": "~1.2", - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" }, "type": "library", "autoload": { "psr-4": { - "React\\Stream\\": "src/" + "React\\Stream\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3159,28 +3172,93 @@ ], "support": { "issues": "https://github.com/reactphp/stream/issues", - "source": "https://github.com/reactphp/stream/tree/v1.3.0" + "source": "https://github.com/reactphp/stream/tree/v1.2.0" }, "funding": [ { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" + "url": "https://github.com/WyriHaximus", + "type": "github" + }, + { + "url": "https://github.com/clue", + "type": "github" } ], - "time": "2023-06-16T10:52:11+00:00" + "time": "2021-07-11T12:37:55+00:00" + }, + { + "name": "ringcentral/psr7", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/ringcentral/psr7.git", + "reference": "360faaec4b563958b673fb52bbe94e37f14bc686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ringcentral/psr7/zipball/360faaec4b563958b673fb52bbe94e37f14bc686", + "reference": "360faaec4b563958b673fb52bbe94e37f14bc686", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "RingCentral\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "PSR-7 message implementation", + "keywords": [ + "http", + "message", + "stream", + "uri" + ], + "support": { + "source": "https://github.com/ringcentral/psr7/tree/master" + }, + "time": "2018-05-29T20:21:04+00:00" }, { "name": "symfony/console", - "version": "v5.4.40", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "aa73115c0c24220b523625bfcfa655d7d73662dd" + "reference": "f4f71842f24c2023b91237c72a365306f3c58827" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/aa73115c0c24220b523625bfcfa655d7d73662dd", - "reference": "aa73115c0c24220b523625bfcfa655d7d73662dd", + "url": "https://api.github.com/repos/symfony/console/zipball/f4f71842f24c2023b91237c72a365306f3c58827", + "reference": "f4f71842f24c2023b91237c72a365306f3c58827", "shasum": "" }, "require": { @@ -3250,7 +3328,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.40" + "source": "https://github.com/symfony/console/tree/v5.4.28" }, "funding": [ { @@ -3266,20 +3344,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2023-08-07T06:12:30+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.0", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", "shasum": "" }, "require": { @@ -3288,7 +3366,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -3317,7 +3395,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" }, "funding": [ { @@ -3333,20 +3411,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/finder", - "version": "v5.4.40", + "version": "v5.4.27", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "f51cff4687547641c7d8180d74932ab40b2205ce" + "reference": "ff4bce3c33451e7ec778070e45bd23f74214cd5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/f51cff4687547641c7d8180d74932ab40b2205ce", - "reference": "f51cff4687547641c7d8180d74932ab40b2205ce", + "url": "https://api.github.com/repos/symfony/finder/zipball/ff4bce3c33451e7ec778070e45bd23f74214cd5d", + "reference": "ff4bce3c33451e7ec778070e45bd23f74214cd5d", "shasum": "" }, "require": { @@ -3380,7 +3458,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.40" + "source": "https://github.com/symfony/finder/tree/v5.4.27" }, "funding": [ { @@ -3396,20 +3474,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2023-07-31T08:02:31+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.29.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", "shasum": "" }, "require": { @@ -3423,6 +3501,9 @@ }, "type": "library", "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -3459,7 +3540,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" }, "funding": [ { @@ -3475,20 +3556,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.29.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + "reference": "875e90aeea2777b6f135677f618529449334a612" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", + "reference": "875e90aeea2777b6f135677f618529449334a612", "shasum": "" }, "require": { @@ -3499,6 +3580,9 @@ }, "type": "library", "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -3537,7 +3621,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" }, "funding": [ { @@ -3553,20 +3637,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.29.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", "shasum": "" }, "require": { @@ -3577,6 +3661,9 @@ }, "type": "library", "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -3618,7 +3705,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" }, "funding": [ { @@ -3634,20 +3721,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.29.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + "reference": "42292d99c55abe617799667f454222c54c60e229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", "shasum": "" }, "require": { @@ -3661,6 +3748,9 @@ }, "type": "library", "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -3698,7 +3788,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" }, "funding": [ { @@ -3714,20 +3804,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2023-07-28T09:04:16+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.29.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "21bd091060673a1177ae842c0ef8fe30893114d2" + "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2", - "reference": "21bd091060673a1177ae842c0ef8fe30893114d2", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fe2f306d1d9d346a7fee353d0d5012e401e984b5", + "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5", "shasum": "" }, "require": { @@ -3735,6 +3825,9 @@ }, "type": "library", "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -3774,7 +3867,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.28.0" }, "funding": [ { @@ -3790,20 +3883,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php74", - "version": "v1.29.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php74.git", - "reference": "da301202eb63b838aed1e62134e2302f826ca600" + "reference": "8b755b41a155c89f1af29cc33305538499fa05ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php74/zipball/da301202eb63b838aed1e62134e2302f826ca600", - "reference": "da301202eb63b838aed1e62134e2302f826ca600", + "url": "https://api.github.com/repos/symfony/polyfill-php74/zipball/8b755b41a155c89f1af29cc33305538499fa05ea", + "reference": "8b755b41a155c89f1af29cc33305538499fa05ea", "shasum": "" }, "require": { @@ -3811,6 +3904,9 @@ }, "type": "library", "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -3851,7 +3947,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php74/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php74/tree/v1.28.0" }, "funding": [ { @@ -3867,20 +3963,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.29.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "shasum": "" }, "require": { @@ -3888,6 +3984,9 @@ }, "type": "library", "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -3931,7 +4030,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" }, "funding": [ { @@ -3947,20 +4046,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.29.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d" + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d", - "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b", + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b", "shasum": "" }, "require": { @@ -3968,6 +4067,9 @@ }, "type": "library", "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -4007,7 +4109,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0" }, "funding": [ { @@ -4023,20 +4125,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/process", - "version": "v5.4.40", + "version": "v5.4.28", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046" + "reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/deedcb3bb4669cae2148bc920eafd2b16dc7c046", - "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046", + "url": "https://api.github.com/repos/symfony/process/zipball/45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b", + "reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b", "shasum": "" }, "require": { @@ -4069,7 +4171,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.40" + "source": "https://github.com/symfony/process/tree/v5.4.28" }, "funding": [ { @@ -4085,20 +4187,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2023-08-07T10:36:04+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.5.3", + "version": "v2.5.2", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3" + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a2329596ddc8fd568900e3fc76cba42489ecc7f3", - "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", "shasum": "" }, "require": { @@ -4152,7 +4254,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.3" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" }, "funding": [ { @@ -4168,20 +4270,20 @@ "type": "tidelift" } ], - "time": "2023-04-21T15:04:16+00:00" + "time": "2022-05-30T19:17:29+00:00" }, { "name": "symfony/string", - "version": "v5.4.40", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "142877285aa974a6f7685e292ab5ba9aae86b143" + "reference": "1181fe9270e373537475e826873b5867b863883c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/142877285aa974a6f7685e292ab5ba9aae86b143", - "reference": "142877285aa974a6f7685e292ab5ba9aae86b143", + "url": "https://api.github.com/repos/symfony/string/zipball/1181fe9270e373537475e826873b5867b863883c", + "reference": "1181fe9270e373537475e826873b5867b863883c", "shasum": "" }, "require": { @@ -4238,7 +4340,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.40" + "source": "https://github.com/symfony/string/tree/v5.4.26" }, "funding": [ { @@ -4254,7 +4356,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2023-06-28T12:46:07+00:00" } ], "packages-dev": [ @@ -4749,16 +4851,16 @@ }, { "name": "php-parallel-lint/php-parallel-lint", - "version": "v1.4.0", + "version": "v1.3.2", "source": { "type": "git", "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git", - "reference": "6db563514f27e19595a19f45a4bf757b6401194e" + "reference": "6483c9832e71973ed29cf71bd6b3f4fde438a9de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/6db563514f27e19595a19f45a4bf757b6401194e", - "reference": "6db563514f27e19595a19f45a4bf757b6401194e", + "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/6483c9832e71973ed29cf71bd6b3f4fde438a9de", + "reference": "6483c9832e71973ed29cf71bd6b3f4fde438a9de", "shasum": "" }, "require": { @@ -4796,30 +4898,26 @@ "email": "ahoj@jakubonderka.cz" } ], - "description": "This tool checks the syntax of PHP files about 20x faster than serial check.", + "description": "This tool check syntax of PHP files about 20x faster than serial check.", "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint", - "keywords": [ - "lint", - "static analysis" - ], "support": { "issues": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/issues", - "source": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/tree/v1.4.0" + "source": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/tree/v1.3.2" }, - "time": "2024-03-27T12:14:49+00:00" + "time": "2022-02-21T12:50:22+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "1.2.0", + "version": "1.2.x-dev", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26" + "reference": "788ea1bd84f7848abf27ba29b92c6c9d285dfc95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/fa8cce7720fa782899a0aa97b6a41225d1bb7b26", - "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/788ea1bd84f7848abf27ba29b92c6c9d285dfc95", + "reference": "788ea1bd84f7848abf27ba29b92c6c9d285dfc95", "shasum": "" }, "require": { @@ -4831,6 +4929,7 @@ "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^9.5" }, + "default-branch": true, "type": "phpstan-extension", "extra": { "phpstan": { @@ -4853,20 +4952,20 @@ "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.2.0" }, - "time": "2024-04-20T06:39:48+00:00" + "time": "2023-09-19T08:17:29+00:00" }, { "name": "phpstan/phpstan-nette", - "version": "1.3.1", + "version": "1.2.9", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-nette.git", - "reference": "9eebad8ceb52ad5e8f7c5012c4b7ed8777673ba0" + "reference": "0e3a6805917811d685e59bb83c2286315f2f6d78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/9eebad8ceb52ad5e8f7c5012c4b7ed8777673ba0", - "reference": "9eebad8ceb52ad5e8f7c5012c4b7ed8777673ba0", + "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/0e3a6805917811d685e59bb83c2286315f2f6d78", + "reference": "0e3a6805917811d685e59bb83c2286315f2f6d78", "shasum": "" }, "require": { @@ -4887,6 +4986,7 @@ "nette/utils": "^2.3.0 || ^3.0.0", "nikic/php-parser": "^4.13.2", "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-php-parser": "^1.1", "phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.5" @@ -4912,27 +5012,27 @@ "description": "Nette Framework class reflection extension for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-nette/issues", - "source": "https://github.com/phpstan/phpstan-nette/tree/1.3.1" + "source": "https://github.com/phpstan/phpstan-nette/tree/1.2.9" }, - "time": "2024-06-07T09:36:02+00:00" + "time": "2023-04-12T14:11:53+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "1.4.0", + "version": "1.3.16", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11" + "reference": "d5242a59d035e46774f2e634b374bc39ff62cb95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f3ea021866f4263f07ca3636bf22c64be9610c11", - "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d5242a59d035e46774f2e634b374bc39ff62cb95", + "reference": "d5242a59d035e46774f2e634b374bc39ff62cb95", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.10" }, "conflict": { "phpunit/phpunit": "<7.0" @@ -4964,22 +5064,22 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.16" }, - "time": "2024-04-20T06:39:00+00:00" + "time": "2024-02-23T09:51:20+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.6.0", + "version": "1.6.x-dev", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "363f921dd8441777d4fc137deb99beb486c77df1" + "reference": "a3b0404c40197996b6ed32b2613e5a337fcbefd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/363f921dd8441777d4fc137deb99beb486c77df1", - "reference": "363f921dd8441777d4fc137deb99beb486c77df1", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/a3b0404c40197996b6ed32b2613e5a337fcbefd4", + "reference": "a3b0404c40197996b6ed32b2613e5a337fcbefd4", "shasum": "" }, "require": { @@ -4993,6 +5093,7 @@ "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^9.5" }, + "default-branch": true, "type": "phpstan-extension", "extra": { "phpstan": { @@ -5015,7 +5116,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.0" }, - "time": "2024-04-20T06:37:51+00:00" + "time": "2023-10-30T14:35:14+00:00" }, { "name": "phpunit/php-code-coverage", From d782ab385fc46ed81b2d40e59da15956c979d7c6 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 11:28:35 +0200 Subject: [PATCH 21/55] fix hoa/regex parseable named captures --- src/Type/Php/RegexShapeMatcher.php | 9 +++++++-- tests/PHPStan/Analyser/data/preg_match_shapes.php | 10 ++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexShapeMatcher.php index 8b6eaace93..24cae1e93b 100644 --- a/src/Type/Php/RegexShapeMatcher.php +++ b/src/Type/Php/RegexShapeMatcher.php @@ -16,6 +16,8 @@ use PHPStan\Type\TypeCombinator; use function array_key_last; use function array_keys; +use function in_array; +use function is_int; use function is_string; use function preg_match; use function preg_replace; @@ -71,7 +73,10 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont $optional = true; } else { $optional = $remainingNonOptionalGroupCount <= 0; - $remainingNonOptionalGroupCount--; + + if (is_int($key)) { + $remainingNonOptionalGroupCount--; + } } $builder->setOffsetValueType( @@ -137,7 +142,7 @@ private function countNonOptionalGroups(string $regex): ?int private function walkRegexAst(TreeNode $ast, int $inAlternation, int $inOptionalQuantification): int { if ( - $ast->getId() === '#capturing' + in_array($ast->getId(), ['#capturing', '#namedcapturing'], true) && !($inAlternation > 0 || $inOptionalQuantification > 0) ) { return 1; diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index c45b79cea9..2d025aebde 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -52,6 +52,16 @@ function doNamedSubpattern(string $s): void { assertType('array', $matches); } assertType('array', $matches); + + if (preg_match('/^(?\S+::\S+)/', $s, $matches)) { + assertType('array{0: string, name: string, 1: string}', $matches); + } + assertType('array', $matches); + + if (preg_match('/^(?\S+::\S+)(?:(? with data set (?:#\d+|"[^"]+"))\s\()?/', $s, $matches)) { + assertType('array{0: string, name: string, 1: string, dataname?: string, 2?: string}', $matches); + } + assertType('array', $matches); } function doOffsetCapture(string $s): void { From 3dd7fc5a4842e3f9164ac509dfaa62b7a36185ac Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 11:49:09 +0200 Subject: [PATCH 22/55] Prevent "Call to function preg_match() with arguments '/^(?\\S+::\\S+)/', string and array{} will always evaluate to false." --- .../Php/PregMatchTypeSpecifyingExtension.php | 1 + ...mpossibleCheckTypeFunctionCallRuleTest.php | 7 ++++++ .../data/always-true-preg-match.php | 23 +++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 tests/PHPStan/Rules/Comparison/data/always-true-preg-match.php diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php index 0983a7ae10..8d32cc1bad 100644 --- a/src/Type/Php/PregMatchTypeSpecifyingExtension.php +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -92,6 +92,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $context, false, $scope, + $node, ); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index f20ba6c116..88d9315d84 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1094,4 +1094,11 @@ public function testBug10502(): void ]); } + public function testAlwaysTruePregMatch(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/always-true-preg-match.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/always-true-preg-match.php b/tests/PHPStan/Rules/Comparison/data/always-true-preg-match.php new file mode 100644 index 0000000000..160f21791a --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/always-true-preg-match.php @@ -0,0 +1,23 @@ +\S+::\S+)/', $test, $matches)) { + $test = $matches['name']; + } + + return $test; + } +} From 5387535f3bdba73674e1642c0de4c1917dc5269e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 12:27:13 +0200 Subject: [PATCH 23/55] Fix hoa/regex bug31 --- patches/Grammar.patch | 22 +++++++++++++++++-- .../Analyser/data/preg_match_shapes.php | 13 +++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/patches/Grammar.patch b/patches/Grammar.patch index 8baf670fb6..bed9ded86c 100644 --- a/patches/Grammar.patch +++ b/patches/Grammar.patch @@ -1,5 +1,23 @@ ---- Grammar.pp 2017-01-13 17:10:24 -+++ Grammar.pp 2024-05-18 08:49:19 +--- Grammar.pp 2024-05-18 12:15:53 ++++ Grammar.pp.fix 2024-05-18 12:15:05 +@@ -109,7 +109,7 @@ + // Please, see PCRESYNTAX(3), General Category properties, PCRE special category + // properties and script names for \p{} and \P{}. + %token character_type \\([CdDhHNRsSvVwWX]|[pP]{[^}]+}) +-%token anchor \\(bBAZzG)|\^|\$ ++%token anchor \\([bBAZzG])|\^|\$ + %token match_point_reset \\K + %token literal \\.|. + +@@ -168,7 +168,7 @@ + ::negative_class_:: #negativeclass + | ::class_:: + ) +- ( range() | literal() )+ ++ ( | range() | literal() )+ ? + ::_class:: + + #range: @@ -178,7 +178,7 @@ capturing() | literal() diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index 2d025aebde..bcc5956cc7 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -6,6 +6,11 @@ function doMatch(string $s): void { + if (preg_match('/Price: /i', $s, $matches)) { + assertType('array{string}', $matches); + } + assertType('array', $matches); + if (preg_match('/Price: (£|€)\d+/', $s, $matches)) { assertType('array{string, string}', $matches); } else { @@ -116,3 +121,11 @@ function doMultipleConsecutiveCaptureGroupsWithSameNameWithModifier(string $s): } assertType('array', $matches); } + +// https://github.com/hoaproject/Regex/issues/31 +function hoaBug31(string $s): void { + if (preg_match('/([\w-])/', $s, $matches)) { + assertType('array{string, string}', $matches); + } + assertType('array', $matches); +} From e03f6b1346a8af4e4d7ae70d06529e9776b506ec Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 13:58:58 +0200 Subject: [PATCH 24/55] read the grammar only once --- src/Type/Php/RegexShapeMatcher.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexShapeMatcher.php index 24cae1e93b..a7dbef6bea 100644 --- a/src/Type/Php/RegexShapeMatcher.php +++ b/src/Type/Php/RegexShapeMatcher.php @@ -4,6 +4,7 @@ use Hoa\Compiler\Exception\Exception; use Hoa\Compiler\Llk\Llk; +use Hoa\Compiler\Llk\Parser; use Hoa\Compiler\Llk\TreeNode; use Hoa\File\Read; use PHPStan\Analyser\TypeSpecifierContext; @@ -28,6 +29,8 @@ final class RegexShapeMatcher { + private static ?Parser $parser = null; + /** * @param int-mask|null $flags */ @@ -128,10 +131,13 @@ private function getValueType(int $flags): Type private function countNonOptionalGroups(string $regex): ?int { - /** @throws void */ - $parser = Llk::load(new Read('hoa://Library/Regex/Grammar.pp')); + if (self::$parser === null) { + /** @throws void */ + self::$parser = Llk::load(new Read('hoa://Library/Regex/Grammar.pp')); + } + try { - $ast = $parser->parse($regex); + $ast = self::$parser->parse($regex); } catch ( Exception) { // @phpstan-ignore catch.notThrowable return null; } From 45f9ab3ad9a09065d84ff9730735ab2d2dc081ba Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 14:01:41 +0200 Subject: [PATCH 25/55] simplify --- src/Type/Php/PregMatchTypeSpecifyingExtension.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php index 8d32cc1bad..b7011172f1 100644 --- a/src/Type/Php/PregMatchTypeSpecifyingExtension.php +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -44,10 +44,6 @@ public function isFunctionSupported(FunctionReflection $functionReflection, Func public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { $args = $node->getArgs(); - if (count($args) < 2) { - return new SpecifiedTypes(); - } - $patternArg = $args[0] ?? null; $matchesArg = $args[2] ?? null; $flagsArg = $args[3] ?? null; From cce33a5a8f35a8034741d1a93dd705cd0211be74 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 14:03:00 +0200 Subject: [PATCH 26/55] remove outdated comment --- src/Type/Php/RegexShapeMatcher.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexShapeMatcher.php index a7dbef6bea..18a466ad13 100644 --- a/src/Type/Php/RegexShapeMatcher.php +++ b/src/Type/Php/RegexShapeMatcher.php @@ -50,7 +50,6 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont unset($matches[array_key_last($matches)]); unset($matches['phpstanNamedCaptureGroupLast']); - // XXX hoa/regex does not support named capturing groups $remainingNonOptionalGroupCount = $this->countNonOptionalGroups($regex); if ($remainingNonOptionalGroupCount === null) { // regex could not be parsed by Hoa/Regex From 71f409d30d53184a1d45a5480dd0a86a4f2c7b6d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 14:10:01 +0200 Subject: [PATCH 27/55] fix test typo --- tests/PHPStan/Analyser/data/preg_match_shapes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index bcc5956cc7..70921d5670 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -92,7 +92,7 @@ function doOffsetCaptureWithUnmatchedNull(string $s): void { } function doUnknownFlags(string $s, int $flags): void { - if (preg_match('/(foo)(bar)(baz)/', $s, $matches, $flags)) { + if (preg_match('/(foo)(bar)(baz)/xyz', $s, $matches, $flags)) { assertType('array}|string|null>', $matches); } assertType('array}|string|null>', $matches); From 25de9029dee3a617e61fecdc695b919728447bdd Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 May 2024 14:23:36 +0200 Subject: [PATCH 28/55] test more quantifiers --- .../Analyser/data/preg_match_shapes.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index 70921d5670..c324eba9b0 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -42,6 +42,36 @@ function doMatch(string $s): void { assertType('array{0: string, 1: string, 2: string, 3?: string, 4?: string}', $matches); } assertType('array', $matches); + + if (preg_match('/(a|b)|(?:c)/', $s, $matches)) { + assertType('array{0: string, 1?: string}', $matches); + } + assertType('array', $matches); + + if (preg_match('/(foo)(bar)(baz)+/', $s, $matches)) { + assertType('array{string, string, string, string}', $matches); + } + assertType('array', $matches); + + if (preg_match('/(foo)(bar)(baz)*/', $s, $matches)) { + assertType('array{0: string, 1: string, 2: string, 3?: string}', $matches); + } + assertType('array', $matches); + + if (preg_match('/(foo)(bar)(baz)?/', $s, $matches)) { + assertType('array{0: string, 1: string, 2: string, 3?: string}', $matches); + } + assertType('array', $matches); + + if (preg_match('/(foo)(bar)(baz){0,3}/', $s, $matches)) { + assertType('array{0: string, 1: string, 2: string, 3?: string}', $matches); + } + assertType('array', $matches); + + if (preg_match('/(foo)(bar)(baz){2,3}/', $s, $matches)) { + assertType('array{string, string, string, string}', $matches); + } + assertType('array', $matches); } function doNonCapturingGroup(string $s): void { From e1e33bebfb63028665a0805aee767960086d14a7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 19 May 2024 09:03:57 +0200 Subject: [PATCH 29/55] test hoa/regex unsupported regex syntax --- tests/PHPStan/Analyser/data/preg_match_shapes.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index c324eba9b0..55d9b0bea5 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -159,3 +159,11 @@ function hoaBug31(string $s): void { } assertType('array', $matches); } + +// https://github.com/phpstan/phpstan/issues/10855#issuecomment-2044323638 +function testHoaUnsupportedRegexSyntax(string $s): void { + if (preg_match('#\QPHPDoc type array of property App\Log::$fillable is not covariant with PHPDoc type array of overridden property Illuminate\Database\E\\\\\QEloquent\Model::$fillable.\E#', $s, $matches)) { + assertType('array{string}', $matches); + } + assertType('array', $matches); +} From 5afdf6f607a091460e65d18d605b55b1cb0a2998 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 19 May 2024 09:06:03 +0200 Subject: [PATCH 30/55] enable only in bleeding edge --- src/Type/Php/PregMatchTypeSpecifyingExtension.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php index b7011172f1..3905ba19a5 100644 --- a/src/Type/Php/PregMatchTypeSpecifyingExtension.php +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -8,6 +8,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\BleedingEdgeToggle; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantIntegerType; @@ -48,7 +49,11 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $matchesArg = $args[2] ?? null; $flagsArg = $args[3] ?? null; - if ($patternArg === null || $matchesArg === null || !$this->phpVersion->returnsPregUnmatchedCapturingGroups()) { + if ( + $patternArg === null || $matchesArg === null + || !$this->phpVersion->returnsPregUnmatchedCapturingGroups() + || !BleedingEdgeToggle::isBleedingEdge() + ) { return new SpecifiedTypes(); } From 85cff746adf1921ae84c370171d341c31fd7dff1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 19 May 2024 09:22:21 +0200 Subject: [PATCH 31/55] use nette/utils --- src/Type/Php/RegexShapeMatcher.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexShapeMatcher.php index 18a466ad13..21927bda9c 100644 --- a/src/Type/Php/RegexShapeMatcher.php +++ b/src/Type/Php/RegexShapeMatcher.php @@ -7,6 +7,8 @@ use Hoa\Compiler\Llk\Parser; use Hoa\Compiler\Llk\TreeNode; use Hoa\File\Read; +use Nette\Utils\RegexpException; +use Nette\Utils\Strings; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantIntegerType; @@ -39,14 +41,17 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont // add one capturing group to the end so all capture group keys // are present in the $matches // see https://3v4l.org/sOXbn, https://3v4l.org/3SdDM - $captureGroupsRegex = preg_replace('~.[a-z\s]*$~i', '|(?)$0', $regex); + $captureGroupsRegex = Strings::replace($regex, '~.[a-z\s]*$~i', '|(?)$0'); - if ( - $captureGroupsRegex === null - || @preg_match($captureGroupsRegex, '', $matches, PREG_UNMATCHED_AS_NULL) === false - ) { + try { + $matches = Strings::match('', $captureGroupsRegex, PREG_UNMATCHED_AS_NULL); + if ($matches === null) { + return null; + } + } catch (RegexpException $e) { return null; } + unset($matches[array_key_last($matches)]); unset($matches['phpstanNamedCaptureGroupLast']); From 1650b005fb21a5f71b11ce847509ae707320302b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 19 May 2024 09:46:04 +0200 Subject: [PATCH 32/55] refactor into service --- .../Php/PregMatchTypeSpecifyingExtension.php | 39 ++----------- src/Type/Php/RegexShapeMatcher.php | 57 +++++++++++++++++-- 2 files changed, 58 insertions(+), 38 deletions(-) diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php index 3905ba19a5..425faabb03 100644 --- a/src/Type/Php/PregMatchTypeSpecifyingExtension.php +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -8,17 +8,10 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; -use PHPStan\DependencyInjection\BleedingEdgeToggle; -use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\FunctionTypeSpecifyingExtension; -use PHPStan\Type\TypeCombinator; -use function count; use function in_array; use function strtolower; -use const PREG_OFFSET_CAPTURE; -use const PREG_UNMATCHED_AS_NULL; final class PregMatchTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { @@ -27,7 +20,6 @@ final class PregMatchTypeSpecifyingExtension implements FunctionTypeSpecifyingEx public function __construct( private RegexShapeMatcher $regexShapeMatcher, - private PhpVersion $phpVersion, ) { } @@ -51,45 +43,24 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n if ( $patternArg === null || $matchesArg === null - || !$this->phpVersion->returnsPregUnmatchedCapturingGroups() - || !BleedingEdgeToggle::isBleedingEdge() ) { return new SpecifiedTypes(); } $patternType = $scope->getType($patternArg->value); - $constantStrings = $patternType->getConstantStrings(); - if (count($constantStrings) === 0) { - return new SpecifiedTypes(); - } - - $flags = null; + $flagsType = null; if ($flagsArg !== null) { $flagsType = $scope->getType($flagsArg->value); - - if ( - !$flagsType instanceof ConstantIntegerType - || !in_array($flagsType->getValue(), [PREG_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL, PREG_OFFSET_CAPTURE | PREG_UNMATCHED_AS_NULL], true) - ) { - return new SpecifiedTypes(); - } - - $flags = $flagsType->getValue(); } - $matchedTypes = []; - foreach ($constantStrings as $constantString) { - $matched = $this->regexShapeMatcher->matchType($constantString->getValue(), $flags, $context); - if ($matched === null) { - return new SpecifiedTypes(); - } - - $matchedTypes[] = $matched; + $matchedType = $this->regexShapeMatcher->matchType($patternType, $flagsType, $context); + if ($matchedType === null) { + return new SpecifiedTypes(); } return $this->typeSpecifier->create( $matchesArg->value, - TypeCombinator::union(...$matchedTypes), + $matchedType, $context, false, $scope, diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexShapeMatcher.php index 21927bda9c..834c421803 100644 --- a/src/Type/Php/RegexShapeMatcher.php +++ b/src/Type/Php/RegexShapeMatcher.php @@ -10,6 +10,8 @@ use Nette\Utils\RegexpException; use Nette\Utils\Strings; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\BleedingEdgeToggle; +use PHPStan\Php\PhpVersion; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; @@ -19,24 +21,71 @@ use PHPStan\Type\TypeCombinator; use function array_key_last; use function array_keys; +use function count; use function in_array; use function is_int; use function is_string; -use function preg_match; -use function preg_replace; use function str_contains; use const PREG_OFFSET_CAPTURE; use const PREG_UNMATCHED_AS_NULL; +/** + * @api + */ final class RegexShapeMatcher { private static ?Parser $parser = null; + public function __construct( + private PhpVersion $phpVersion, + ) + { + } + + public function matchType(Type $patternType, ?Type $flagsType, TypeSpecifierContext $context): ?Type + { + if ( + !$this->phpVersion->returnsPregUnmatchedCapturingGroups() + || !BleedingEdgeToggle::isBleedingEdge() + ) { + return null; + } + + $constantStrings = $patternType->getConstantStrings(); + if (count($constantStrings) === 0) { + return null; + } + + $flags = null; + if ($flagsType !== null) { + if ( + !$flagsType instanceof ConstantIntegerType + || !in_array($flagsType->getValue(), [PREG_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL, PREG_OFFSET_CAPTURE | PREG_UNMATCHED_AS_NULL], true) + ) { + return null; + } + + $flags = $flagsType->getValue(); + } + + $matchedTypes = []; + foreach ($constantStrings as $constantString) { + $matched = $this->matchRegex($constantString->getValue(), $flags, $context); + if ($matched === null) { + return null; + } + + $matchedTypes[] = $matched; + } + + return TypeCombinator::union(...$matchedTypes); + } + /** * @param int-mask|null $flags */ - public function matchType(string $regex, ?int $flags, TypeSpecifierContext $context): ?Type + private function matchRegex(string $regex, ?int $flags, TypeSpecifierContext $context): ?Type { // add one capturing group to the end so all capture group keys // are present in the $matches @@ -48,7 +97,7 @@ public function matchType(string $regex, ?int $flags, TypeSpecifierContext $cont if ($matches === null) { return null; } - } catch (RegexpException $e) { + } catch (RegexpException) { return null; } From 6e246e20c5d7bdc6be46f6b2577b291e0634ce1f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 19 May 2024 09:59:39 +0200 Subject: [PATCH 33/55] fix dash outside of capturing group --- patches/Grammar.patch | 10 +++++++++- tests/PHPStan/Analyser/data/preg_match_shapes.php | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/patches/Grammar.patch b/patches/Grammar.patch index bed9ded86c..f35bbf3b9f 100644 --- a/patches/Grammar.patch +++ b/patches/Grammar.patch @@ -14,7 +14,7 @@ | ::class_:: ) - ( range() | literal() )+ -+ ( | range() | literal() )+ ? ++ ( | range() | literal() )+ ::_class:: #range: @@ -27,3 +27,11 @@ ::comment_:: ? ::_comment:: #comment | ( ::named_capturing_:: ::_named_capturing:: #namedcapturing +@@ -191,6 +191,7 @@ + + literal: + ++ | + | + | + | diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index 55d9b0bea5..6bdb7b7377 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -158,6 +158,11 @@ function hoaBug31(string $s): void { assertType('array{string, string}', $matches); } assertType('array', $matches); + + if (preg_match('/\w-(\d+)-(\w)/', $s, $matches)) { + assertType('array{string, string, string}', $matches); + } + assertType('array', $matches); } // https://github.com/phpstan/phpstan/issues/10855#issuecomment-2044323638 From 1c977c8620b1522cccbc4afb8bd0a6c9f1685739 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 21 May 2024 12:14:00 +0200 Subject: [PATCH 34/55] decouple RegexArrayShapeMatcher from TypeSpecifierContext --- conf/config.neon | 2 +- src/Type/Php/PregMatchTypeSpecifyingExtension.php | 6 ++++-- ...ShapeMatcher.php => RegexArrayShapeMatcher.php} | 14 +++++++------- tests/PHPStan/Analyser/data/preg_match_shapes.php | 1 + 4 files changed, 13 insertions(+), 10 deletions(-) rename src/Type/Php/{RegexShapeMatcher.php => RegexArrayShapeMatcher.php} (95%) diff --git a/conf/config.neon b/conf/config.neon index bd1b75e949..e6b6bcff6a 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1471,7 +1471,7 @@ services: - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - class: PHPStan\Type\Php\RegexShapeMatcher + class: PHPStan\Type\Php\RegexArrayShapeMatcher - class: PHPStan\Type\Php\ReflectionClassConstructorThrowTypeExtension diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php index 425faabb03..d89a0401bf 100644 --- a/src/Type/Php/PregMatchTypeSpecifyingExtension.php +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -9,6 +9,7 @@ use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\FunctionReflection; +use PHPStan\TrinaryLogic; use PHPStan\Type\FunctionTypeSpecifyingExtension; use function in_array; use function strtolower; @@ -19,7 +20,7 @@ final class PregMatchTypeSpecifyingExtension implements FunctionTypeSpecifyingEx private TypeSpecifier $typeSpecifier; public function __construct( - private RegexShapeMatcher $regexShapeMatcher, + private RegexArrayShapeMatcher $regexShapeMatcher, ) { } @@ -53,7 +54,8 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $flagsType = $scope->getType($flagsArg->value); } - $matchedType = $this->regexShapeMatcher->matchType($patternType, $flagsType, $context); + $wasMatched = TrinaryLogic::createFromBoolean($context->true()); + $matchedType = $this->regexShapeMatcher->matchType($patternType, $flagsType, $wasMatched); if ($matchedType === null) { return new SpecifiedTypes(); } diff --git a/src/Type/Php/RegexShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php similarity index 95% rename from src/Type/Php/RegexShapeMatcher.php rename to src/Type/Php/RegexArrayShapeMatcher.php index 834c421803..c57870da39 100644 --- a/src/Type/Php/RegexShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -9,9 +9,9 @@ use Hoa\File\Read; use Nette\Utils\RegexpException; use Nette\Utils\Strings; -use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\DependencyInjection\BleedingEdgeToggle; use PHPStan\Php\PhpVersion; +use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; @@ -32,7 +32,7 @@ /** * @api */ -final class RegexShapeMatcher +final class RegexArrayShapeMatcher { private static ?Parser $parser = null; @@ -43,7 +43,7 @@ public function __construct( { } - public function matchType(Type $patternType, ?Type $flagsType, TypeSpecifierContext $context): ?Type + public function matchType(Type $patternType, ?Type $flagsType, TrinaryLogic $wasMatched): ?Type { if ( !$this->phpVersion->returnsPregUnmatchedCapturingGroups() @@ -71,7 +71,7 @@ public function matchType(Type $patternType, ?Type $flagsType, TypeSpecifierCont $matchedTypes = []; foreach ($constantStrings as $constantString) { - $matched = $this->matchRegex($constantString->getValue(), $flags, $context); + $matched = $this->matchRegex($constantString->getValue(), $flags, $wasMatched); if ($matched === null) { return null; } @@ -85,7 +85,7 @@ public function matchType(Type $patternType, ?Type $flagsType, TypeSpecifierCont /** * @param int-mask|null $flags */ - private function matchRegex(string $regex, ?int $flags, TypeSpecifierContext $context): ?Type + private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched): ?Type { // add one capturing group to the end so all capture group keys // are present in the $matches @@ -119,13 +119,13 @@ private function matchRegex(string $regex, ?int $flags, TypeSpecifierContext $co $builder->setOffsetValueType( $this->getKeyType($key), TypeCombinator::removeNull($valueType), - !$context->true(), + !$wasMatched->yes(), ); continue; } - if (!$context->true()) { + if (!$wasMatched->yes()) { $optional = true; } else { $optional = $remainingNonOptionalGroupCount <= 0; diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/data/preg_match_shapes.php index 6bdb7b7377..de241475f0 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes.php @@ -14,6 +14,7 @@ function doMatch(string $s): void { if (preg_match('/Price: (£|€)\d+/', $s, $matches)) { assertType('array{string, string}', $matches); } else { + // could be assertType('array{}', $matches); assertType('array', $matches); } assertType('array', $matches); From 6cae0acca3aa061149016211c79d6e8747e2bcb5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 24 May 2024 15:22:34 +0200 Subject: [PATCH 35/55] use separate feature flag --- conf/bleedingEdge.neon | 1 + conf/config.neon | 5 +++-- conf/parametersSchema.neon | 1 + src/Type/Php/RegexArrayShapeMatcher.php | 2 -- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 788cec4791..ef0fba19f4 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -54,5 +54,6 @@ parameters: paramOutType: true pure: true checkParameterCastableToStringFunctions: true + narrowPregMatches: true stubFiles: - ../stubs/bleedingEdge/Rule.stub diff --git a/conf/config.neon b/conf/config.neon index e6b6bcff6a..b5ea7c4a42 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -89,6 +89,7 @@ parameters: paramOutType: false pure: false checkParameterCastableToStringFunctions: false + narrowPregMatches: false fileExtensions: - php checkAdvancedIsset: false @@ -284,6 +285,8 @@ conditionalTags: phpstan.parser.richParserNodeVisitor: %featureToggles.curlSetOptTypes% PHPStan\Parser\TypeTraverserInstanceofVisitor: phpstan.parser.richParserNodeVisitor: %featureToggles.instanceofType% + PHPStan\Type\Php\PregMatchTypeSpecifyingExtension: + phpstan.typeSpecifier.functionTypeSpecifyingExtension: %featureToggles.narrowPregMatches% services: - @@ -1467,8 +1470,6 @@ services: - class: PHPStan\Type\Php\PregMatchTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - class: PHPStan\Type\Php\RegexArrayShapeMatcher diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index e4bf73db35..0a63268a1b 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -84,6 +84,7 @@ parametersSchema: paramOutType: bool() pure: bool() checkParameterCastableToStringFunctions: bool() + narrowPregMatches: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index c57870da39..80f82010de 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -9,7 +9,6 @@ use Hoa\File\Read; use Nette\Utils\RegexpException; use Nette\Utils\Strings; -use PHPStan\DependencyInjection\BleedingEdgeToggle; use PHPStan\Php\PhpVersion; use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; @@ -47,7 +46,6 @@ public function matchType(Type $patternType, ?Type $flagsType, TrinaryLogic $was { if ( !$this->phpVersion->returnsPregUnmatchedCapturingGroups() - || !BleedingEdgeToggle::isBleedingEdge() ) { return null; } From 7fca860ea0e8ed7a7808749a74d68760ae244527 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 20 Jun 2024 15:57:26 +0200 Subject: [PATCH 36/55] move NodeScopeResolverTest-tests into "nsrt" --- .../Analyser/NodeScopeResolverTest.php | 20 ------------------- .../{data => nsrt}/preg_match_shapes.php | 0 .../preg_match_shapes_php73.php | 2 +- .../preg_match_shapes_php82.php | 2 +- 4 files changed, 2 insertions(+), 22 deletions(-) rename tests/PHPStan/Analyser/{data => nsrt}/preg_match_shapes.php (100%) rename tests/PHPStan/Analyser/{data => nsrt}/preg_match_shapes_php73.php (93%) rename tests/PHPStan/Analyser/{data => nsrt}/preg_match_shapes_php82.php (96%) diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 76b4de6201..3a1bf98033 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -179,26 +179,6 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-9542.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-9803.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/PhpDoc/data/bug-10594.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/set-type-type-specifying.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/mysqli_fetch_object.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10468.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6613.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10187.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10834.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10952.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10952b.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/case-insensitive-parent.php'); - - if (PHP_VERSION_ID < 70400) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_match_shapes_php73.php'); - } else { - yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_match_shapes.php'); - } - if (PHP_VERSION_ID >= 80200) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_match_shapes_php82.php'); - } - - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10893.php'); } /** diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php similarity index 100% rename from tests/PHPStan/Analyser/data/preg_match_shapes.php rename to tests/PHPStan/Analyser/nsrt/preg_match_shapes.php diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes_php73.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php73.php similarity index 93% rename from tests/PHPStan/Analyser/data/preg_match_shapes_php73.php rename to tests/PHPStan/Analyser/nsrt/preg_match_shapes_php73.php index 8c5728bc8e..53e23ccff5 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes_php73.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php73.php @@ -1,4 +1,4 @@ -= 8.2 namespace PregMatchShapesPhp82; From 11512bb498b5d1c85bb7380d25e1c4b805c78a48 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 20 Jun 2024 16:54:22 +0200 Subject: [PATCH 37/55] Implement additional PregMatchParameterOutTypeExtension --- conf/config.neon | 9 ++- .../PregMatchParameterOutTypeExtension.php | 60 +++++++++++++++++++ .../Php/PregMatchTypeSpecifyingExtension.php | 5 +- .../Analyser/LegacyNodeScopeResolverTest.php | 10 ++-- tests/PHPStan/Analyser/data/param-out.php | 4 +- .../Analyser/nsrt/preg_match_shapes.php | 48 +++++++-------- .../Analyser/nsrt/preg_match_shapes_php82.php | 2 +- 7 files changed, 101 insertions(+), 37 deletions(-) create mode 100644 src/Type/Php/PregMatchParameterOutTypeExtension.php diff --git a/conf/config.neon b/conf/config.neon index b5ea7c4a42..b1eef0be9b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -285,8 +285,6 @@ conditionalTags: phpstan.parser.richParserNodeVisitor: %featureToggles.curlSetOptTypes% PHPStan\Parser\TypeTraverserInstanceofVisitor: phpstan.parser.richParserNodeVisitor: %featureToggles.instanceofType% - PHPStan\Type\Php\PregMatchTypeSpecifyingExtension: - phpstan.typeSpecifier.functionTypeSpecifyingExtension: %featureToggles.narrowPregMatches% services: - @@ -1470,6 +1468,13 @@ services: - class: PHPStan\Type\Php\PregMatchTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.functionTypeSpecifyingExtension + + - + class: PHPStan\Type\Php\PregMatchParameterOutTypeExtension + tags: + - phpstan.functionParameterOutTypeExtension - class: PHPStan\Type\Php\RegexArrayShapeMatcher diff --git a/src/Type/Php/PregMatchParameterOutTypeExtension.php b/src/Type/Php/PregMatchParameterOutTypeExtension.php new file mode 100644 index 0000000000..f4cb89773d --- /dev/null +++ b/src/Type/Php/PregMatchParameterOutTypeExtension.php @@ -0,0 +1,60 @@ +typeSpecifier = $typeSpecifier; + } + + public function isFunctionSupported(FunctionReflection $functionReflection, ParameterReflection $parameter): bool + { + return in_array(strtolower($functionReflection->getName()), ['preg_match'], true) && $parameter->getName() === 'matches'; + } + + public function getParameterOutTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $funcCall, ParameterReflection $parameter, Scope $scope): ?Type + { + $args = $funcCall->getArgs(); + $patternArg = $args[0] ?? null; + $matchesArg = $args[2] ?? null; + $flagsArg = $args[3] ?? null; + + if ( + $patternArg === null || $matchesArg === null + ) { + return null; + } + + $patternType = $scope->getType($patternArg->value); + $flagsType = null; + if ($flagsArg !== null) { + $flagsType = $scope->getType($flagsArg->value); + } + + return $this->regexShapeMatcher->matchType($patternType, $flagsType, TrinaryLogic::createMaybe()); + } + +} diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php index d89a0401bf..4f11c5dce0 100644 --- a/src/Type/Php/PregMatchTypeSpecifyingExtension.php +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -32,7 +32,7 @@ public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool { - return in_array(strtolower($functionReflection->getName()), ['preg_match'], true); + return in_array(strtolower($functionReflection->getName()), ['preg_match'], true) && $context->true(); } public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes @@ -54,8 +54,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $flagsType = $scope->getType($flagsArg->value); } - $wasMatched = TrinaryLogic::createFromBoolean($context->true()); - $matchedType = $this->regexShapeMatcher->matchType($patternType, $flagsType, $wasMatched); + $matchedType = $this->regexShapeMatcher->matchType($patternType, $flagsType, TrinaryLogic::createYes()); if ($matchedType === null) { return new SpecifiedTypes(); } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index c6a73b060b..dd8222ec2a 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -302,7 +302,7 @@ public function dataAssignInIf(): array $testScope, 'matches', TrinaryLogic::createYes(), - 'array', + 'array{0?: string}', ], [ $testScope, @@ -343,7 +343,7 @@ public function dataAssignInIf(): array $testScope, 'matches2', TrinaryLogic::createMaybe(), - 'array', + 'array{0?: string}', ], [ $testScope, @@ -355,13 +355,13 @@ public function dataAssignInIf(): array $testScope, 'matches3', TrinaryLogic::createYes(), - 'array', + 'array{0?: string}', ], [ $testScope, 'matches4', TrinaryLogic::createMaybe(), - 'array', + 'array{0?: string}', ], [ $testScope, @@ -415,7 +415,7 @@ public function dataAssignInIf(): array $testScope, 'ternaryMatches', TrinaryLogic::createYes(), - 'array', + 'array{0?: string}', ], [ $testScope, diff --git a/tests/PHPStan/Analyser/data/param-out.php b/tests/PHPStan/Analyser/data/param-out.php index 88cd9bf14d..e70ba12b2b 100644 --- a/tests/PHPStan/Analyser/data/param-out.php +++ b/tests/PHPStan/Analyser/data/param-out.php @@ -291,7 +291,7 @@ function fooMatch(string $input): void { assertType('list>', $matches); preg_match('/@[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/', $input, $matches, PREG_UNMATCHED_AS_NULL); - assertType("array", $matches); + assertType("array{0?: string}", $matches); } function fooParams(ExtendsFooBar $subX, float $x1, float $y1) @@ -318,7 +318,7 @@ function fooDateTime(\SplFileObject $splFileObject, ?string $wouldBlock) { function testMatch() { preg_match('#.*#', 'foo', $matches); - assertType('array', $matches); + assertType('array{0?: string}', $matches); } function testParseStr() { diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index de241475f0..bac99c32c1 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -9,77 +9,77 @@ function doMatch(string $s): void { if (preg_match('/Price: /i', $s, $matches)) { assertType('array{string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string}', $matches); if (preg_match('/Price: (£|€)\d+/', $s, $matches)) { assertType('array{string, string}', $matches); } else { // could be assertType('array{}', $matches); - assertType('array', $matches); + assertType('array{0?: string, 1?: string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string}', $matches); if (preg_match('/Price: (£|€)(\d+)/i', $s, $matches)) { assertType('array{string, string, string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string, 2?: string}', $matches); if (preg_match(' /Price: (£|€)\d+/ i u', $s, $matches)) { assertType('array{string, string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string}', $matches); if (preg_match('(Price: (£|€))i', $s, $matches)) { assertType('array{string, string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string}', $matches); if (preg_match('_foo(.)\_i_i', $s, $matches)) { assertType('array{string, string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string}', $matches); if (preg_match('/(a)(b)*(c)(d)*/', $s, $matches)) { assertType('array{0: string, 1: string, 2: string, 3?: string, 4?: string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string, 2?: string, 3?: string, 4?: string}', $matches); if (preg_match('/(a|b)|(?:c)/', $s, $matches)) { assertType('array{0: string, 1?: string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string}', $matches); if (preg_match('/(foo)(bar)(baz)+/', $s, $matches)) { assertType('array{string, string, string, string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string, 2?: string, 3?: string}', $matches); if (preg_match('/(foo)(bar)(baz)*/', $s, $matches)) { assertType('array{0: string, 1: string, 2: string, 3?: string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string, 2?: string, 3?: string}', $matches); if (preg_match('/(foo)(bar)(baz)?/', $s, $matches)) { assertType('array{0: string, 1: string, 2: string, 3?: string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string, 2?: string, 3?: string}', $matches); if (preg_match('/(foo)(bar)(baz){0,3}/', $s, $matches)) { assertType('array{0: string, 1: string, 2: string, 3?: string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string, 2?: string, 3?: string}', $matches); if (preg_match('/(foo)(bar)(baz){2,3}/', $s, $matches)) { assertType('array{string, string, string, string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string, 2?: string, 3?: string}', $matches); } function doNonCapturingGroup(string $s): void { if (preg_match('/Price: (?:£|€)(\d+)/', $s, $matches)) { assertType('array{string, string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string}', $matches); } function doNamedSubpattern(string $s): void { @@ -92,34 +92,34 @@ function doNamedSubpattern(string $s): void { if (preg_match('/^(?\S+::\S+)/', $s, $matches)) { assertType('array{0: string, name: string, 1: string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, name?: string, 1?: string}', $matches); if (preg_match('/^(?\S+::\S+)(?:(? with data set (?:#\d+|"[^"]+"))\s\()?/', $s, $matches)) { assertType('array{0: string, name: string, 1: string, dataname?: string, 2?: string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, name?: string, 1?: string, dataname?: string, 2?: string}', $matches); } function doOffsetCapture(string $s): void { if (preg_match('/(foo)(bar)(baz)/', $s, $matches, PREG_OFFSET_CAPTURE)) { assertType('array{array{string, int<0, max>}, array{string, int<0, max>}, array{string, int<0, max>}, array{string, int<0, max>}}', $matches); } - assertType('array}>', $matches); + assertType('array{0?: array{string, int<0, max>}, 1?: array{string, int<0, max>}, 2?: array{string, int<0, max>}, 3?: array{string, int<0, max>}}', $matches); } function doUnmatchedAsNull(string $s): void { if (preg_match('/(foo)?(bar)?(baz)?/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { assertType('array{0: string, 1?: string|null, 2?: string|null, 3?: string|null}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string|null, 2?: string|null, 3?: string|null}', $matches); } function doOffsetCaptureWithUnmatchedNull(string $s): void { // see https://3v4l.org/07rBO#v8.2.9 if (preg_match('/(foo)(bar)(baz)/', $s, $matches, PREG_OFFSET_CAPTURE|PREG_UNMATCHED_AS_NULL)) { - assertType('array{array{null, -1}|array{string, int<0, max>}, array{null, -1}|array{string, int<0, max>}, array{null, -1}|array{string, int<0, max>}, array{null, -1}|array{string, int<0, max>}}', $matches); + assertType('array{array{string|null, int<-1, max>}, array{string|null, int<-1, max>}, array{string|null, int<-1, max>}, array{string|null, int<-1, max>}}', $matches); } - assertType('array}>', $matches); + assertType('array{0?: array{string|null, int<-1, max>}, 1?: array{string|null, int<-1, max>}, 2?: array{string|null, int<-1, max>}, 3?: array{string|null, int<-1, max>}}', $matches); } function doUnknownFlags(string $s, int $flags): void { @@ -158,12 +158,12 @@ function hoaBug31(string $s): void { if (preg_match('/([\w-])/', $s, $matches)) { assertType('array{string, string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string}', $matches); if (preg_match('/\w-(\d+)-(\w)/', $s, $matches)) { assertType('array{string, string, string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string, 1?: string, 2?: string}', $matches); } // https://github.com/phpstan/phpstan/issues/10855#issuecomment-2044323638 @@ -171,5 +171,5 @@ function testHoaUnsupportedRegexSyntax(string $s): void { if (preg_match('#\QPHPDoc type array of property App\Log::$fillable is not covariant with PHPDoc type array of overridden property Illuminate\Database\E\\\\\QEloquent\Model::$fillable.\E#', $s, $matches)) { assertType('array{string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string}', $matches); } diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php82.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php82.php index b4c404bd86..88711da54f 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php82.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php82.php @@ -10,7 +10,7 @@ function doNonAutoCapturingFlag(string $s): void { if (preg_match('/(\d+)/n', $s, $matches)) { assertType('array{string}', $matches); } - assertType('array', $matches); + assertType('array{0?: string}', $matches); if (preg_match('/(\d+)(?P\d+)/n', $s, $matches)) { // could be assertType('array{0: string, num: string, 1: string}', $matches); From 5334cfd5d00a7983aff65d2b34dd58f7c1b7762a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 20 Jun 2024 17:10:38 +0200 Subject: [PATCH 38/55] utlize feature flag --- conf/config.neon | 4 ++++ src/Type/Php/PregMatchParameterOutTypeExtension.php | 3 ++- src/Type/Php/PregMatchTypeSpecifyingExtension.php | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index b1eef0be9b..dfe7222891 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1468,11 +1468,15 @@ services: - class: PHPStan\Type\Php\PregMatchTypeSpecifyingExtension + arguments: + disabled: %featureToggles.narrowPregMatches% tags: - phpstan.typeSpecifier.functionTypeSpecifyingExtension - class: PHPStan\Type\Php\PregMatchParameterOutTypeExtension + arguments: + disabled: %featureToggles.narrowPregMatches% tags: - phpstan.functionParameterOutTypeExtension diff --git a/src/Type/Php/PregMatchParameterOutTypeExtension.php b/src/Type/Php/PregMatchParameterOutTypeExtension.php index f4cb89773d..125aa59f4e 100644 --- a/src/Type/Php/PregMatchParameterOutTypeExtension.php +++ b/src/Type/Php/PregMatchParameterOutTypeExtension.php @@ -21,6 +21,7 @@ final class PregMatchParameterOutTypeExtension implements FunctionParameterOutTy public function __construct( private RegexArrayShapeMatcher $regexShapeMatcher, + private bool $disabled, ) { } @@ -32,7 +33,7 @@ public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void public function isFunctionSupported(FunctionReflection $functionReflection, ParameterReflection $parameter): bool { - return in_array(strtolower($functionReflection->getName()), ['preg_match'], true) && $parameter->getName() === 'matches'; + return !$this->disabled && in_array(strtolower($functionReflection->getName()), ['preg_match'], true) && $parameter->getName() === 'matches'; } public function getParameterOutTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $funcCall, ParameterReflection $parameter, Scope $scope): ?Type diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php index 4f11c5dce0..14aa49b98a 100644 --- a/src/Type/Php/PregMatchTypeSpecifyingExtension.php +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -21,6 +21,7 @@ final class PregMatchTypeSpecifyingExtension implements FunctionTypeSpecifyingEx public function __construct( private RegexArrayShapeMatcher $regexShapeMatcher, + private bool $disabled, ) { } @@ -32,7 +33,7 @@ public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool { - return in_array(strtolower($functionReflection->getName()), ['preg_match'], true) && $context->true(); + return !$this->disabled && in_array(strtolower($functionReflection->getName()), ['preg_match'], true) && $context->true(); } public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes From bf66341997ec2037fd8aa42f73bdf2a520acd6d1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 20 Jun 2024 17:13:35 +0200 Subject: [PATCH 39/55] Update composer.lock --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index 5667f7eade..2b2d5333f1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "954daf42f3b55135a22511443c88b3ec", + "content-hash": "30f0be2f5f4d8a9074cff904026ec2a6", "packages": [ { "name": "clue/ndjson-react", From f85da99f17ab7f558dd0cd250229f5c7b5e118cd Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 20 Jun 2024 17:28:42 +0200 Subject: [PATCH 40/55] Update PregMatchParameterOutTypeExtension.php --- src/Type/Php/PregMatchParameterOutTypeExtension.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Type/Php/PregMatchParameterOutTypeExtension.php b/src/Type/Php/PregMatchParameterOutTypeExtension.php index 125aa59f4e..277ce0593f 100644 --- a/src/Type/Php/PregMatchParameterOutTypeExtension.php +++ b/src/Type/Php/PregMatchParameterOutTypeExtension.php @@ -14,11 +14,9 @@ use function in_array; use function strtolower; -final class PregMatchParameterOutTypeExtension implements FunctionParameterOutTypeExtension, TypeSpecifierAwareExtension +final class PregMatchParameterOutTypeExtension implements FunctionParameterOutTypeExtension { - private TypeSpecifier $typeSpecifier; - public function __construct( private RegexArrayShapeMatcher $regexShapeMatcher, private bool $disabled, @@ -26,11 +24,6 @@ public function __construct( { } - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - public function isFunctionSupported(FunctionReflection $functionReflection, ParameterReflection $parameter): bool { return !$this->disabled && in_array(strtolower($functionReflection->getName()), ['preg_match'], true) && $parameter->getName() === 'matches'; From da8653dc74d3d9cf29714636b4b87179bba87d1c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 20 Jun 2024 17:50:39 +0200 Subject: [PATCH 41/55] Update composer.lock --- composer.lock | 691 +++++++++++++++++++++----------------------------- 1 file changed, 295 insertions(+), 396 deletions(-) diff --git a/composer.lock b/composer.lock index 2b2d5333f1..f18481ad71 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "30f0be2f5f4d8a9074cff904026ec2a6", + "content-hash": "db09e230b5029b6247349873fc13819a", "packages": [ { "name": "clue/ndjson-react", @@ -72,28 +72,28 @@ }, { "name": "composer/ca-bundle", - "version": "1.3.7", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "76e46335014860eec1aa5a724799a00a2e47cc85" + "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/76e46335014860eec1aa5a724799a00a2e47cc85", - "reference": "76e46335014860eec1aa5a724799a00a2e47cc85", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", + "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", + "phpstan/phpstan": "^1.10", "psr/log": "^1.0", "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { @@ -128,7 +128,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.3.7" + "source": "https://github.com/composer/ca-bundle/tree/1.5.0" }, "funding": [ { @@ -144,7 +144,7 @@ "type": "tidelift" } ], - "time": "2023-08-30T09:31:38+00:00" + "time": "2024-03-15T14:00:32+00:00" }, { "name": "composer/pcre", @@ -217,6 +217,87 @@ ], "time": "2024-03-19T10:26:25+00:00" }, + { + "name": "composer/semver", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2023-08-31T09:50:34+00:00" + }, { "name": "composer/xdebug-handler", "version": "3.0.5", @@ -1353,12 +1434,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "80e1e4810b9b72f2ce3871a344762e3a0ea87423" + "reference": "cf7e447ddfa7f0cbab0c1dd38392f0cb05f9881c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/80e1e4810b9b72f2ce3871a344762e3a0ea87423", - "reference": "80e1e4810b9b72f2ce3871a344762e3a0ea87423", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/cf7e447ddfa7f0cbab0c1dd38392f0cb05f9881c", + "reference": "cf7e447ddfa7f0cbab0c1dd38392f0cb05f9881c", "shasum": "" }, "require-dev": { @@ -1393,7 +1474,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-04-29T13:21:12+00:00" + "time": "2024-06-17T19:18:18+00:00" }, { "name": "nette/bootstrap", @@ -2095,16 +2176,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.25.0.11", + "version": "6.25.0.12", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "7fcef699523b59a2bc6cfd7966ee7c17d285b9ae" + "reference": "e8f880bf854d9a6737ac49ce67f58952d3b59c3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/7fcef699523b59a2bc6cfd7966ee7c17d285b9ae", - "reference": "7fcef699523b59a2bc6cfd7966ee7c17d285b9ae", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/e8f880bf854d9a6737ac49ce67f58952d3b59c3c", + "reference": "e8f880bf854d9a6737ac49ce67f58952d3b59c3c", "shasum": "" }, "require": { @@ -2161,9 +2242,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.11" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.12" }, - "time": "2024-05-13T13:09:26+00:00" + "time": "2024-06-04T07:28:56+00:00" }, { "name": "phpstan/php-8-stubs", @@ -2199,16 +2280,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.29.0", + "version": "1.29.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc" + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/536889f2b340489d328f5ffb7b02bb6b183ddedc", - "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", "shasum": "" }, "require": { @@ -2240,9 +2321,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" }, - "time": "2024-05-06T12:04:23+00:00" + "time": "2024-05-31T08:52:43+00:00" }, { "name": "psr/container", @@ -2397,16 +2478,16 @@ }, { "name": "react/async", - "version": "v3.0.0", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/reactphp/async.git", - "reference": "3c3b812be77aec14bf8300b052ba589c9a5bc95b" + "reference": "bc3ef672b33e95bf814fe8377731e46888ed4b54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/async/zipball/3c3b812be77aec14bf8300b052ba589c9a5bc95b", - "reference": "3c3b812be77aec14bf8300b052ba589c9a5bc95b", + "url": "https://api.github.com/repos/reactphp/async/zipball/bc3ef672b33e95bf814fe8377731e46888ed4b54", + "reference": "bc3ef672b33e95bf814fe8377731e46888ed4b54", "shasum": "" }, "require": { @@ -2415,7 +2496,8 @@ "react/promise": "^3.0 || ^2.8 || ^1.2.1" }, "require-dev": { - "phpunit/phpunit": "^9.3 || ^7.5" + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", "autoload": { @@ -2456,19 +2538,15 @@ ], "support": { "issues": "https://github.com/reactphp/async/issues", - "source": "https://github.com/reactphp/async/tree/v3.0.0" + "source": "https://github.com/reactphp/async/tree/v3.2.0" }, "funding": [ { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2022-07-11T14:17:23+00:00" + "time": "2023-11-22T16:21:11+00:00" }, { "name": "react/cache", @@ -2623,33 +2701,33 @@ }, { "name": "react/dns", - "version": "v1.10.0", + "version": "v1.13.0", "source": { "type": "git", "url": "https://github.com/reactphp/dns.git", - "reference": "a5427e7dfa47713e438016905605819d101f238c" + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/a5427e7dfa47713e438016905605819d101f238c", - "reference": "a5427e7dfa47713e438016905605819d101f238c", + "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", "shasum": "" }, "require": { "php": ">=5.3.0", "react/cache": "^1.0 || ^0.6 || ^0.5", "react/event-loop": "^1.2", - "react/promise": "^3.0 || ^2.7 || ^1.2.1", - "react/promise-timer": "^1.9" + "react/promise": "^3.2 || ^2.7 || ^1.2.1" }, "require-dev": { - "phpunit/phpunit": "^9.3 || ^4.8.35", - "react/async": "^4 || ^3 || ^2" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" }, "type": "library", "autoload": { "psr-4": { - "React\\Dns\\": "src" + "React\\Dns\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2687,32 +2765,28 @@ ], "support": { "issues": "https://github.com/reactphp/dns/issues", - "source": "https://github.com/reactphp/dns/tree/v1.10.0" + "source": "https://github.com/reactphp/dns/tree/v1.13.0" }, "funding": [ { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2022-09-08T12:22:46+00:00" + "time": "2024-06-13T14:18:03+00:00" }, { "name": "react/event-loop", - "version": "v1.4.0", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/reactphp/event-loop.git", - "reference": "6e7e587714fff7a83dcc7025aee42ab3b265ae05" + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/6e7e587714fff7a83dcc7025aee42ab3b265ae05", - "reference": "6e7e587714fff7a83dcc7025aee42ab3b265ae05", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", "shasum": "" }, "require": { @@ -2763,7 +2837,7 @@ ], "support": { "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.4.0" + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" }, "funding": [ { @@ -2771,20 +2845,20 @@ "type": "open_collective" } ], - "time": "2023-05-05T10:11:24+00:00" + "time": "2023-11-13T13:48:05+00:00" }, { "name": "react/http", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/reactphp/http.git", - "reference": "bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0" + "reference": "8111281ee57f22b7194f5dba225e609ba7ce4d20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/http/zipball/bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0", - "reference": "bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0", + "url": "https://api.github.com/repos/reactphp/http/zipball/8111281ee57f22b7194f5dba225e609ba7ce4d20", + "reference": "8111281ee57f22b7194f5dba225e609ba7ce4d20", "shasum": "" }, "require": { @@ -2795,14 +2869,13 @@ "react/event-loop": "^1.2", "react/promise": "^3 || ^2.3 || ^1.2.1", "react/socket": "^1.12", - "react/stream": "^1.2", - "ringcentral/psr7": "^1.2" + "react/stream": "^1.2" }, "require-dev": { "clue/http-proxy-react": "^1.8", "clue/reactphp-ssh-proxy": "^1.4", "clue/socks-react": "^1.4", - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", "react/async": "^4 || ^3 || ^2", "react/promise-stream": "^1.4", "react/promise-timer": "^1.9" @@ -2855,7 +2928,7 @@ ], "support": { "issues": "https://github.com/reactphp/http/issues", - "source": "https://github.com/reactphp/http/tree/v1.9.0" + "source": "https://github.com/reactphp/http/tree/v1.10.0" }, "funding": [ { @@ -2863,27 +2936,28 @@ "type": "open_collective" } ], - "time": "2023-04-26T10:29:24+00:00" + "time": "2024-03-27T17:20:46+00:00" }, { "name": "react/promise", - "version": "v2.10.0", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38" + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38", - "reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": ">=7.1.0" }, "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.36" + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", "autoload": { @@ -2927,7 +3001,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v2.10.0" + "source": "https://github.com/reactphp/promise/tree/v3.2.0" }, "funding": [ { @@ -2935,123 +3009,40 @@ "type": "open_collective" } ], - "time": "2023-05-02T15:15:43+00:00" - }, - { - "name": "react/promise-timer", - "version": "v1.9.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise-timer.git", - "reference": "aa7a73c74b8d8c0f622f5982ff7b0351bc29e495" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/aa7a73c74b8d8c0f622f5982ff7b0351bc29e495", - "reference": "aa7a73c74b8d8c0f622f5982ff7b0351bc29e495", - "shasum": "" - }, - "require": { - "php": ">=5.3", - "react/event-loop": "^1.2", - "react/promise": "^3.0 || ^2.7.0 || ^1.2.1" - }, - "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "React\\Promise\\Timer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.", - "homepage": "https://github.com/reactphp/promise-timer", - "keywords": [ - "async", - "event-loop", - "promise", - "reactphp", - "timeout", - "timer" - ], - "support": { - "issues": "https://github.com/reactphp/promise-timer/issues", - "source": "https://github.com/reactphp/promise-timer/tree/v1.9.0" - }, - "funding": [ - { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" - } - ], - "time": "2022-06-13T13:41:03+00:00" + "time": "2024-05-24T10:39:05+00:00" }, { "name": "react/socket", - "version": "v1.12.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/reactphp/socket.git", - "reference": "81e1b4d7f5450ebd8d2e9a95bb008bb15ca95a7b" + "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/81e1b4d7f5450ebd8d2e9a95bb008bb15ca95a7b", - "reference": "81e1b4d7f5450ebd8d2e9a95bb008bb15ca95a7b", + "url": "https://api.github.com/repos/reactphp/socket/zipball/216d3aec0b87f04a40ca04f481e6af01bdd1d038", + "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038", "shasum": "" }, "require": { "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "php": ">=5.3.0", - "react/dns": "^1.8", + "react/dns": "^1.11", "react/event-loop": "^1.2", "react/promise": "^3 || ^2.6 || ^1.2.1", - "react/promise-timer": "^1.9", "react/stream": "^1.2" }, "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", "react/async": "^4 || ^3 || ^2", - "react/promise-stream": "^1.4" + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.10" }, "type": "library", "autoload": { "psr-4": { - "React\\Socket\\": "src" + "React\\Socket\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3090,32 +3081,28 @@ ], "support": { "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.12.0" + "source": "https://github.com/reactphp/socket/tree/v1.15.0" }, "funding": [ { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2022-08-25T12:32:25+00:00" + "time": "2023-12-15T11:02:10+00:00" }, { "name": "react/stream", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/reactphp/stream.git", - "reference": "7a423506ee1903e89f1e08ec5f0ed430ff784ae9" + "reference": "6fbc9672905c7d5a885f2da2fc696f65840f4a66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/7a423506ee1903e89f1e08ec5f0ed430ff784ae9", - "reference": "7a423506ee1903e89f1e08ec5f0ed430ff784ae9", + "url": "https://api.github.com/repos/reactphp/stream/zipball/6fbc9672905c7d5a885f2da2fc696f65840f4a66", + "reference": "6fbc9672905c7d5a885f2da2fc696f65840f4a66", "shasum": "" }, "require": { @@ -3125,12 +3112,12 @@ }, "require-dev": { "clue/stream-filter": "~1.2", - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" }, "type": "library", "autoload": { "psr-4": { - "React\\Stream\\": "src" + "React\\Stream\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3172,93 +3159,28 @@ ], "support": { "issues": "https://github.com/reactphp/stream/issues", - "source": "https://github.com/reactphp/stream/tree/v1.2.0" + "source": "https://github.com/reactphp/stream/tree/v1.3.0" }, "funding": [ { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" - } - ], - "time": "2021-07-11T12:37:55+00:00" - }, - { - "name": "ringcentral/psr7", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/ringcentral/psr7.git", - "reference": "360faaec4b563958b673fb52bbe94e37f14bc686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ringcentral/psr7/zipball/360faaec4b563958b673fb52bbe94e37f14bc686", - "reference": "360faaec4b563958b673fb52bbe94e37f14bc686", - "shasum": "" - }, - "require": { - "php": ">=5.3", - "psr/http-message": "~1.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "RingCentral\\Psr7\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "description": "PSR-7 message implementation", - "keywords": [ - "http", - "message", - "stream", - "uri" - ], - "support": { - "source": "https://github.com/ringcentral/psr7/tree/master" - }, - "time": "2018-05-29T20:21:04+00:00" + "time": "2023-06-16T10:52:11+00:00" }, { "name": "symfony/console", - "version": "v5.4.28", + "version": "v5.4.40", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f4f71842f24c2023b91237c72a365306f3c58827" + "reference": "aa73115c0c24220b523625bfcfa655d7d73662dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f4f71842f24c2023b91237c72a365306f3c58827", - "reference": "f4f71842f24c2023b91237c72a365306f3c58827", + "url": "https://api.github.com/repos/symfony/console/zipball/aa73115c0c24220b523625bfcfa655d7d73662dd", + "reference": "aa73115c0c24220b523625bfcfa655d7d73662dd", "shasum": "" }, "require": { @@ -3328,7 +3250,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.28" + "source": "https://github.com/symfony/console/tree/v5.4.40" }, "funding": [ { @@ -3344,20 +3266,20 @@ "type": "tidelift" } ], - "time": "2023-08-07T06:12:30+00:00" + "time": "2024-05-31T14:33:22+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.3.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { @@ -3366,7 +3288,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -3395,7 +3317,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" }, "funding": [ { @@ -3411,20 +3333,20 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/finder", - "version": "v5.4.27", + "version": "v5.4.40", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ff4bce3c33451e7ec778070e45bd23f74214cd5d" + "reference": "f51cff4687547641c7d8180d74932ab40b2205ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ff4bce3c33451e7ec778070e45bd23f74214cd5d", - "reference": "ff4bce3c33451e7ec778070e45bd23f74214cd5d", + "url": "https://api.github.com/repos/symfony/finder/zipball/f51cff4687547641c7d8180d74932ab40b2205ce", + "reference": "f51cff4687547641c7d8180d74932ab40b2205ce", "shasum": "" }, "require": { @@ -3458,7 +3380,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.27" + "source": "https://github.com/symfony/finder/tree/v5.4.40" }, "funding": [ { @@ -3474,20 +3396,20 @@ "type": "tidelift" } ], - "time": "2023-07-31T08:02:31+00:00" + "time": "2024-05-31T14:33:22+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", "shasum": "" }, "require": { @@ -3501,9 +3423,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -3540,7 +3459,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" }, "funding": [ { @@ -3556,20 +3475,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "875e90aeea2777b6f135677f618529449334a612" + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", - "reference": "875e90aeea2777b6f135677f618529449334a612", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", "shasum": "" }, "require": { @@ -3580,9 +3499,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -3621,7 +3537,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" }, "funding": [ { @@ -3637,20 +3553,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", "shasum": "" }, "require": { @@ -3661,9 +3577,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -3705,7 +3618,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" }, "funding": [ { @@ -3721,20 +3634,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", "shasum": "" }, "require": { @@ -3748,9 +3661,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -3788,7 +3698,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" }, "funding": [ { @@ -3804,20 +3714,20 @@ "type": "tidelift" } ], - "time": "2023-07-28T09:04:16+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5" + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fe2f306d1d9d346a7fee353d0d5012e401e984b5", - "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2", + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2", "shasum": "" }, "require": { @@ -3825,9 +3735,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -3867,7 +3774,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0" }, "funding": [ { @@ -3883,20 +3790,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php74", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php74.git", - "reference": "8b755b41a155c89f1af29cc33305538499fa05ea" + "reference": "da301202eb63b838aed1e62134e2302f826ca600" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php74/zipball/8b755b41a155c89f1af29cc33305538499fa05ea", - "reference": "8b755b41a155c89f1af29cc33305538499fa05ea", + "url": "https://api.github.com/repos/symfony/polyfill-php74/zipball/da301202eb63b838aed1e62134e2302f826ca600", + "reference": "da301202eb63b838aed1e62134e2302f826ca600", "shasum": "" }, "require": { @@ -3904,9 +3811,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -3947,7 +3851,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php74/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php74/tree/v1.29.0" }, "funding": [ { @@ -3963,20 +3867,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "shasum": "" }, "require": { @@ -3984,9 +3888,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -4030,7 +3931,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { @@ -4046,20 +3947,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b" + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b", - "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d", + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d", "shasum": "" }, "require": { @@ -4067,9 +3968,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -4109,7 +4007,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0" }, "funding": [ { @@ -4125,20 +4023,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/process", - "version": "v5.4.28", + "version": "v5.4.40", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b" + "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b", - "reference": "45261e1fccad1b5447a8d7a8e67aa7b4a9798b7b", + "url": "https://api.github.com/repos/symfony/process/zipball/deedcb3bb4669cae2148bc920eafd2b16dc7c046", + "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046", "shasum": "" }, "require": { @@ -4171,7 +4069,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.28" + "source": "https://github.com/symfony/process/tree/v5.4.40" }, "funding": [ { @@ -4187,20 +4085,20 @@ "type": "tidelift" } ], - "time": "2023-08-07T10:36:04+00:00" + "time": "2024-05-31T14:33:22+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.5.2", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a2329596ddc8fd568900e3fc76cba42489ecc7f3", + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3", "shasum": "" }, "require": { @@ -4254,7 +4152,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.3" }, "funding": [ { @@ -4270,20 +4168,20 @@ "type": "tidelift" } ], - "time": "2022-05-30T19:17:29+00:00" + "time": "2023-04-21T15:04:16+00:00" }, { "name": "symfony/string", - "version": "v5.4.26", + "version": "v5.4.40", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "1181fe9270e373537475e826873b5867b863883c" + "reference": "142877285aa974a6f7685e292ab5ba9aae86b143" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/1181fe9270e373537475e826873b5867b863883c", - "reference": "1181fe9270e373537475e826873b5867b863883c", + "url": "https://api.github.com/repos/symfony/string/zipball/142877285aa974a6f7685e292ab5ba9aae86b143", + "reference": "142877285aa974a6f7685e292ab5ba9aae86b143", "shasum": "" }, "require": { @@ -4340,7 +4238,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.26" + "source": "https://github.com/symfony/string/tree/v5.4.40" }, "funding": [ { @@ -4356,7 +4254,7 @@ "type": "tidelift" } ], - "time": "2023-06-28T12:46:07+00:00" + "time": "2024-05-31T14:33:22+00:00" } ], "packages-dev": [ @@ -4851,16 +4749,16 @@ }, { "name": "php-parallel-lint/php-parallel-lint", - "version": "v1.3.2", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git", - "reference": "6483c9832e71973ed29cf71bd6b3f4fde438a9de" + "reference": "6db563514f27e19595a19f45a4bf757b6401194e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/6483c9832e71973ed29cf71bd6b3f4fde438a9de", - "reference": "6483c9832e71973ed29cf71bd6b3f4fde438a9de", + "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/6db563514f27e19595a19f45a4bf757b6401194e", + "reference": "6db563514f27e19595a19f45a4bf757b6401194e", "shasum": "" }, "require": { @@ -4898,26 +4796,30 @@ "email": "ahoj@jakubonderka.cz" } ], - "description": "This tool check syntax of PHP files about 20x faster than serial check.", + "description": "This tool checks the syntax of PHP files about 20x faster than serial check.", "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint", + "keywords": [ + "lint", + "static analysis" + ], "support": { "issues": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/issues", - "source": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/tree/v1.3.2" + "source": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/tree/v1.4.0" }, - "time": "2022-02-21T12:50:22+00:00" + "time": "2024-03-27T12:14:49+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "1.2.x-dev", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "788ea1bd84f7848abf27ba29b92c6c9d285dfc95" + "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/788ea1bd84f7848abf27ba29b92c6c9d285dfc95", - "reference": "788ea1bd84f7848abf27ba29b92c6c9d285dfc95", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/fa8cce7720fa782899a0aa97b6a41225d1bb7b26", + "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26", "shasum": "" }, "require": { @@ -4929,7 +4831,6 @@ "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^9.5" }, - "default-branch": true, "type": "phpstan-extension", "extra": { "phpstan": { @@ -4952,20 +4853,20 @@ "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.2.0" }, - "time": "2023-09-19T08:17:29+00:00" + "time": "2024-04-20T06:39:48+00:00" }, { "name": "phpstan/phpstan-nette", - "version": "1.2.9", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-nette.git", - "reference": "0e3a6805917811d685e59bb83c2286315f2f6d78" + "reference": "9eebad8ceb52ad5e8f7c5012c4b7ed8777673ba0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/0e3a6805917811d685e59bb83c2286315f2f6d78", - "reference": "0e3a6805917811d685e59bb83c2286315f2f6d78", + "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/9eebad8ceb52ad5e8f7c5012c4b7ed8777673ba0", + "reference": "9eebad8ceb52ad5e8f7c5012c4b7ed8777673ba0", "shasum": "" }, "require": { @@ -4986,7 +4887,6 @@ "nette/utils": "^2.3.0 || ^3.0.0", "nikic/php-parser": "^4.13.2", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-php-parser": "^1.1", "phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.5" @@ -5012,27 +4912,27 @@ "description": "Nette Framework class reflection extension for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-nette/issues", - "source": "https://github.com/phpstan/phpstan-nette/tree/1.2.9" + "source": "https://github.com/phpstan/phpstan-nette/tree/1.3.1" }, - "time": "2023-04-12T14:11:53+00:00" + "time": "2024-06-07T09:36:02+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "1.3.16", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "d5242a59d035e46774f2e634b374bc39ff62cb95" + "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d5242a59d035e46774f2e634b374bc39ff62cb95", - "reference": "d5242a59d035e46774f2e634b374bc39ff62cb95", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f3ea021866f4263f07ca3636bf22c64be9610c11", + "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^1.11" }, "conflict": { "phpunit/phpunit": "<7.0" @@ -5064,22 +4964,22 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.16" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0" }, - "time": "2024-02-23T09:51:20+00:00" + "time": "2024-04-20T06:39:00+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.6.x-dev", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "a3b0404c40197996b6ed32b2613e5a337fcbefd4" + "reference": "363f921dd8441777d4fc137deb99beb486c77df1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/a3b0404c40197996b6ed32b2613e5a337fcbefd4", - "reference": "a3b0404c40197996b6ed32b2613e5a337fcbefd4", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/363f921dd8441777d4fc137deb99beb486c77df1", + "reference": "363f921dd8441777d4fc137deb99beb486c77df1", "shasum": "" }, "require": { @@ -5093,7 +4993,6 @@ "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^9.5" }, - "default-branch": true, "type": "phpstan-extension", "extra": { "phpstan": { @@ -5116,7 +5015,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.0" }, - "time": "2023-10-30T14:35:14+00:00" + "time": "2024-04-20T06:37:51+00:00" }, { "name": "phpunit/php-code-coverage", From eb766816e03e8b86ec34858dcf59a0fe2bb847fc Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 20 Jun 2024 17:52:01 +0200 Subject: [PATCH 42/55] Update composer.lock --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index f18481ad71..5c135e2347 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "db09e230b5029b6247349873fc13819a", + "content-hash": "30f0be2f5f4d8a9074cff904026ec2a6", "packages": [ { "name": "clue/ndjson-react", From f8b9b8a089d14002959b9af00e0d6124a26815d2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 20 Jun 2024 17:55:09 +0200 Subject: [PATCH 43/55] cs --- src/Type/Php/PregMatchParameterOutTypeExtension.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Type/Php/PregMatchParameterOutTypeExtension.php b/src/Type/Php/PregMatchParameterOutTypeExtension.php index 277ce0593f..91844751f3 100644 --- a/src/Type/Php/PregMatchParameterOutTypeExtension.php +++ b/src/Type/Php/PregMatchParameterOutTypeExtension.php @@ -4,8 +4,6 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; -use PHPStan\Analyser\TypeSpecifier; -use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\TrinaryLogic; From ae0b208cf2526f2e6f4fce8a0d87a1e0533c5ba8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 20 Jun 2024 17:58:10 +0200 Subject: [PATCH 44/55] use conditionalTags --- conf/config.neon | 12 ++++-------- src/Type/Php/PregMatchParameterOutTypeExtension.php | 3 +-- src/Type/Php/PregMatchTypeSpecifyingExtension.php | 3 +-- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index dfe7222891..b7521b88aa 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -285,6 +285,10 @@ conditionalTags: phpstan.parser.richParserNodeVisitor: %featureToggles.curlSetOptTypes% PHPStan\Parser\TypeTraverserInstanceofVisitor: phpstan.parser.richParserNodeVisitor: %featureToggles.instanceofType% + PHPStan\Type\Php\PregMatchTypeSpecifyingExtension: + phpstan.typeSpecifier.functionTypeSpecifyingExtension: %featureToggles.narrowPregMatches% + PHPStan\Type\Php\PregMatchParameterOutTypeExtension: + phpstan.functionParameterOutTypeExtension: %featureToggles.narrowPregMatches% services: - @@ -1468,17 +1472,9 @@ services: - class: PHPStan\Type\Php\PregMatchTypeSpecifyingExtension - arguments: - disabled: %featureToggles.narrowPregMatches% - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - class: PHPStan\Type\Php\PregMatchParameterOutTypeExtension - arguments: - disabled: %featureToggles.narrowPregMatches% - tags: - - phpstan.functionParameterOutTypeExtension - class: PHPStan\Type\Php\RegexArrayShapeMatcher diff --git a/src/Type/Php/PregMatchParameterOutTypeExtension.php b/src/Type/Php/PregMatchParameterOutTypeExtension.php index 91844751f3..2a86bbf864 100644 --- a/src/Type/Php/PregMatchParameterOutTypeExtension.php +++ b/src/Type/Php/PregMatchParameterOutTypeExtension.php @@ -17,14 +17,13 @@ final class PregMatchParameterOutTypeExtension implements FunctionParameterOutTy public function __construct( private RegexArrayShapeMatcher $regexShapeMatcher, - private bool $disabled, ) { } public function isFunctionSupported(FunctionReflection $functionReflection, ParameterReflection $parameter): bool { - return !$this->disabled && in_array(strtolower($functionReflection->getName()), ['preg_match'], true) && $parameter->getName() === 'matches'; + return in_array(strtolower($functionReflection->getName()), ['preg_match'], true) && $parameter->getName() === 'matches'; } public function getParameterOutTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $funcCall, ParameterReflection $parameter, Scope $scope): ?Type diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php index 14aa49b98a..4f11c5dce0 100644 --- a/src/Type/Php/PregMatchTypeSpecifyingExtension.php +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -21,7 +21,6 @@ final class PregMatchTypeSpecifyingExtension implements FunctionTypeSpecifyingEx public function __construct( private RegexArrayShapeMatcher $regexShapeMatcher, - private bool $disabled, ) { } @@ -33,7 +32,7 @@ public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool { - return !$this->disabled && in_array(strtolower($functionReflection->getName()), ['preg_match'], true) && $context->true(); + return in_array(strtolower($functionReflection->getName()), ['preg_match'], true) && $context->true(); } public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes From 6ae7dda0e3b9522393862e3656cd2fd57ee3337b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 20 Jun 2024 19:17:18 +0200 Subject: [PATCH 45/55] Update preg_match_shapes.php --- tests/PHPStan/Analyser/nsrt/preg_match_shapes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index bac99c32c1..13fc0804d2 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -1,4 +1,4 @@ -= 7.4 namespace PregMatchShapes; From 84a4ca1c8fdd1e58462813dbd99b2d7135d3c60d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 21 Jun 2024 08:25:28 +0200 Subject: [PATCH 46/55] Improve negative context inference --- .../Php/PregMatchTypeSpecifyingExtension.php | 12 +++-- src/Type/Php/RegexArrayShapeMatcher.php | 5 ++ .../Analyser/LegacyNodeScopeResolverTest.php | 2 +- .../Analyser/nsrt/preg_match_shapes_php73.php | 4 +- ...shapes.php => preg_match_shapes_php74.php} | 47 +++++++++---------- .../Analyser/nsrt/preg_match_shapes_php82.php | 2 +- .../BooleanOrConstantConditionRuleTest.php | 11 ++++- .../Rules/Comparison/data/bug-6551.php | 4 +- 8 files changed, 54 insertions(+), 33 deletions(-) rename tests/PHPStan/Analyser/nsrt/{preg_match_shapes.php => preg_match_shapes_php74.php} (74%) diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php index 4f11c5dce0..49d82df6f0 100644 --- a/src/Type/Php/PregMatchTypeSpecifyingExtension.php +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -32,7 +32,7 @@ public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool { - return in_array(strtolower($functionReflection->getName()), ['preg_match'], true) && $context->true(); + return in_array(strtolower($functionReflection->getName()), ['preg_match'], true) && !$context->null(); } public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes @@ -54,16 +54,22 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $flagsType = $scope->getType($flagsArg->value); } - $matchedType = $this->regexShapeMatcher->matchType($patternType, $flagsType, TrinaryLogic::createYes()); + $matchedType = $this->regexShapeMatcher->matchType($patternType, $flagsType, TrinaryLogic::createFromBoolean($context->true())); if ($matchedType === null) { return new SpecifiedTypes(); } + $overwrite = false; + if ($context->false()) { + $overwrite = true; + $context = $context->negate(); + } + return $this->typeSpecifier->create( $matchesArg->value, $matchedType, $context, - false, + $overwrite, $scope, $node, ); diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index 80f82010de..018dd82162 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -11,6 +11,7 @@ use Nette\Utils\Strings; use PHPStan\Php\PhpVersion; use PHPStan\TrinaryLogic; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; @@ -44,6 +45,10 @@ public function __construct( public function matchType(Type $patternType, ?Type $flagsType, TrinaryLogic $wasMatched): ?Type { + if ($wasMatched->no()) { + return new ConstantArrayType([], []); + } + if ( !$this->phpVersion->returnsPregUnmatchedCapturingGroups() ) { diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index dd8222ec2a..e165bbb820 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -361,7 +361,7 @@ public function dataAssignInIf(): array $testScope, 'matches4', TrinaryLogic::createMaybe(), - 'array{0?: string}', + 'array{}|array{string}', ], [ $testScope, diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php73.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php73.php index 53e23ccff5..83cb5eb4e6 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php73.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php73.php @@ -7,6 +7,8 @@ function doUnmatchedAsNull(string $s): void { if (preg_match('/(foo)?(bar)?(baz)?/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { assertType('array', $matches); + } else { + assertType('array{}', $matches); } - assertType('array', $matches); + assertType('array{}|array', $matches); } diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php74.php similarity index 74% rename from tests/PHPStan/Analyser/nsrt/preg_match_shapes.php rename to tests/PHPStan/Analyser/nsrt/preg_match_shapes_php74.php index 13fc0804d2..1c4c1b4be9 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php74.php @@ -9,77 +9,76 @@ function doMatch(string $s): void { if (preg_match('/Price: /i', $s, $matches)) { assertType('array{string}', $matches); } - assertType('array{0?: string}', $matches); + assertType('array{}|array{string}', $matches); if (preg_match('/Price: (£|€)\d+/', $s, $matches)) { assertType('array{string, string}', $matches); } else { - // could be assertType('array{}', $matches); - assertType('array{0?: string, 1?: string}', $matches); + assertType('array{}', $matches); } - assertType('array{0?: string, 1?: string}', $matches); + assertType('array{}|array{string, string}', $matches); if (preg_match('/Price: (£|€)(\d+)/i', $s, $matches)) { assertType('array{string, string, string}', $matches); } - assertType('array{0?: string, 1?: string, 2?: string}', $matches); + assertType('array{}|array{string, string, string}', $matches); if (preg_match(' /Price: (£|€)\d+/ i u', $s, $matches)) { assertType('array{string, string}', $matches); } - assertType('array{0?: string, 1?: string}', $matches); + assertType('array{}|array{string, string}', $matches); if (preg_match('(Price: (£|€))i', $s, $matches)) { assertType('array{string, string}', $matches); } - assertType('array{0?: string, 1?: string}', $matches); + assertType('array{}|array{string, string}', $matches); if (preg_match('_foo(.)\_i_i', $s, $matches)) { assertType('array{string, string}', $matches); } - assertType('array{0?: string, 1?: string}', $matches); + assertType('array{}|array{string, string}', $matches); if (preg_match('/(a)(b)*(c)(d)*/', $s, $matches)) { assertType('array{0: string, 1: string, 2: string, 3?: string, 4?: string}', $matches); } - assertType('array{0?: string, 1?: string, 2?: string, 3?: string, 4?: string}', $matches); + assertType('array{}|array{0: string, 1: string, 2: string, 3?: string, 4?: string}', $matches); if (preg_match('/(a|b)|(?:c)/', $s, $matches)) { assertType('array{0: string, 1?: string}', $matches); } - assertType('array{0?: string, 1?: string}', $matches); + assertType('array{}|array{0: string, 1?: string}', $matches); if (preg_match('/(foo)(bar)(baz)+/', $s, $matches)) { assertType('array{string, string, string, string}', $matches); } - assertType('array{0?: string, 1?: string, 2?: string, 3?: string}', $matches); + assertType('array{}|array{string, string, string, string}', $matches); if (preg_match('/(foo)(bar)(baz)*/', $s, $matches)) { assertType('array{0: string, 1: string, 2: string, 3?: string}', $matches); } - assertType('array{0?: string, 1?: string, 2?: string, 3?: string}', $matches); + assertType('array{}|array{0: string, 1: string, 2: string, 3?: string}', $matches); if (preg_match('/(foo)(bar)(baz)?/', $s, $matches)) { assertType('array{0: string, 1: string, 2: string, 3?: string}', $matches); } - assertType('array{0?: string, 1?: string, 2?: string, 3?: string}', $matches); + assertType('array{}|array{0: string, 1: string, 2: string, 3?: string}', $matches); if (preg_match('/(foo)(bar)(baz){0,3}/', $s, $matches)) { assertType('array{0: string, 1: string, 2: string, 3?: string}', $matches); } - assertType('array{0?: string, 1?: string, 2?: string, 3?: string}', $matches); + assertType('array{}|array{0: string, 1: string, 2: string, 3?: string}', $matches); if (preg_match('/(foo)(bar)(baz){2,3}/', $s, $matches)) { assertType('array{string, string, string, string}', $matches); } - assertType('array{0?: string, 1?: string, 2?: string, 3?: string}', $matches); + assertType('array{}|array{string, string, string, string}', $matches); } function doNonCapturingGroup(string $s): void { if (preg_match('/Price: (?:£|€)(\d+)/', $s, $matches)) { assertType('array{string, string}', $matches); } - assertType('array{0?: string, 1?: string}', $matches); + assertType('array{}|array{string, string}', $matches); } function doNamedSubpattern(string $s): void { @@ -92,26 +91,26 @@ function doNamedSubpattern(string $s): void { if (preg_match('/^(?\S+::\S+)/', $s, $matches)) { assertType('array{0: string, name: string, 1: string}', $matches); } - assertType('array{0?: string, name?: string, 1?: string}', $matches); + assertType('array{}|array{0: string, name: string, 1: string}', $matches); if (preg_match('/^(?\S+::\S+)(?:(? with data set (?:#\d+|"[^"]+"))\s\()?/', $s, $matches)) { assertType('array{0: string, name: string, 1: string, dataname?: string, 2?: string}', $matches); } - assertType('array{0?: string, name?: string, 1?: string, dataname?: string, 2?: string}', $matches); + assertType('array{}|array{0: string, name: string, 1: string, dataname?: string, 2?: string}', $matches); } function doOffsetCapture(string $s): void { if (preg_match('/(foo)(bar)(baz)/', $s, $matches, PREG_OFFSET_CAPTURE)) { assertType('array{array{string, int<0, max>}, array{string, int<0, max>}, array{string, int<0, max>}, array{string, int<0, max>}}', $matches); } - assertType('array{0?: array{string, int<0, max>}, 1?: array{string, int<0, max>}, 2?: array{string, int<0, max>}, 3?: array{string, int<0, max>}}', $matches); + assertType('array{}|array{array{string, int<0, max>}, array{string, int<0, max>}, array{string, int<0, max>}, array{string, int<0, max>}}', $matches); } function doUnmatchedAsNull(string $s): void { if (preg_match('/(foo)?(bar)?(baz)?/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { assertType('array{0: string, 1?: string|null, 2?: string|null, 3?: string|null}', $matches); } - assertType('array{0?: string, 1?: string|null, 2?: string|null, 3?: string|null}', $matches); + assertType('array{}|array{0: string, 1?: string|null, 2?: string|null, 3?: string|null}', $matches); } function doOffsetCaptureWithUnmatchedNull(string $s): void { @@ -119,7 +118,7 @@ function doOffsetCaptureWithUnmatchedNull(string $s): void { if (preg_match('/(foo)(bar)(baz)/', $s, $matches, PREG_OFFSET_CAPTURE|PREG_UNMATCHED_AS_NULL)) { assertType('array{array{string|null, int<-1, max>}, array{string|null, int<-1, max>}, array{string|null, int<-1, max>}, array{string|null, int<-1, max>}}', $matches); } - assertType('array{0?: array{string|null, int<-1, max>}, 1?: array{string|null, int<-1, max>}, 2?: array{string|null, int<-1, max>}, 3?: array{string|null, int<-1, max>}}', $matches); + assertType('array{}|array{array{string|null, int<-1, max>}, array{string|null, int<-1, max>}, array{string|null, int<-1, max>}, array{string|null, int<-1, max>}}', $matches); } function doUnknownFlags(string $s, int $flags): void { @@ -158,12 +157,12 @@ function hoaBug31(string $s): void { if (preg_match('/([\w-])/', $s, $matches)) { assertType('array{string, string}', $matches); } - assertType('array{0?: string, 1?: string}', $matches); + assertType('array{}|array{string, string}', $matches); if (preg_match('/\w-(\d+)-(\w)/', $s, $matches)) { assertType('array{string, string, string}', $matches); } - assertType('array{0?: string, 1?: string, 2?: string}', $matches); + assertType('array{}|array{string, string, string}', $matches); } // https://github.com/phpstan/phpstan/issues/10855#issuecomment-2044323638 @@ -171,5 +170,5 @@ function testHoaUnsupportedRegexSyntax(string $s): void { if (preg_match('#\QPHPDoc type array of property App\Log::$fillable is not covariant with PHPDoc type array of overridden property Illuminate\Database\E\\\\\QEloquent\Model::$fillable.\E#', $s, $matches)) { assertType('array{string}', $matches); } - assertType('array{0?: string}', $matches); + assertType('array{}|array{string}', $matches); } diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php82.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php82.php index 88711da54f..86cf0b0cdf 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php82.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php82.php @@ -10,7 +10,7 @@ function doNonAutoCapturingFlag(string $s): void { if (preg_match('/(\d+)/n', $s, $matches)) { assertType('array{string}', $matches); } - assertType('array{0?: string}', $matches); + assertType('array{}|array{string}', $matches); if (preg_match('/(\d+)(?P\d+)/n', $s, $matches)) { // could be assertType('array{0: string, num: string, 1: string}', $matches); diff --git a/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php index b8b0777ca2..ee616efbc5 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php @@ -435,7 +435,16 @@ public function testBug6551(): void { $this->treatPhpDocTypesAsCertain = true; $this->reportAlwaysTrueInLastCondition = true; - $this->analyse([__DIR__ . '/data/bug-6551.php'], []); + $this->analyse([__DIR__ . '/data/bug-6551.php'], [ + [ + 'Result of || is always true.', + 49, + ], + [ + 'Result of || is always true.', + 61, + ], + ]); } public function testBug4004(): void diff --git a/tests/PHPStan/Rules/Comparison/data/bug-6551.php b/tests/PHPStan/Rules/Comparison/data/bug-6551.php index 3b3e9574a6..561fbc9cfd 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-6551.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-6551.php @@ -46,7 +46,7 @@ function (): void { foreach ($data as $key => $value) { $match = []; - assertType('bool', preg_match('/^c(\d+)$/', $key, $match) || empty($match)); + assertType('true', preg_match('/^c(\d+)$/', $key, $match) || empty($match)); } }; @@ -58,6 +58,6 @@ function (): void { ]; foreach ($data as $key => $value) { - assertType('bool', preg_match('/^c(\d+)$/', $key, $match) || empty($match)); + assertType('true', preg_match('/^c(\d+)$/', $key, $match) || empty($match)); } }; From 28103f8f5acc804249325f685aaaaa6b9f913ea4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 21 Jun 2024 08:38:30 +0200 Subject: [PATCH 47/55] separate php 7.x expectations --- .../Analyser/LegacyNodeScopeResolverTest.php | 10 +++++----- tests/PHPStan/Analyser/ParamOutTypeTest.php | 7 +++++++ tests/PHPStan/Analyser/data/param-out-php7.php | 17 +++++++++++++++++ tests/PHPStan/Analyser/data/param-out-php8.php | 17 +++++++++++++++++ tests/PHPStan/Analyser/data/param-out.php | 11 ----------- 5 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/param-out-php7.php create mode 100644 tests/PHPStan/Analyser/data/param-out-php8.php diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index e165bbb820..effa9605a6 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -302,7 +302,7 @@ public function dataAssignInIf(): array $testScope, 'matches', TrinaryLogic::createYes(), - 'array{0?: string}', + PHP_VERSION_ID <= 80000 ? 'array' : 'array{0?: string}', ], [ $testScope, @@ -343,7 +343,7 @@ public function dataAssignInIf(): array $testScope, 'matches2', TrinaryLogic::createMaybe(), - 'array{0?: string}', + PHP_VERSION_ID <= 80000 ? 'array' : 'array{0?: string}', ], [ $testScope, @@ -355,13 +355,13 @@ public function dataAssignInIf(): array $testScope, 'matches3', TrinaryLogic::createYes(), - 'array{0?: string}', + PHP_VERSION_ID <= 80000 ? 'array' : 'array{0?: string}', ], [ $testScope, 'matches4', TrinaryLogic::createMaybe(), - 'array{}|array{string}', + PHP_VERSION_ID <= 80000 ? 'array' : 'array{}|array{string}', ], [ $testScope, @@ -415,7 +415,7 @@ public function dataAssignInIf(): array $testScope, 'ternaryMatches', TrinaryLogic::createYes(), - 'array{0?: string}', + PHP_VERSION_ID <= 80000 ? 'array' : 'array{0?: string}', ], [ $testScope, diff --git a/tests/PHPStan/Analyser/ParamOutTypeTest.php b/tests/PHPStan/Analyser/ParamOutTypeTest.php index b29b6fbd0c..5c655fa069 100644 --- a/tests/PHPStan/Analyser/ParamOutTypeTest.php +++ b/tests/PHPStan/Analyser/ParamOutTypeTest.php @@ -3,12 +3,19 @@ namespace PHPStan\Analyser; use PHPStan\Testing\TypeInferenceTestCase; +use const PHP_VERSION_ID; class ParamOutTypeTest extends TypeInferenceTestCase { public function dataFileAsserts(): iterable { + if (PHP_VERSION_ID < 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/param-out-php7.php'); + } + if (PHP_VERSION_ID >= 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/param-out-php8.php'); + } yield from $this->gatherAssertTypes(__DIR__ . '/data/param-out.php'); } diff --git a/tests/PHPStan/Analyser/data/param-out-php7.php b/tests/PHPStan/Analyser/data/param-out-php7.php new file mode 100644 index 0000000000..3edd9c2e21 --- /dev/null +++ b/tests/PHPStan/Analyser/data/param-out-php7.php @@ -0,0 +1,17 @@ +>', $matches); + + preg_match_all('/@[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/', $input, $matches, PREG_SET_ORDER); + assertType('list>', $matches); + + preg_match('/@[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/', $input, $matches, PREG_UNMATCHED_AS_NULL); + assertType("array{0?: string}", $matches); +} + diff --git a/tests/PHPStan/Analyser/data/param-out-php8.php b/tests/PHPStan/Analyser/data/param-out-php8.php new file mode 100644 index 0000000000..3f016152f5 --- /dev/null +++ b/tests/PHPStan/Analyser/data/param-out-php8.php @@ -0,0 +1,17 @@ +>', $matches); + + preg_match_all('/@[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/', $input, $matches, PREG_SET_ORDER); + assertType('list>', $matches); + + preg_match('/@[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/', $input, $matches, PREG_UNMATCHED_AS_NULL); + assertType("array{0?: string}", $matches); +} + diff --git a/tests/PHPStan/Analyser/data/param-out.php b/tests/PHPStan/Analyser/data/param-out.php index e70ba12b2b..c6400978fc 100644 --- a/tests/PHPStan/Analyser/data/param-out.php +++ b/tests/PHPStan/Analyser/data/param-out.php @@ -283,17 +283,6 @@ function fooScanf(): void assertType('float|int|string|null', $p2); } -function fooMatch(string $input): void { - preg_match_all('/@[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/', $input, $matches, PREG_PATTERN_ORDER); - assertType('array>', $matches); - - preg_match_all('/@[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/', $input, $matches, PREG_SET_ORDER); - assertType('list>', $matches); - - preg_match('/@[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/', $input, $matches, PREG_UNMATCHED_AS_NULL); - assertType("array{0?: string}", $matches); -} - function fooParams(ExtendsFooBar $subX, float $x1, float $y1) { $subX->renamedParams($x1, $y1); From 891a631e2ff032392a5130a88b15c80f78d19c72 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 21 Jun 2024 08:43:48 +0200 Subject: [PATCH 48/55] php 7.2/7.3 expectation fixes --- tests/PHPStan/Analyser/data/param-out-php7.php | 8 +++++++- tests/PHPStan/Analyser/data/param-out-php8.php | 5 +++++ tests/PHPStan/Analyser/data/param-out.php | 5 ----- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/PHPStan/Analyser/data/param-out-php7.php b/tests/PHPStan/Analyser/data/param-out-php7.php index 3edd9c2e21..48b9b12015 100644 --- a/tests/PHPStan/Analyser/data/param-out-php7.php +++ b/tests/PHPStan/Analyser/data/param-out-php7.php @@ -12,6 +12,12 @@ function fooMatch(string $input): void { assertType('list>', $matches); preg_match('/@[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/', $input, $matches, PREG_UNMATCHED_AS_NULL); - assertType("array{0?: string}", $matches); + assertType("array", $matches); } +function testMatch() { + preg_match('#.*#', 'foo', $matches); + assertType('array', $matches); +} + + diff --git a/tests/PHPStan/Analyser/data/param-out-php8.php b/tests/PHPStan/Analyser/data/param-out-php8.php index 3f016152f5..fc45c93dc7 100644 --- a/tests/PHPStan/Analyser/data/param-out-php8.php +++ b/tests/PHPStan/Analyser/data/param-out-php8.php @@ -15,3 +15,8 @@ function fooMatch(string $input): void { assertType("array{0?: string}", $matches); } +function testMatch() { + preg_match('#.*#', 'foo', $matches); + assertType('array{0?: string}', $matches); +} + diff --git a/tests/PHPStan/Analyser/data/param-out.php b/tests/PHPStan/Analyser/data/param-out.php index c6400978fc..f5c889400a 100644 --- a/tests/PHPStan/Analyser/data/param-out.php +++ b/tests/PHPStan/Analyser/data/param-out.php @@ -305,11 +305,6 @@ function fooDateTime(\SplFileObject $splFileObject, ?string $wouldBlock) { assertType('string', $wouldBlock); } -function testMatch() { - preg_match('#.*#', 'foo', $matches); - assertType('array{0?: string}', $matches); -} - function testParseStr() { $str="first=value&arr[]=foo+bar&arr[]=baz"; parse_str($str, $output); From c073c6a27f75bf21004a57d91941389e89588901 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 21 Jun 2024 09:41:48 +0200 Subject: [PATCH 49/55] fix phpstan --- src/Testing/TypeInferenceTestCase.php | 5 +++++ src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 067612d25b..41da709214 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -18,12 +18,14 @@ use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; use Symfony\Component\Finder\Finder; +use function array_key_exists; use function array_map; use function array_merge; use function count; @@ -302,6 +304,9 @@ private static function isFileLintSkipped(string $file): bool @fclose($f); if (preg_match('~getType($args[$checkArg]->value); + if (!array_key_exists(2, $matches)) { + throw new ShouldNotHappenException(); + } + if ($matches[2] === 's' && $checkArgType->isString()->yes()) { $singlePlaceholderEarlyReturn = $checkArgType; } elseif ($matches[2] !== 's') { From db824cdc3e8ad86bf590cfe3e78abc7afcba4678 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 21 Jun 2024 09:52:59 +0200 Subject: [PATCH 50/55] Update LegacyNodeScopeResolverTest.php --- tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index effa9605a6..5fc2c17466 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -361,7 +361,13 @@ public function dataAssignInIf(): array $testScope, 'matches4', TrinaryLogic::createMaybe(), - PHP_VERSION_ID <= 80000 ? 'array' : 'array{}|array{string}', + PHP_VERSION_ID <= 80000 ? + ( + PHP_VERSION_ID < 70400 ? + 'array' : + 'array{}|array{string}' + ) + : 'array{}|array{string}', ], [ $testScope, From d74d107ce216f168a823005a14f766cf3c227d73 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 21 Jun 2024 09:59:06 +0200 Subject: [PATCH 51/55] move test into separate file --- .../Analyser/nsrt/preg_match_shapes_php74.php | 8 -------- .../Analyser/nsrt/preg_match_shapes_php80.php | 13 +++++++++++++ 2 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/preg_match_shapes_php80.php diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php74.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php74.php index 1c4c1b4be9..8d432e9c52 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php74.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php74.php @@ -113,14 +113,6 @@ function doUnmatchedAsNull(string $s): void { assertType('array{}|array{0: string, 1?: string|null, 2?: string|null, 3?: string|null}', $matches); } -function doOffsetCaptureWithUnmatchedNull(string $s): void { - // see https://3v4l.org/07rBO#v8.2.9 - if (preg_match('/(foo)(bar)(baz)/', $s, $matches, PREG_OFFSET_CAPTURE|PREG_UNMATCHED_AS_NULL)) { - assertType('array{array{string|null, int<-1, max>}, array{string|null, int<-1, max>}, array{string|null, int<-1, max>}, array{string|null, int<-1, max>}}', $matches); - } - assertType('array{}|array{array{string|null, int<-1, max>}, array{string|null, int<-1, max>}, array{string|null, int<-1, max>}, array{string|null, int<-1, max>}}', $matches); -} - function doUnknownFlags(string $s, int $flags): void { if (preg_match('/(foo)(bar)(baz)/xyz', $s, $matches, $flags)) { assertType('array}|string|null>', $matches); diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php80.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php80.php new file mode 100644 index 0000000000..d3f0421457 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php80.php @@ -0,0 +1,13 @@ += 8.0 + +namespace PregMatchShapesPhp82; + +use function PHPStan\Testing\assertType; + +function doOffsetCaptureWithUnmatchedNull(string $s): void { + // see https://3v4l.org/07rBO#v8.2.9 + if (preg_match('/(foo)(bar)(baz)/', $s, $matches, PREG_OFFSET_CAPTURE|PREG_UNMATCHED_AS_NULL)) { + assertType('array{array{string|null, int<-1, max>}, array{string|null, int<-1, max>}, array{string|null, int<-1, max>}, array{string|null, int<-1, max>}}', $matches); + } + assertType('array{}|array{array{string|null, int<-1, max>}, array{string|null, int<-1, max>}, array{string|null, int<-1, max>}, array{string|null, int<-1, max>}}', $matches); +} From 1ccfcfea42cbbc70f8c458f533dd51c912c757f5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 21 Jun 2024 10:02:27 +0200 Subject: [PATCH 52/55] turn test into a php 7.3 only test --- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 4 ++++ .../Analyser/{nsrt => data}/preg_match_shapes_php73.php | 0 2 files changed, 4 insertions(+) rename tests/PHPStan/Analyser/{nsrt => data}/preg_match_shapes_php73.php (100%) diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 3a1bf98033..5a0f3e8fff 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -172,6 +172,10 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-9499.php'); } + if (PHP_VERSION_ID >= 70300 && PHP_VERSION_ID < 70400) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_match_shapes_php73.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/PhpDoc/data/bug-8609-function.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-5365.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-6551.php'); diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php73.php b/tests/PHPStan/Analyser/data/preg_match_shapes_php73.php similarity index 100% rename from tests/PHPStan/Analyser/nsrt/preg_match_shapes_php73.php rename to tests/PHPStan/Analyser/data/preg_match_shapes_php73.php From 71f4223fd5f8b399fb34f9cda639c475e25f4950 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 21 Jun 2024 10:06:38 +0200 Subject: [PATCH 53/55] Update preg_match_shapes_php73.php --- tests/PHPStan/Analyser/data/preg_match_shapes_php73.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/data/preg_match_shapes_php73.php b/tests/PHPStan/Analyser/data/preg_match_shapes_php73.php index 83cb5eb4e6..2e37134ac7 100644 --- a/tests/PHPStan/Analyser/data/preg_match_shapes_php73.php +++ b/tests/PHPStan/Analyser/data/preg_match_shapes_php73.php @@ -10,5 +10,5 @@ function doUnmatchedAsNull(string $s): void { } else { assertType('array{}', $matches); } - assertType('array{}|array', $matches); + assertType('array', $matches); } From 328fcb91de1605c1a578e45d72f20952b3312501 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 21 Jun 2024 11:28:33 +0200 Subject: [PATCH 54/55] TypeSpecifier - understand `preg_match() === 1` same way as `preg_match()` --- src/Analyser/TypeSpecifier.php | 16 ++++++++++ .../Analyser/nsrt/preg_match_shapes_php74.php | 31 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index f54a79f35e..faba735c4e 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1989,6 +1989,22 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty $unwrappedRightExpr = $rightExpr->getExpr(); } $rightType = $scope->getType($rightExpr); + + if ( + $context->true() + && $unwrappedLeftExpr instanceof FuncCall + && $unwrappedLeftExpr->name instanceof Name + && $unwrappedLeftExpr->name->toLowerString() === 'preg_match' + && (new ConstantIntegerType(1))->isSuperTypeOf($rightType)->yes() + ) { + return $this->specifyTypesInCondition( + $scope, + $leftExpr, + $context, + $rootExpr, + ); + } + if ( $context->true() && $unwrappedLeftExpr instanceof FuncCall diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php74.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php74.php index 8d432e9c52..c5f9e9ded6 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php74.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php74.php @@ -164,3 +164,34 @@ function testHoaUnsupportedRegexSyntax(string $s): void { } assertType('array{}|array{string}', $matches); } + +function testPregMatchSimpleCondition(string $value): void { + if (preg_match('/%env\((.*)\:.*\)%/U', $value, $matches)) { + assertType('array{string, string}', $matches); + } +} + + +function testPregMatchIdenticalToOne(string $value): void { + if (preg_match('/%env\((.*)\:.*\)%/U', $value, $matches) === 1) { + assertType('array{string, string}', $matches); + } +} + +function testPregMatchIdenticalToOneFalseyContext(string $value): void { + if (!(preg_match('/%env\((.*)\:.*\)%/U', $value, $matches) !== 1)) { + assertType('array{string, string}', $matches); + } +} + +function testPregMatchIdenticalToOneInverted(string $value): void { + if (1 === preg_match('/%env\((.*)\:.*\)%/U', $value, $matches)) { + assertType('array{string, string}', $matches); + } +} + +function testPregMatchIdenticalToOneFalseyContextInverted(string $value): void { + if (!(1 !== preg_match('/%env\((.*)\:.*\)%/U', $value, $matches))) { + assertType('array{string, string}', $matches); + } +} From cf3fc1362dbf137d77aa829fd814ac4cd526fba1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 21 Jun 2024 11:38:11 +0200 Subject: [PATCH 55/55] PHPStan fix --- src/Testing/TypeInferenceTestCase.php | 5 ----- src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 41da709214..067612d25b 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -18,14 +18,12 @@ use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; -use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; use Symfony\Component\Finder\Finder; -use function array_key_exists; use function array_map; use function array_merge; use function count; @@ -304,9 +302,6 @@ private static function isFileLintSkipped(string $file): bool @fclose($f); if (preg_match('~getConstantStrings() as $constantString) { // The printf format is %[argnum$][flags][width][.precision] if (preg_match('/^%([0-9]*\$)?[0-9]*\.?[0-9]*([sbdeEfFgGhHouxX])$/', $constantString->getValue(), $matches) === 1) { - if (array_key_exists(1, $matches) && ($matches[1] !== '')) { + if ($matches[1] !== '') { // invalid positional argument if ($matches[1] === '0$') { return null;