Skip to content

Commit 6173110

Browse files
committed
Fixed generalizing arrays in scope, simplified offset unset in ConstantArrayType
1 parent 0b9f1c0 commit 6173110

File tree

5 files changed

+73
-19
lines changed

5 files changed

+73
-19
lines changed

src/Analyser/MutatingScope.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4638,6 +4638,7 @@ private static function generalizeType(Type $a, Type $b): Type
46384638
$constantArraysA->getOffsetValueType($keyType),
46394639
$constantArraysB->getOffsetValueType($keyType),
46404640
),
4641+
!$constantArraysA->hasOffsetValueType($keyType)->and($constantArraysB->hasOffsetValueType($keyType))->negate()->no(),
46414642
);
46424643
}
46434644

src/Type/Constant/ConstantArrayType.php

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -575,20 +575,12 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $uni
575575
public function unsetOffset(Type $offsetType): Type
576576
{
577577
$offsetType = ArrayType::castToArrayKeyType($offsetType);
578-
579-
$results = [];
580-
foreach (TypeUtils::getConstantScalars($offsetType) as $constantScalar) {
581-
if (!$constantScalar instanceof ConstantIntegerType && !$constantScalar instanceof ConstantStringType) {
582-
continue;
583-
}
584-
585-
$hasKey = false;
578+
if ($offsetType instanceof ConstantIntegerType || $offsetType instanceof ConstantStringType) {
586579
foreach ($this->keyTypes as $i => $keyType) {
587-
if ($keyType->getValue() !== $constantScalar->getValue()) {
580+
if ($keyType->getValue() !== $offsetType->getValue()) {
588581
continue;
589582
}
590583

591-
$hasKey = true;
592584
$keyTypes = $this->keyTypes;
593585
unset($keyTypes[$i]);
594586
$valueTypes = $this->valueTypes;
@@ -608,17 +600,36 @@ public function unsetOffset(Type $offsetType): Type
608600
$k++;
609601
}
610602

611-
$results[] = new self($newKeyTypes, $newValueTypes, $this->nextAutoIndexes, $newOptionalKeys);
612-
break;
613-
}
614-
if ($hasKey) {
615-
continue;
603+
return new self($newKeyTypes, $newValueTypes, $this->nextAutoIndexes, $newOptionalKeys);
616604
}
617605

618-
$results[] = $this;
606+
return $this;
619607
}
620-
if ($results !== []) {
621-
return TypeCombinator::union(...$results);
608+
609+
$constantScalars = TypeUtils::getConstantScalars($offsetType);
610+
if (count($constantScalars) > 0) {
611+
$optionalKeys = $this->optionalKeys;
612+
613+
foreach ($constantScalars as $constantScalar) {
614+
$constantScalar = ArrayType::castToArrayKeyType($constantScalar);
615+
if (!$constantScalar instanceof ConstantIntegerType && !$constantScalar instanceof ConstantStringType) {
616+
continue;
617+
}
618+
619+
foreach ($this->keyTypes as $i => $keyType) {
620+
if ($keyType->getValue() !== $constantScalar->getValue()) {
621+
continue;
622+
}
623+
624+
if (in_array($i, $optionalKeys, true)) {
625+
continue 2;
626+
}
627+
628+
$optionalKeys[] = $i;
629+
}
630+
}
631+
632+
return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $optionalKeys);
622633
}
623634

624635
return new ArrayType($this->getKeyType(), $this->getItemType());

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,7 @@ public function dataFileAsserts(): iterable
982982
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7809.php');
983983
yield from $this->gatherAssertTypes(__DIR__ . '/data/composer-non-empty-array-after-unset.php');
984984
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-6000.php');
985+
yield from $this->gatherAssertTypes(__DIR__ . '/data/prestashop-breakdowns-empty-array.php');
985986
}
986987

987988
/**
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace PrestashopBreakdownsEmptyArray;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
/**
11+
* @param mixed[] $arrayMixed
12+
*/
13+
public function getTaxBreakdown($mixed, $arrayMixed): void
14+
{
15+
$breakdowns = [
16+
'product_tax' => $mixed,
17+
'shipping_tax' => $arrayMixed,
18+
'ecotax_tax' => $arrayMixed,
19+
'wrapping_tax' => $arrayMixed,
20+
];
21+
22+
foreach ($breakdowns as $type => $bd) {
23+
if (empty($bd)) {
24+
assertType('array{product_tax?: mixed, shipping_tax?: array, ecotax_tax?: array, wrapping_tax?: array}', $breakdowns);
25+
unset($breakdowns[$type]);
26+
assertType('array{product_tax?: mixed, shipping_tax?: array, ecotax_tax?: array, wrapping_tax?: array}', $breakdowns);
27+
}
28+
}
29+
30+
assertType('array{product_tax?: mixed, shipping_tax?: array, ecotax_tax?: array, wrapping_tax?: array}', $breakdowns);
31+
}
32+
33+
public function doFoo(): void
34+
{
35+
$a = ['foo' => 1, 'bar' => 2];
36+
assertType('array{foo: 1, bar: 2}', $a);
37+
unset($a['foo']);
38+
assertType('array{bar: 2}', $a);
39+
}
40+
41+
}

tests/PHPStan/Analyser/data/unset-conditional-expressions.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public function doBaz(): void
4242
}
4343
}
4444

45-
assertType('array{}|array{a?: bool, b?: numeric-string, c?: int<-1, 1>, d?: int<0, 1>}', $breakdowns);
45+
assertType('array{a?: bool, b?: numeric-string, c?: int<-1, 1>, d?: int<0, 1>}', $breakdowns);
4646
}
4747

4848
}

0 commit comments

Comments
 (0)