diff --git a/conf/config.neon b/conf/config.neon index ee0690bd7e..0131d6ce39 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1537,6 +1537,11 @@ services: tags: - phpstan.dynamicFunctionThrowTypeExtension + - + class: PHPStan\Type\Php\JsonValidateTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.functionTypeSpecifyingExtension + - class: PHPStan\Type\Php\ReflectionClassConstructorThrowTypeExtension tags: diff --git a/resources/functionMap_php83delta.php b/resources/functionMap_php83delta.php new file mode 100644 index 0000000000..dbf547dd45 --- /dev/null +++ b/resources/functionMap_php83delta.php @@ -0,0 +1,29 @@ + [ + 'json_validate' => ['bool', 'json'=>'string', 'depth='=>'positive-int', 'flags='=>'int-mask'], + ], + 'old' => [ + + ] +]; diff --git a/src/Reflection/SignatureMap/FunctionSignatureMapProvider.php b/src/Reflection/SignatureMap/FunctionSignatureMapProvider.php index d87aa91f9f..f626f65ac1 100644 --- a/src/Reflection/SignatureMap/FunctionSignatureMapProvider.php +++ b/src/Reflection/SignatureMap/FunctionSignatureMapProvider.php @@ -233,6 +233,15 @@ public function getSignatureMap(): array $signatureMap = $this->computeSignatureMap($signatureMap, $php82MapDelta); } + if ($this->phpVersion->getVersionId() >= 80300) { + $php83MapDelta = require __DIR__ . '/../../../resources/functionMap_php83delta.php'; + if (!is_array($php83MapDelta)) { + throw new ShouldNotHappenException('Signature map could not be loaded.'); + } + + $signatureMap = $this->computeSignatureMap($signatureMap, $php83MapDelta); + } + $this->signatureMap = $signatureMap; } diff --git a/src/Type/Php/JsonValidateTypeSpecifyingExtension.php b/src/Type/Php/JsonValidateTypeSpecifyingExtension.php new file mode 100644 index 0000000000..cd74d69149 --- /dev/null +++ b/src/Type/Php/JsonValidateTypeSpecifyingExtension.php @@ -0,0 +1,42 @@ +getName() === 'json_validate' && isset($node->getArgs()[0]) && $context->true(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + $args = $node->getArgs(); + + if (count($args) < 1) { + return new SpecifiedTypes([], []); + } + + return $this->typeSpecifier->create($node->getArgs()[0]->value, new AccessoryNonEmptyStringType(), $context, false, $scope); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 131545aebc..dba9c9fa05 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1283,6 +1283,9 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5782b-php7.php'); } + if (PHP_VERSION_ID >= 80300) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/json_validate.php'); + } yield from $this->gatherAssertTypes(__DIR__ . '/data/gettype.php'); } diff --git a/tests/PHPStan/Analyser/data/json_validate.php b/tests/PHPStan/Analyser/data/json_validate.php new file mode 100644 index 0000000000..f288fbddcf --- /dev/null +++ b/tests/PHPStan/Analyser/data/json_validate.php @@ -0,0 +1,28 @@ +markTestSkipped('Test requires PHP 8.3'); + } + + $this->analyse([__DIR__ . '/data/json_validate.php'], [ + [ + 'Parameter #2 $depth of function json_validate expects int<1, max>, 0 given.', + 6, + ], + [ + 'Parameter #3 $flags of function json_validate expects 0|1048576, 2 given.', + 7, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/json_validate.php b/tests/PHPStan/Rules/Functions/data/json_validate.php new file mode 100644 index 0000000000..08c1268bb7 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/json_validate.php @@ -0,0 +1,13 @@ +