Skip to content

Commit 6c32371

Browse files
committed
Be smarter about new array keys after assignment
1 parent 164241d commit 6c32371

File tree

5 files changed

+83
-1
lines changed

5 files changed

+83
-1
lines changed

src/Type/ArrayType.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use PHPStan\Type\Traits\UndecidedBooleanTypeTrait;
2929
use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
3030
use function array_merge;
31+
use function count;
3132
use function sprintf;
3233

3334
/** @api */
@@ -419,7 +420,31 @@ public function getOffsetValueType(Type $offsetType): Type
419420
public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
420421
{
421422
if ($offsetType === null) {
422-
$offsetType = new IntegerType();
423+
$isKeyTypeInteger = $this->keyType->isInteger();
424+
if ($isKeyTypeInteger->no()) {
425+
$offsetType = new IntegerType();
426+
} elseif ($isKeyTypeInteger->yes()) {
427+
$offsetType = $this->keyType;
428+
} else {
429+
$integerTypes = [];
430+
TypeTraverser::map($this->keyType, static function (Type $type, callable $traverse) use (&$integerTypes): Type {
431+
if ($type instanceof UnionType) {
432+
return $traverse($type);
433+
}
434+
435+
$isInteger = $type->isInteger();
436+
if ($isInteger->yes()) {
437+
$integerTypes[] = $type;
438+
}
439+
440+
return $type;
441+
});
442+
if (count($integerTypes) === 0) {
443+
$offsetType = $this->keyType;
444+
} else {
445+
$offsetType = TypeCombinator::union(...$integerTypes);
446+
}
447+
}
423448
} else {
424449
$offsetType = $offsetType->toArrayKey();
425450
}

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,7 @@ public function dataFileAsserts(): iterable
12191219
yield from $this->gatherAssertTypes(__DIR__ . '/data/trait-type-alias.php');
12201220
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8609.php');
12211221
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/PhpDoc/data/bug-8609-function.php');
1222+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9131.php');
12221223
}
12231224

12241225
/**
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Bug9131TypeInference;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
/**
11+
* @param string[] $a
12+
* @param array<string, string> $b
13+
* @param array<int<0, max>, string> $c
14+
* @param array<int<0, max>|string, string> $d
15+
* @return void
16+
*/
17+
public function doFoo(
18+
array $a,
19+
array $b,
20+
array $c,
21+
array $d
22+
): void
23+
{
24+
$a[] = 'foo';
25+
assertType('non-empty-array<string>', $a);
26+
27+
$b[] = 'foo';
28+
assertType('non-empty-array<int|string, string>', $b);
29+
30+
$c[] = 'foo';
31+
assertType('non-empty-array<int<0, max>, string>', $c);
32+
33+
$d[] = 'foo';
34+
assertType('non-empty-array<int<0, max>|string, string>', $d);
35+
}
36+
37+
}

tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,4 +526,10 @@ public function testBug7789(): void
526526
$this->analyse([__DIR__ . '/data/bug-7789.php'], []);
527527
}
528528

529+
public function testBug9131(): void
530+
{
531+
$this->checkExplicitMixed = true;
532+
$this->analyse([__DIR__ . '/data/bug-9131.php'], []);
533+
}
534+
529535
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Bug9131;
4+
5+
class A
6+
{
7+
/** @var array<int<0, max>, string> */
8+
public array $l = [];
9+
10+
public function add(string $s): void {
11+
$this->l[] = $s;
12+
}
13+
}

0 commit comments

Comments
 (0)