Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1537,6 +1537,11 @@ services:
tags:
- phpstan.dynamicFunctionThrowTypeExtension

-
class: PHPStan\Type\Php\JsonValidateTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension

-
class: PHPStan\Type\Php\ReflectionClassConstructorThrowTypeExtension
tags:
Expand Down
29 changes: 29 additions & 0 deletions resources/functionMap_php83delta.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php // phpcs:ignoreFile

/**
* Copied over from https://github.com/phan/phan/blob/8866d6b98be94b37996390da226e8c4befea29aa/src/Phan/Language/Internal/FunctionSignatureMap_php80_delta.php
* Copyright (c) 2015 Rasmus Lerdorf
* Copyright (c) 2015 Andrew Morrison
*/

/**
* This contains the information needed to convert the function signatures for php 8.0 to php 7.4 (and vice versa)
*
* This has two sections.
* The 'new' section contains function/method names from FunctionSignatureMap (And alternates, if applicable) that do not exist in php7.4 or have different signatures in php 8.0.
* If they were just updated, the function/method will be present in the 'added' signatures.
* The 'old' signatures contains the signatures that are different in php 7.4.
* Functions are expected to be removed only in major releases of php.
*
* @see FunctionSignatureMap.php
*
* @phan-file-suppress PhanPluginMixedKeyNoKey (read by Phan when analyzing this file)
*/
return [
'new' => [
'json_validate' => ['bool', 'json'=>'string', 'depth='=>'positive-int', 'flags='=>'int-mask<JSON_INVALID_UTF8_IGNORE>'],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only a single flag is supported, see php/php-src#9399 (comment)

],
'old' => [

]
];
9 changes: 9 additions & 0 deletions src/Reflection/SignatureMap/FunctionSignatureMapProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
42 changes: 42 additions & 0 deletions src/Type/Php/JsonValidateTypeSpecifyingExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierAwareExtension;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
use PHPStan\Type\FunctionTypeSpecifyingExtension;
use function count;

class JsonValidateTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension
Copy link
Contributor Author

@staabm staabm Jun 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried using a stub signature instead, but this did not work for me locally:

/**
 * @phpstan-assert-if-true non-falsy-string $json
 * @return bool
 */
function json_validate(string $json, int $depth = 512, int $flags = 0): bool {}

as I can see this extension also only work in CI PHP 8.3.. I will retry using a stub signature on a different composer which has PHP 8.3 installed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, did a few more tests. It does not work with pure stub signatures, because these would also affect the ELSE case in a wrong way:

1) PHPStan\Analyser\NodeScopeResolverTest::testFileAsserts with data set "/Users/staabm/workspace/phpstan-src/tests/PHPStan/Analyser/data/json_validate.php:13" ('type', '/Users/staabm/workspace/phpst...te.php', PHPStan\Type\Constant\ConstantStringType Object (...), PHPStan\Type\MixedType Object (...), 13)
Expected type mixed, got type mixed~non-falsy-string in /Users/staabm/workspace/phpstan-src/tests/PHPStan/Analyser/data/json_validate.php on line 13.
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-'mixed'
+'mixed~non-falsy-string'

{

private TypeSpecifier $typeSpecifier;

public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool
{
return $functionReflection->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 AccessoryNonFalsyStringType(), $context, false, $scope);
Copy link
Contributor

@mad-briller mad-briller Jun 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is non falsy string type correct here?

https://3v4l.org/5IGU2/rfc#vgit.master

'0' is a valid json string (according to php anyway) but is also falsey

Copy link
Contributor Author

@staabm staabm Jun 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I did a 3v4l before with this exact case in mind. Seems I fucked it up.

Need to go with non-empty-string then :-)

}

public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
{
$this->typeSpecifier = $typeSpecifier;
}

}
3 changes: 3 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}

Expand Down
28 changes: 28 additions & 0 deletions tests/PHPStan/Analyser/data/json_validate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace JsonValidate;

use function PHPStan\Testing\assertType;

function doFoo($m): void {
assertType('bool', json_validate($m));

if (json_validate($m)) {
assertType('non-falsy-string', $m);
} else {
assertType('mixed', $m);
}
assertType('mixed', $m);
}

/**
* @param non-empty-string $nonES
*/
function doBar($nonES): void {
if (json_validate($nonES)) {
assertType('non-falsy-string', $nonES);
} else {
assertType('non-empty-string', $nonES);
}
assertType('non-empty-string', $nonES);
}
18 changes: 18 additions & 0 deletions tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1388,4 +1388,22 @@ public function testFlockParams(): void
]);
}

public function testJsonValidate(): void
{
if (PHP_VERSION_ID < 80300) {
$this->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,
],
]);
}

}
13 changes: 13 additions & 0 deletions tests/PHPStan/Rules/Functions/data/json_validate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace JsonValidateParams;

function doFoo() {
$x = json_validate('{ "test": { "foo": "bar" } }', 0, 0); // invalid depth
$x = json_validate('{ "test": { "foo": "bar" } }', 100, JSON_BIGINT_AS_STRING); // invalid flags

$x = json_validate('{ "test": { "foo": "bar" } }');
$x = json_validate('{ "test": { "foo": "bar" } }', 100, 0);
$x = json_validate('{ "test": { "foo": "bar" } }', 100, JSON_INVALID_UTF8_IGNORE);

}