Skip to content
Merged
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
12 changes: 0 additions & 12 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1515,18 +1515,6 @@ parameters:
count: 1
path: src/Type/PHPStan/ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension.php

-
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantArrayType is error-prone and deprecated. Use Type::getConstantArrays() instead.'
identifier: phpstanApi.instanceofType
count: 2
path: src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php

-
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.'
identifier: phpstanApi.instanceofType
count: 1
path: src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php

-
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.'
identifier: phpstanApi.instanceofType
Expand Down
66 changes: 45 additions & 21 deletions src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\Accessory\NonEmptyArrayType;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\ConstantScalarType;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\ErrorType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\UnionType;
use function array_key_exists;
use function count;
use function is_int;
use function is_string;

#[AutowiredService]
final class ArrayCombineFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
Expand All @@ -49,29 +50,47 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
$keysParamType = $scope->getType($firstArg);
$valuesParamType = $scope->getType($secondArg);

$constantKeysArrays = $keysParamType->getConstantArrays();
$constantValuesArrays = $valuesParamType->getConstantArrays();
if (
$keysParamType instanceof ConstantArrayType
&& $valuesParamType instanceof ConstantArrayType
$constantKeysArrays !== []
&& $constantValuesArrays !== []
&& count($constantKeysArrays) === count($constantValuesArrays)
) {
$keyTypes = $keysParamType->getValueTypes();
$valueTypes = $valuesParamType->getValueTypes();
$results = [];
foreach ($constantKeysArrays as $k => $constantKeysArray) {
$constantValueArrays = $constantValuesArrays[$k];

$keyTypes = $constantKeysArray->getValueTypes();
$valueTypes = $constantValueArrays->getValueTypes();

if (count($keyTypes) !== count($valueTypes)) {
if ($this->phpVersion->throwsTypeErrorForInternalFunctions()) {
return new NeverType();
}
return new ConstantBooleanType(false);
}

if (count($keyTypes) !== count($valueTypes)) {
if ($this->phpVersion->throwsTypeErrorForInternalFunctions()) {
return new NeverType();
$keyTypes = $this->sanitizeConstantArrayKeyTypes($keyTypes);
if ($keyTypes === null) {
continue;
}
return new ConstantBooleanType(false);
}

$keyTypes = $this->sanitizeConstantArrayKeyTypes($keyTypes);
if ($keyTypes !== null) {
$builder = ConstantArrayTypeBuilder::createEmpty();
foreach ($keyTypes as $i => $keyType) {
if (!array_key_exists($i, $valueTypes)) {
$results = [];
break 2;
}
$valueType = $valueTypes[$i];
$builder->setOffsetValueType($keyType, $valueType);
}

return $builder->getArray();
$results[] = $builder->getArray();
}

if ($results !== []) {
return TypeCombinator::union(...$results);
}
}

Expand Down Expand Up @@ -114,7 +133,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
/**
* @param array<int, Type> $types
*
* @return array<int, ConstantIntegerType|ConstantStringType>|null
* @return list<ConstantScalarType>|null
*/
private function sanitizeConstantArrayKeyTypes(array $types): ?array
{
Expand All @@ -125,14 +144,19 @@ private function sanitizeConstantArrayKeyTypes(array $types): ?array
$type = $type->toString();
}

if (
!$type instanceof ConstantIntegerType
&& !$type instanceof ConstantStringType
) {
$scalars = $type->getConstantScalarTypes();
if (count($scalars) === 0) {
return null;
}

$sanitizedTypes[] = $type;
foreach ($scalars as $scalar) {
$value = $scalar->getValue();
if (!is_int($value) && !is_string($value)) {
return null;
}

$sanitizedTypes[] = $scalar;
}
}

return $sanitizedTypes;
Expand Down
56 changes: 56 additions & 0 deletions tests/PHPStan/Analyser/nsrt/array-combine-php8.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,62 @@ function withObjectKey() : void
assertType("*NEVER*", array_combine([new Baz, 'red', 'yellow'], $b));
}

function withUnionConstArrays(): void
{
if (rand(0, 1)) {
$a = [1];
$b = ['avocado'];
} else {
$a = ["2", "3"];
$b = ['apple', 'banana'];
}

assertType("array{1: 'avocado'}|array{2: 'apple', 3: 'banana'}", array_combine($a, $b));
}

function withUnionConstArraysDifferentKeyValueCount(): void
{
if (rand(0, 1)) {
$a = [1];
$b = [];
} else {
$a = ["2", "3"];
$b = ['apple', 'banana'];
}

assertType("*NEVER*", array_combine($a, $b));
}

function withUnionConstArraysDifferentArraysCount(): void
{
if (rand(0, 1)) {
$a = [1];
$b = ['avocado'];
} else {
$a = ["2", "3"];
if (rand(0, 1)) {
$b = ['apple', 'banana'];
} else {
$b = ['banana', 'pear'];
}
}

assertType("non-empty-array<1|'2'|'3', 'apple'|'avocado'|'banana'|'pear'>", array_combine($a, $b));
}

function withUnionConstArraysAndDifferentFiniteKeysCount(bool $bool): void
{
if (rand(0, 1)) {
$a = [$bool]; // translates to [true, false]
$b = ['avocado'];
} else {
$a = ["2", $bool]; // translates to ["2", true, false]
$b = ['apple', 'banana'];
}

assertType("non-empty-array<''|'1'|'2', 'apple'|'avocado'|'banana'>", array_combine($a, $b));
}

/**
* @param non-empty-array<int, 'foo'|'bar'|'baz'> $a
* @param non-empty-array<int, 'apple'|'avocado'|'banana'> $b
Expand Down
Loading