Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More precise array_keys return type #3590

Open
wants to merge 8 commits into
base: 2.1.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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 src/Type/Accessory/AccessoryArrayListType.php
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@ public function unsetOffset(Type $offsetType): Type
return new ErrorType();
}

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
{
return $this->getKeysArray();
}

public function getKeysArray(): Type
{
return $this;
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Accessory/HasOffsetType.php
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,11 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
return new BooleanType();
}

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
{
return $this->getKeysArray();
}

public function getKeysArray(): Type
{
return new NonEmptyArrayType();
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Accessory/HasOffsetValueType.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ public function unsetOffset(Type $offsetType): Type
return $this;
}

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
{
return $this->getKeysArray();
}

public function getKeysArray(): Type
{
return new NonEmptyArrayType();
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Accessory/NonEmptyArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ public function unsetOffset(Type $offsetType): Type
return new ErrorType();
}

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
{
return $this->getKeysArray();
}

public function getKeysArray(): Type
{
return $this;
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Accessory/OversizedArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ public function unsetOffset(Type $offsetType): Type
return new ErrorType();
}

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
{
return $this->getKeysArray();
}

public function getKeysArray(): Type
{
return $this;
Expand Down
5 changes: 5 additions & 0 deletions src/Type/ArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ public function generalizeValues(): self
return new self($this->keyType, $this->itemType->generalize(GeneralizePrecision::lessSpecific()));
}

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
{
return $this->getKeysArray();
}

public function getKeysArray(): Type
{
return TypeCombinator::intersect(new self(new IntegerType(), $this->getIterableKeyType()), new AccessoryArrayListType());
Expand Down
14 changes: 14 additions & 0 deletions src/Type/Constant/ConstantArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\Generic\TemplateTypeVariance;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\IsSuperTypeOfResult;
use PHPStan\Type\MixedType;
Expand Down Expand Up @@ -1250,6 +1251,19 @@ public function generalizeValues(): self
return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList);
}

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
{
$keysArray = $this->getKeysOrValuesArray($this->keyTypes);

return TypeCombinator::intersect(
new ArrayType(
new IntegerType(),
$keysArray->getIterableValueType(),
),
new AccessoryArrayListType(),
);
}

public function getKeysArray(): self
{
return $this->getKeysOrValuesArray($this->keyTypes);
Expand Down
5 changes: 5 additions & 0 deletions src/Type/IntersectionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,11 @@ public function unsetOffset(Type $offsetType): Type
return $this->intersectTypes(static fn (Type $type): Type => $type->unsetOffset($offsetType));
}

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
{
return $this->intersectTypes(static fn (Type $type): Type => $type->getKeysArrayFiltered($filterValueType, $strict));
}

public function getKeysArray(): Type
{
return $this->intersectTypes(static fn (Type $type): Type => $type->getKeysArray());
Expand Down
5 changes: 5 additions & 0 deletions src/Type/MixedType.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ public function unsetOffset(Type $offsetType): Type
return $this;
}

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
{
return $this->getKeysArray();
}

public function getKeysArray(): Type
{
if ($this->isArray()->no()) {
Expand Down
5 changes: 5 additions & 0 deletions src/Type/NeverType.php
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ public function unsetOffset(Type $offsetType): Type
return new NeverType();
}

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
{
return $this->getKeysArray();
}

public function getKeysArray(): Type
{
return new NeverType();
Expand Down
17 changes: 15 additions & 2 deletions src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PHPStan\Analyser\Scope;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\NeverType;
use PHPStan\Type\NullType;
Expand All @@ -27,15 +28,27 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type
{
if (count($functionCall->getArgs()) !== 1) {
$args = $functionCall->getArgs();
if (count($args) < 1) {
return null;
}

$arrayType = $scope->getType($functionCall->getArgs()[0]->value);
$arrayType = $scope->getType($args[0]->value);
if ($arrayType->isArray()->no()) {
return $this->phpVersion->arrayFunctionsReturnNullWithNonArray() ? new NullType() : new NeverType();
}

if (count($args) >= 2) {
$filterType = $scope->getType($args[1]->value);

$strict = TrinaryLogic::createNo();
if (count($args) >= 3) {
$strict = $scope->getType($args[2]->value)->isTrue();
}

return $arrayType->getKeysArrayFiltered($filterType, $strict);
}

return $arrayType->getKeysArray();
}

Expand Down
5 changes: 5 additions & 0 deletions src/Type/StaticType.php
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,11 @@ public function unsetOffset(Type $offsetType): Type
return $this->getStaticObjectType()->unsetOffset($offsetType);
}

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
{
return $this->getStaticObjectType()->getKeysArrayFiltered($filterValueType, $strict);
}

public function getKeysArray(): Type
{
return $this->getStaticObjectType()->getKeysArray();
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Traits/LateResolvableTypeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ public function unsetOffset(Type $offsetType): Type
return $this->resolve()->unsetOffset($offsetType);
}

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
{
return $this->resolve()->getKeysArrayFiltered($filterValueType, $strict);
}

public function getKeysArray(): Type
{
return $this->resolve()->getKeysArray();
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Traits/MaybeArrayTypeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public function isList(): TrinaryLogic
return TrinaryLogic::createMaybe();
}

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
{
return $this->getKeysArray();
}

public function getKeysArray(): Type
{
return new ErrorType();
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Traits/NonArrayTypeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public function isList(): TrinaryLogic
return TrinaryLogic::createNo();
}

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
{
return $this->getKeysArray();
}

public function getKeysArray(): Type
{
return new ErrorType();
Expand Down
2 changes: 2 additions & 0 deletions src/Type/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ public function setExistingOffsetValueType(Type $offsetType, Type $valueType): T

public function unsetOffset(Type $offsetType): Type;

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type;

public function getKeysArray(): Type;

public function getValuesArray(): Type;
Expand Down
5 changes: 5 additions & 0 deletions src/Type/UnionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,11 @@ public function unsetOffset(Type $offsetType): Type
return $this->unionTypes(static fn (Type $type): Type => $type->unsetOffset($offsetType));
}

public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
{
return $this->unionTypes(static fn (Type $type): Type => $type->getKeysArrayFiltered($filterValueType, $strict));
}

public function getKeysArray(): Type
{
return $this->unionTypes(static fn (Type $type): Type => $type->getKeysArray());
Expand Down
68 changes: 68 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-11928.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace Bug11928;
Copy link
Contributor Author

Choose a reason for hiding this comment

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


use function PHPStan\Testing\assertType;

function doFoo()
{
$a = [2 => 1, 3 => 2, 4 => 1];

$keys = array_keys($a, 1); // returns [2, 4]
assertType('list<2|3|4>', $keys);

$keys = array_keys($a); // returns [2, 3, 4]
assertType('array{2, 3, 4}', $keys);
}

/**
* @param array<1|2|3, 4|5|6> $unionKeyedArray
* @param 4|5 $fourOrFive
* @return void
*/
function doFooStrings($unionKeyedArray, $fourOrFive) {
$a = [2 => 'hi', 3 => '123', 'xy' => 5];
$keys = array_keys($a, 1);
assertType("list<2|3|'xy'>", $keys);

$keys = array_keys($a);
assertType("array{2, 3, 'xy'}", $keys);

$keys = array_keys($unionKeyedArray, 1);
assertType("list<1|2|3>", $keys); // could be array{}

$keys = array_keys($unionKeyedArray, 4);
assertType("list<1|2|3>", $keys);

$keys = array_keys($unionKeyedArray, $fourOrFive);
assertType("list<1|2|3>", $keys);

$keys = array_keys($unionKeyedArray);
assertType("list<1|2|3>", $keys);
}

/**
* @param array<int, int> $array
* @param list<int> $list
* @param array<string, string> $strings
* @return void
*/
function doFooBar(array $array, array $list, array $strings) {
$keys = array_keys($strings, "a", true);
assertType('list<string>', $keys);

$keys = array_keys($strings, "a", false);
assertType('list<string>', $keys);

$keys = array_keys($array, 1, true);
assertType('list<int>', $keys);

$keys = array_keys($array, 1, false);
assertType('list<int>', $keys);

$keys = array_keys($list, 1, true);
assertType('list<int<0, max>>', $keys);

$keys = array_keys($list, 1, true);
assertType('list<int<0, max>>', $keys);
}
Loading