Skip to content

Commit 579a7f1

Browse files
authored
Keep the list flag when a list shape is intersected with an array type (#6717)
2 parents c066ed5 + 0c9e5be commit 579a7f1

4 files changed

Lines changed: 65 additions & 3 deletions

File tree

‎src/Turbo/TurboExtensionEnabler.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
final class TurboExtensionEnabler
3434
{
3535

36-
public const EXPECTED_EXTENSION_VERSION = '16bf720';
36+
public const EXPECTED_EXTENSION_VERSION = 'f46d060';
3737

3838
private static bool $active = false;
3939

‎src/Type/TypeCombinator.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2310,6 +2310,9 @@ public static function doIntersect(Type ...$types): Type
23102310
);
23112311
}
23122312
$newArrayType = $newArray->getArray();
2313+
if ($constArray->isList()->yes()) {
2314+
$newArrayType = self::intersect($newArrayType, new AccessoryArrayListType());
2315+
}
23132316
}
23142317

23152318
if ($constArrayIsI) {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug15391;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
/**
10+
* @param list{0: 'x', 1?: 'a'|'b', 2?: 'a'|'b'} $l
11+
*/
12+
function narrow(array $l): void
13+
{
14+
if (!in_array('b', $l, true)) {
15+
assertType("list{0: 'x', 1?: 'a', 2?: 'a'}", $l);
16+
} else {
17+
assertType("list{0: 'x', 1?: 'a'|'b', 2?: 'a'|'b'}", $l);
18+
}
19+
20+
if (in_array('a', $l, true)) {
21+
assertType("list{0: 'x', 1?: 'a'|'b', 2?: 'a'|'b'}", $l);
22+
}
23+
}
24+
25+
/**
26+
* @param list{0: 'x', 1?: 'a'|'b', 2?: 'a'|'b'}&array<mixed, 'a'|'x'> $a
27+
* @param array{0: 'x', 1?: 'a'|'b', 2?: 'a'|'b'}&array<mixed, 'a'|'x'> $b
28+
*/
29+
function intersect(array $a, array $b): void
30+
{
31+
assertType("list{0: 'x', 1?: 'a', 2?: 'a'}", $a);
32+
assertType("array{0: 'x', 1?: 'a', 2?: 'a'}", $b);
33+
}
34+
35+
function guardedAppendsInLoops(bool $c): void
36+
{
37+
$l = ['x'];
38+
foreach ([1, 2] as $_) {
39+
if ($c && !in_array('a', $l, true)) {
40+
$l[] = 'a';
41+
}
42+
}
43+
foreach ([1, 2] as $_) {
44+
if (!in_array('b', $l, true)) {
45+
$l[] = 'b';
46+
}
47+
}
48+
assertType("list{0: 'x', 1: 'a'|'b', 2?: 'a'|'b', 3?: 'b', 4?: 'b'}", $l);
49+
}

‎turbo-ext/src/TypeCombinator.cpp‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3177,7 +3177,8 @@ class TypeCombinator
31773177
/* the constant array & array arm of doIntersect(): $constArray merged
31783178
* with $otherArray (a definite constant array pair through
31793179
* intersectDefiniteConstantArrays(), otherwise rebuilt through the
3180-
* builder); the never reason falls back on $isSuperTypeA/$isSuperTypeB;
3180+
* builder, a list shape staying a list); the never reason falls back on
3181+
* $isSuperTypeA/$isSuperTypeB;
31813182
* UNDEF = pending exception */
31823183
static zv::Val intersectConstantArrayWith(zval *constArray, zval *otherArray, zval *isSuperTypeA, zval *isSuperTypeB)
31833184
{
@@ -3277,7 +3278,16 @@ class TypeCombinator
32773278
zv::Val set = call(newArray.raw(), PT_LC("setoffsetvaluetype"), 3, args);
32783279
PT_FAIL_IF_UNDEF(set);
32793280
}
3280-
return call(newArray.raw(), PT_LC("getarray"));
3281+
zv::Val newArrayType = call(newArray.raw(), PT_LC("getarray"));
3282+
PT_FAIL_IF_UNDEF(newArrayType);
3283+
zend_long isList = callTrinary(constArray, PT_LC("islist"));
3284+
PT_FAIL_IF_NEG(isList);
3285+
if (isList == PT_TRI_YES) {
3286+
zv::Val list = accessoryArrayListType();
3287+
PT_FAIL_IF_UNDEF(list);
3288+
return intersect2(newArrayType.raw(), list.raw());
3289+
}
3290+
return newArrayType;
32813291
}
32823292

32833293
/* Mirrors isPlainOrBenevolentUnion() */

0 commit comments

Comments
 (0)