Skip to content

Commit

Permalink
Fix dynamic function return type extension for get_sites() (#155)
Browse files Browse the repository at this point in the history
* Fix return type extension for get_sites()

* Add test for get_sites()

* Fix return type for get_sites()

* Add brackets to if statement

Co-authored-by: Viktor Szépe <[email protected]>

---------

Co-authored-by: Viktor Szépe <[email protected]>
  • Loading branch information
IanDelMar and szepeviktor authored Feb 26, 2023
1 parent 0c34d2f commit d0c7100
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 28 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"require": {
"php": "^7.2 || ^8.0",
"php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0",
"phpstan/phpstan": "^1.9.4",
"phpstan/phpstan": "^1.10.0",
"symfony/polyfill-php73": "^1.12.0"
},
"require-dev": {
Expand Down
103 changes: 77 additions & 26 deletions src/GetSitesDynamicFunctionReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
use PHPStan\Type\ArrayType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\TypeCombinator;
use WP_Site;

class GetSitesDynamicFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension
{
Expand All @@ -36,41 +36,92 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,

// Called without arguments
if (count($args) === 0) {
return new ArrayType(new IntegerType(), new ObjectType('WP_Site'));
return new ArrayType(new IntegerType(), new ObjectType(WP_Site::class));
}

$fields = '';

$argumentType = $scope->getType($args[0]->value);

// Called with an array argument
if ($argumentType instanceof ConstantArrayType) {
foreach ($argumentType->getKeyTypes() as $index => $key) {
if (! $key instanceof ConstantStringType || $key->getValue() !== 'fields') {
continue;
}
// Called with a non constant argument
if (
count($argumentType->getConstantArrays()) === 0 &&
count($argumentType->getConstantStrings()) === 0
) {
return TypeCombinator::union(
new ArrayType(new IntegerType(), new ObjectType(WP_Site::class)),
new ArrayType(new IntegerType(), new IntegerType()),
new IntegerType()
);
}

$fields = [];
$count = [];
$returnType = [];

$fieldsType = $argumentType->getValueTypes()[$index];
if ($fieldsType instanceof ConstantStringType) {
$fields = $fieldsType->getValue();
// Called with a constant array argument
if (count($argumentType->getConstantArrays()) !== 0) {
foreach ($argumentType->getConstantArrays() as $constantArray) {
foreach ($constantArray->getKeyTypes() as $index => $key) {
if (count($key->getConstantStrings()) === 0) {
continue;
}
foreach ($key->getConstantStrings() as $constantKey) {
if (!in_array($constantKey->getValue(), ['fields', 'count'], true)) {
continue;
}
$fieldsType = $constantArray->getValueTypes()[$index];
if (count($fieldsType->getConstantScalarValues()) === 0) {
continue;
}

foreach ($fieldsType->getConstantScalarTypes() as $constantField) {
if ($constantKey->getValue() === 'fields') {
$fields[] = $constantField->getValue();
}
if ($constantKey->getValue() !== 'count') {
continue;
}

$count[] = (bool)$constantField->getValue();
}
}
}
break;
}
if ($fields === []) {
$fields = [''];
}
if ($count === []) {
$count = [false];
}
}

// Called with a string argument
if ($argumentType instanceof ConstantStringType) {
parse_str($argumentType->getValue(), $variables);
$fields = $variables['fields'] ?? 'all';
// Called with a constant string argument
if (count($argumentType->getConstantStrings()) !== 0) {
foreach ($argumentType->getConstantStrings() as $constantString) {
parse_str($constantString->getValue(), $variables);
$fields[] = $variables['fields'] ?? '';
$count[] = isset($variables['count']) ? (bool)$variables['count'] : false;
}
}

if (in_array(true, $count, true) && count($count) === 1) {
return new IntegerType();
}

if (in_array(true, $count, true)) {
$returnType[] = new IntegerType();
}

if (in_array('ids', $fields, true)) {
$returnType[] = new ArrayType(new IntegerType(), new IntegerType());
}

switch ($fields) {
case 'count':
return new IntegerType();
case 'ids':
return new ArrayType(new IntegerType(), new IntegerType());
default:
return new ArrayType(new IntegerType(), new ObjectType('WP_Site'));
if (
(in_array('ids', $fields, true) && count($fields) > 1) ||
(!in_array('ids', $fields, true) && count($fields) > 0)
) {
$returnType[] = new ArrayType(new IntegerType(), new ObjectType(WP_Site::class));
}

return TypeCombinator::union(...$returnType);
}
}
50 changes: 49 additions & 1 deletion tests/data/get_sites.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,54 @@

use function PHPStan\Testing\assertType;

// Default parameter
assertType('array<int, WP_Site>', get_sites());
assertType('int', get_sites(['fields' => 'count']));

// Non constant array parameter
/** @var array<int|string,mixed> $value */
$value = $_GET['foo'];
assertType('array<int, int|WP_Site>|int', get_sites($value));

// Non constant string parameter
/** @var string $value */
$value = $_GET['foo'];
assertType('array<int, int|WP_Site>|int', get_sites($value));

// Unknown parameter
/** @var mixed $value */
$value = $_GET['foo'];
assertType('array<int, int|WP_Site>|int', get_sites($value));

// Array parameter with explicit fields value.
assertType('array<int, int>', get_sites(['fields' => 'ids']));
assertType('array<int, WP_Site>', get_sites(['fields' => '']));
assertType('array<int, WP_Site>', get_sites(['fields' => 'nonEmptyString']));

// Array parameter with truthy count value.
assertType('int', get_sites(['count' => true]));
assertType('int', get_sites(['count' => 1]));
assertType('int', get_sites(['count' => 'nonEmptyString']));
assertType('int', get_sites(['fields' => '', 'count' => true]));
assertType('int', get_sites(['fields' => '', 'count' => 1]));
assertType('int', get_sites(['fields' => '', 'count' => 'nonEmptyString']));
assertType('int', get_sites(['fields' => 'ids', 'count' => true]));
assertType('int', get_sites(['fields' => 'ids', 'count' => 1]));
assertType('int', get_sites(['fields' => 'ids', 'count' => 'nonEmptyString']));
assertType('int', get_sites(['fields' => 'nonEmptyString', 'count' => true]));
assertType('int', get_sites(['fields' => 'nonEmptyString', 'count' => 1]));
assertType('int', get_sites(['fields' => 'nonEmptyString', 'count' => 'nonEmptyString']));

// Array parameter with falsy count value.
assertType('array<int, WP_Site>', get_sites(['count' => false]));
assertType('array<int, WP_Site>', get_sites(['count' => 0]));
assertType('array<int, WP_Site>', get_sites(['count' => '']));
assertType('array<int, WP_Site>', get_sites(['fields' => '', 'count' => false]));
assertType('array<int, WP_Site>', get_sites(['fields' => '', 'count' => 0]));
assertType('array<int, WP_Site>', get_sites(['fields' => '', 'count' => '']));
assertType('array<int, int>', get_sites(['fields' => 'ids', 'count' => false]));
assertType('array<int, int>', get_sites(['fields' => 'ids', 'count' => 0]));
assertType('array<int, int>', get_sites(['fields' => 'ids', 'count' => '']));
assertType('array<int, WP_Site>', get_sites(['fields' => 'nonEmptyString', 'count' => false]));
assertType('array<int, WP_Site>', get_sites(['fields' => 'nonEmptyString', 'count' => 0]));
assertType('array<int, WP_Site>', get_sites(['fields' => 'nonEmptyString', 'count' => '']));

0 comments on commit d0c7100

Please sign in to comment.