Skip to content

Commit

Permalink
ArrayType - use getIterableKeyType, it preserves array-key
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Mar 16, 2024
1 parent fc4e589 commit d5bf23b
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 12 deletions.
6 changes: 3 additions & 3 deletions src/Type/ArrayType.php
Expand Up @@ -132,7 +132,7 @@ public function isSuperTypeOf(Type $type): TrinaryLogic
{
if ($type instanceof self) {
return $this->getItemType()->isSuperTypeOf($type->getItemType())
->and($this->keyType->isSuperTypeOf($type->keyType));
->and($this->getIterableKeyType()->isSuperTypeOf($type->getIterableKeyType()));
}

if ($type instanceof CompoundType) {
Expand Down Expand Up @@ -637,7 +637,7 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
}

if ($receivedType->isArray()->yes()) {
$keyTypeMap = $this->getKeyType()->inferTemplateTypes($receivedType->getIterableKeyType());
$keyTypeMap = $this->getIterableKeyType()->inferTemplateTypes($receivedType->getIterableKeyType());
$itemTypeMap = $this->getItemType()->inferTemplateTypes($receivedType->getIterableValueType());

return $keyTypeMap->union($itemTypeMap);
Expand All @@ -651,7 +651,7 @@ public function getReferencedTemplateTypes(TemplateTypeVariance $positionVarianc
$variance = $positionVariance->compose(TemplateTypeVariance::createCovariant());

return array_merge(
$this->getKeyType()->getReferencedTemplateTypes($variance),
$this->getIterableKeyType()->getReferencedTemplateTypes($variance),
$this->getItemType()->getReferencedTemplateTypes($variance),
);
}
Expand Down
6 changes: 3 additions & 3 deletions src/Type/Constant/ConstantArrayType.php
Expand Up @@ -875,7 +875,7 @@ public function shuffleArray(): Type
return $valuesArray;
}

$generalizedArray = new ArrayType($valuesArray->getKeyType(), $valuesArray->getItemType());
$generalizedArray = new ArrayType($valuesArray->getIterableKeyType(), $valuesArray->getItemType());

if ($isIterableAtLeastOnce->yes()) {
$generalizedArray = TypeCombinator::intersect($generalizedArray, new NonEmptyArrayType());
Expand Down Expand Up @@ -1229,7 +1229,7 @@ public function generalize(GeneralizePrecision $precision): Type
}

$arrayType = new ArrayType(
$this->getKeyType()->generalize($precision),
$this->getIterableKeyType()->generalize($precision),
$this->getItemType()->generalize($precision),
);

Expand Down Expand Up @@ -1281,7 +1281,7 @@ public function generalizeToArray(): Type
return $this;
}

$arrayType = new ArrayType($this->getKeyType(), $this->getItemType());
$arrayType = new ArrayType($this->getIterableKeyType(), $this->getItemType());

if ($isIterableAtLeastOnce->yes()) {
$arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType());
Expand Down
4 changes: 2 additions & 2 deletions src/Type/TypeCombinator.php
Expand Up @@ -671,7 +671,7 @@ private static function processArrayTypes(array $arrayTypes): array
foreach ($arrayTypes as $arrayType) {
if ($generalArrayOccurred || !$arrayType->isConstantArray()->yes()) {
foreach ($arrayType->getArrays() as $type) {
$keyTypesForGeneralArray[] = $type->getKeyType();
$keyTypesForGeneralArray[] = $type->getIterableKeyType();
$valueTypesForGeneralArray[] = $type->getItemType();
$generalArrayOccurred = true;
}
Expand Down Expand Up @@ -1200,7 +1200,7 @@ public static function intersect(Type ...$types): Type
($types[$i] instanceof ArrayType || $types[$i] instanceof IterableType) &&
($types[$j] instanceof ArrayType || $types[$j] instanceof IterableType)
) {
$keyType = self::intersect($types[$i]->getKeyType(), $types[$j]->getKeyType());
$keyType = self::intersect($types[$i]->getIterableKeyType(), $types[$j]->getKeyType());
$itemType = self::intersect($types[$i]->getItemType(), $types[$j]->getItemType());
if ($types[$i] instanceof IterableType && $types[$j] instanceof IterableType) {
$types[$j] = new IterableType($keyType, $itemType);
Expand Down
2 changes: 1 addition & 1 deletion src/Type/TypehintHelper.php
Expand Up @@ -184,7 +184,7 @@ public static function decideType(
foreach ($phpDocType->getTypes() as $innerType) {
if ($innerType instanceof ArrayType) {
$innerTypes[] = new IterableType(
$innerType->getKeyType(),
$innerType->getIterableKeyType(),
$innerType->getItemType(),
);
} else {
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-4498.php
Expand Up @@ -38,7 +38,7 @@ public function fcn(iterable $iterable): iterable
public function bar(iterable $iterable): iterable
{
if (is_array($iterable)) {
assertType('array<TKey (method Bug4498\Foo::bar(), argument), TValue (method Bug4498\Foo::bar(), argument)>', $iterable);
assertType('array<((int&TKey (method Bug4498\Foo::bar(), argument))|(string&TKey (method Bug4498\Foo::bar(), argument))), TValue (method Bug4498\Foo::bar(), argument)>', $iterable);
return $iterable;
}

Expand Down
4 changes: 2 additions & 2 deletions tests/PHPStan/Analyser/data/bug-7805.php
Expand Up @@ -21,10 +21,10 @@ function foo(array $params)
assertNativeType("array<mixed~'help', mixed>", $params);
$params = $params === [] ? ['list'] : $params;
assertType("array{'list'}", $params);
assertNativeType("non-empty-array<mixed~'help', mixed>", $params);
assertNativeType("non-empty-array', mixed>", $params);
array_unshift($params, 'help');
assertType("array{'help', 'list'}", $params);
assertNativeType("non-empty-array<mixed~'help', mixed>", $params);
assertNativeType("non-empty-array", $params);
}
assertType("array{}|array{'help', 'list'}", $params);
assertNativeType('array', $params);
Expand Down
Expand Up @@ -59,4 +59,9 @@ public function testBug10699(): void
$this->analyse([__DIR__ . '/../../Analyser/data/bug-10699.php'], []);
}

public function testBenevolentArrayKey(): void
{
$this->analyse([__DIR__ . '/data/benevolent-array-key.php'], []);
}

}
53 changes: 53 additions & 0 deletions tests/PHPStan/Rules/Variables/data/benevolent-array-key.php
@@ -0,0 +1,53 @@
<?php

namespace ParamOutBenevolentArrayKey;

class HelloWorld
{
/**
* @param array<mixed> $matches
* @param-out array<int|string, list<string>> $matches
*/
public static function matchAllStrictGroups(array &$matches): int
{
$result = self::matchAll($matches);

return $result;
}

/**
* @param array<mixed> $matches
* @param-out array<list<string>> $matches
*/
public static function matchAll(array &$matches): int
{
$matches = [['foo']];

return 1;
}
}

class HelloWorld2
{
/**
* @param array<mixed> $matches
* @param-out array<list<string>> $matches
*/
public static function matchAllStrictGroups(array &$matches): int
{
$result = self::matchAll($matches);

return $result;
}

/**
* @param array<mixed> $matches
* @param-out array<int|string, list<string>> $matches
*/
public static function matchAll(array &$matches): int
{
$matches = [['foo']];

return 1;
}
}

0 comments on commit d5bf23b

Please sign in to comment.