Skip to content

Commit 0b925a9

Browse files
authored
Retain list type when assigning to offset 1 of non-empty-list
1 parent 970117e commit 0b925a9

File tree

4 files changed

+76
-1
lines changed

4 files changed

+76
-1
lines changed

src/Type/IntersectionType.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,14 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $uni
756756
);
757757
});
758758
}
759-
return $this->intersectTypes(static fn (Type $type): Type => $type->setOffsetValueType($offsetType, $valueType, $unionValues));
759+
760+
$result = $this->intersectTypes(static fn (Type $type): Type => $type->setOffsetValueType($offsetType, $valueType, $unionValues));
761+
762+
if ($offsetType !== null && $this->isList()->yes() && $this->isIterableAtLeastOnce()->yes() && (new ConstantIntegerType(1))->isSuperTypeOf($offsetType)->yes()) {
763+
$result = AccessoryArrayListType::intersectWith($result);
764+
}
765+
766+
return $result;
760767
}
761768

762769
public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type

tests/PHPStan/Analyser/nsrt/list-type.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,29 @@ public function testUnset(array $list): void
106106
assertType('array<int<0, 1>|int<3, max>, int>', $list);
107107
}
108108

109+
/** @param list<int> $list */
110+
public function testSetOffsetExplicitlyWithoutGap(array $list): void
111+
{
112+
assertType('list<int>', $list);
113+
$list[0] = 17;
114+
assertType('non-empty-list<int>&hasOffsetValue(0, 17)', $list);
115+
$list[1] = 19;
116+
assertType('non-empty-list<int>&hasOffsetValue(0, 17)&hasOffsetValue(1, 19)', $list);
117+
$list[0] = 21;
118+
assertType('non-empty-list<int>&hasOffsetValue(0, 21)&hasOffsetValue(1, 19)', $list);
119+
120+
$list[2] = 23;
121+
assertType('non-empty-array<int<0, max>, int>&hasOffsetValue(0, 21)&hasOffsetValue(1, 19)&hasOffsetValue(2, 23)', $list);
122+
}
123+
124+
/** @param list<int> $list */
125+
public function testSetOffsetExplicitlyWithGap(array $list): void
126+
{
127+
assertType('list<int>', $list);
128+
$list[0] = 17;
129+
assertType('non-empty-list<int>&hasOffsetValue(0, 17)', $list);
130+
$list[2] = 21;
131+
assertType('non-empty-array<int<0, max>, int>&hasOffsetValue(0, 17)&hasOffsetValue(2, 21)', $list);
132+
}
133+
109134
}

tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,4 +692,16 @@ public function testBug11617(): void
692692
]);
693693
}
694694

695+
public function testBug12131(): void
696+
{
697+
$this->checkExplicitMixed = true;
698+
$this->analyse([__DIR__ . '/data/bug-12131.php'], [
699+
[
700+
'Property Bug12131\Test::$array (non-empty-list<int>) does not accept non-empty-array<int<0, max>, int>.',
701+
29,
702+
'non-empty-array<int<0, max>, int> might not be a list.',
703+
],
704+
]);
705+
}
706+
695707
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php declare(strict_types = 1); // lint >= 7.4
2+
3+
namespace Bug12131;
4+
5+
class Test
6+
{
7+
/**
8+
* @var non-empty-list<int>
9+
*/
10+
public array $array;
11+
12+
public function __construct()
13+
{
14+
$this->array = array_fill(0, 10, 1);
15+
}
16+
17+
public function setAtZero(): void
18+
{
19+
$this->array[0] = 1;
20+
}
21+
22+
public function setAtOne(): void
23+
{
24+
$this->array[1] = 1;
25+
}
26+
27+
public function setAtTwo(): void
28+
{
29+
$this->array[2] = 1;
30+
}
31+
}

0 commit comments

Comments
 (0)