Skip to content

Commit

Permalink
Fix array_unshift for union of constant arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
rvanvelzen authored and ondrejmirtes committed Sep 22, 2022
1 parent bc31fd3 commit 9077af6
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 25 deletions.
58 changes: 33 additions & 25 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -1882,38 +1882,46 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
}
};

if ($arrayType instanceof ConstantArrayType) {
$constantArrays = TypeUtils::getOldConstantArrays($arrayType);
if (count($constantArrays) > 0) {
$newArrayTypes = [];
$prepend = $functionReflection->getName() === 'array_unshift';
$arrayTypeBuilder = $prepend ? ConstantArrayTypeBuilder::createEmpty() : ConstantArrayTypeBuilder::createFromConstantArray($arrayType);
foreach ($constantArrays as $constantArray) {
$arrayTypeBuilder = $prepend ? ConstantArrayTypeBuilder::createEmpty() : ConstantArrayTypeBuilder::createFromConstantArray($constantArray);

$setOffsetValueTypes(
$scope,
$callArgs,
static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arrayTypeBuilder): void {
$arrayTypeBuilder->setOffsetValueType($offsetType, $valueType, $optional);
},
$nonConstantArrayWasUnpacked,
);
$setOffsetValueTypes(
$scope,
$callArgs,
static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arrayTypeBuilder): void {
$arrayTypeBuilder->setOffsetValueType($offsetType, $valueType, $optional);
},
$nonConstantArrayWasUnpacked,
);

if ($prepend) {
$keyTypes = $arrayType->getKeyTypes();
$valueTypes = $arrayType->getValueTypes();
foreach ($keyTypes as $k => $keyType) {
$arrayTypeBuilder->setOffsetValueType(
$keyType instanceof ConstantStringType ? $keyType : null,
$valueTypes[$k],
$arrayType->isOptionalKey($k),
);
if ($prepend) {
$keyTypes = $constantArray->getKeyTypes();
$valueTypes = $constantArray->getValueTypes();
foreach ($keyTypes as $k => $keyType) {
$arrayTypeBuilder->setOffsetValueType(
$keyType instanceof ConstantStringType ? $keyType : null,
$valueTypes[$k],
$constantArray->isOptionalKey($k),
);
}
}
}

$arrayType = $arrayTypeBuilder->getArray();
$constantArray = $arrayTypeBuilder->getArray();

if ($arrayType instanceof ConstantArrayType && $nonConstantArrayWasUnpacked) {
$arrayType = $arrayType->isIterableAtLeastOnce()->yes()
? TypeCombinator::intersect($arrayType->generalizeKeys(), new NonEmptyArrayType())
: $arrayType->generalizeKeys();
if ($constantArray instanceof ConstantArrayType && $nonConstantArrayWasUnpacked) {
$constantArray = $constantArray->isIterableAtLeastOnce()->yes()
? TypeCombinator::intersect($constantArray->generalizeKeys(), new NonEmptyArrayType())
: $constantArray->generalizeKeys();
}

$newArrayTypes[] = $constantArray;
}

$arrayType = TypeCombinator::union(...$newArrayTypes);
} else {
$setOffsetValueTypes(
$scope,
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7141.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/cli-globals.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8033.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/constant-array-union-unshift.php');
}

/**
Expand Down
19 changes: 19 additions & 0 deletions tests/PHPStan/Analyser/data/constant-array-union-unshift.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace ConstantArrayUnionUnshift;

use function PHPStan\Testing\assertType;

function () {
if (random_int(0, 1)) {
$array = ['a' => 1];
} else {
$array = ['b' => 1];
}

assertType('array{a: 1}|array{b: 1}', $array);

array_unshift($array, 2);

assertType('array{0: 2, a: 1}|array{0: 2, b: 1}', $array);
};

0 comments on commit 9077af6

Please sign in to comment.