Skip to content

Commit 2913caa

Browse files
authored
Merge branch refs/heads/2.1.x into 2.2.x
2 parents 256aee4 + 869ddb3 commit 2913caa

File tree

15 files changed

+348
-21
lines changed

15 files changed

+348
-21
lines changed

src/Reflection/ParametersAcceptorSelector.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,27 @@ public static function selectFromArgs(
516516
}
517517
if ($originalArg->unpack) {
518518
$unpack = true;
519-
$types[$index] = $type->getIterableValueType();
519+
$constantArrays = $type->getConstantArrays();
520+
if (count($constantArrays) > 0) {
521+
foreach ($constantArrays as $constantArray) {
522+
$values = $constantArray->getValueTypes();
523+
foreach ($constantArray->getKeyTypes() as $j => $keyType) {
524+
$valueType = $values[$j];
525+
$valueIndex = $keyType->getValue();
526+
if (is_string($valueIndex)) {
527+
$hasName = true;
528+
} else {
529+
$valueIndex = $i + $j;
530+
}
531+
532+
$types[$valueIndex] = isset($types[$valueIndex])
533+
? TypeCombinator::union($types[$valueIndex], $valueType)
534+
: $valueType;
535+
}
536+
}
537+
} else {
538+
$types[$index] = $type->getIterableValueType();
539+
}
520540
} else {
521541
$types[$index] = $type;
522542
}

src/Type/ArrayType.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -521,13 +521,35 @@ public function spliceArray(Type $offsetType, Type $lengthType, Type $replacemen
521521
return new ConstantArrayType([], []);
522522
}
523523

524+
$existingArrayKeyType = $this->getIterableKeyType();
525+
$keyType = TypeTraverser::map($existingArrayKeyType, static function (Type $type, callable $traverse): Type {
526+
if ($type instanceof UnionType) {
527+
return $traverse($type);
528+
}
529+
530+
if ($type->isInteger()->yes()) {
531+
return IntegerRangeType::createAllGreaterThanOrEqualTo(0);
532+
}
533+
534+
return $type;
535+
});
536+
524537
$arrayType = new self(
525-
TypeCombinator::union($this->getIterableKeyType(), $replacementArrayType->getKeysArray()->getIterableKeyType()),
538+
TypeCombinator::union($keyType, $replacementArrayType->getKeysArray()->getIterableKeyType()),
526539
TypeCombinator::union($this->getIterableValueType(), $replacementArrayType->getIterableValueType()),
527540
);
528541

542+
$accessories = [];
529543
if ($replacementArrayTypeIsIterableAtLeastOnce->yes()) {
530-
$arrayType = new IntersectionType([$arrayType, new NonEmptyArrayType()]);
544+
$accessories[] = new NonEmptyArrayType();
545+
}
546+
if ($existingArrayKeyType->isInteger()->yes()) {
547+
$accessories[] = new AccessoryArrayListType();
548+
}
549+
if (count($accessories) > 0) {
550+
$accessories[] = $arrayType;
551+
552+
return new IntersectionType($accessories);
531553
}
532554

533555
return $arrayType;

src/Type/TypeCombinator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1499,7 +1499,7 @@ public static function intersect(Type ...$types): Type
14991499
return $types[0];
15001500
}
15011501

1502-
return new IntersectionType(array_values($types));
1502+
return new IntersectionType($types);
15031503
}
15041504

15051505
public static function removeFalsey(Type $type): Type

tests/PHPStan/Analyser/nsrt/array_splice.php

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,52 +21,52 @@ function insertViaArraySplice(array $arr): void
2121
{
2222
$brr = $arr;
2323
$extract = array_splice($brr, 0, 0, 1);
24-
assertType('non-empty-array<int, int>', $brr);
24+
assertType('non-empty-list<int>', $brr);
2525
assertType('array{}', $extract);
2626

2727
$brr = $arr;
2828
$extract = array_splice($brr, 0, 0, [1]);
29-
assertType('non-empty-array<int, int>', $brr);
29+
assertType('non-empty-list<int>', $brr);
3030
assertType('array{}', $extract);
3131

3232
$brr = $arr;
3333
$extract = array_splice($brr, 0, 0, '');
34-
assertType('non-empty-array<int, \'\'|int>', $brr);
34+
assertType('non-empty-list<\'\'|int>', $brr);
3535
assertType('array{}', $extract);
3636

3737
$brr = $arr;
3838
$extract = array_splice($brr, 0, 0, ['']);
39-
assertType('non-empty-array<int, \'\'|int>', $brr);
39+
assertType('non-empty-list<\'\'|int>', $brr);
4040
assertType('array{}', $extract);
4141

4242
$brr = $arr;
4343
$extract = array_splice($brr, 0, 0, null);
44-
assertType('array<int, int>', $brr);
44+
assertType('list<int>', $brr);
4545
assertType('array{}', $extract);
4646

4747
$brr = $arr;
4848
$extract = array_splice($brr, 0, 0, [null]);
49-
assertType('non-empty-array<int, int|null>', $brr);
49+
assertType('non-empty-list<int|null>', $brr);
5050
assertType('array{}', $extract);
5151

5252
$brr = $arr;
5353
$extract = array_splice($brr, 0, 0, new Foo());
54-
assertType('non-empty-array<int, bool|int|string>', $brr);
54+
assertType('non-empty-list<bool|int|string>', $brr);
5555
assertType('array{}', $extract);
5656

5757
$brr = $arr;
5858
$extract = array_splice($brr, 0, 0, [new \stdClass()]);
59-
assertType('non-empty-array<int, int|stdClass>', $brr);
59+
assertType('non-empty-list<int|stdClass>', $brr);
6060
assertType('array{}', $extract);
6161

6262
$brr = $arr;
6363
$extract = array_splice($brr, 0, 0, false);
64-
assertType('non-empty-array<int, int|false>', $brr);
64+
assertType('non-empty-list<int|false>', $brr);
6565
assertType('array{}', $extract);
6666

6767
$brr = $arr;
6868
$extract = array_splice($brr, 0, 0, [false]);
69-
assertType('non-empty-array<int, int|false>', $brr);
69+
assertType('non-empty-list<int|false>', $brr);
7070
assertType('array{}', $extract);
7171

7272
$brr = $arr;
@@ -323,25 +323,25 @@ function offsets(array $arr): void
323323
{
324324
if (array_key_exists(1, $arr)) {
325325
$extract = array_splice($arr, 0, 1, 'hello');
326-
assertType('non-empty-array', $arr);
326+
assertType('non-empty-array<(int<0, max>|string), mixed>', $arr);
327327
assertType('array', $extract);
328328
}
329329

330330
if (array_key_exists(1, $arr)) {
331331
$extract = array_splice($arr, 0, 0, 'hello');
332-
assertType('non-empty-array&hasOffset(1)', $arr);
332+
assertType('non-empty-array<(int<0, max>|string), mixed>&hasOffset(1)', $arr);
333333
assertType('array{}', $extract);
334334
}
335335

336336
if (array_key_exists(1, $arr) && $arr[1] === 'foo') {
337337
$extract = array_splice($arr, 0, 1, 'hello');
338-
assertType('non-empty-array', $arr);
338+
assertType('non-empty-array<(int<0, max>|string), mixed>', $arr);
339339
assertType('array', $extract);
340340
}
341341

342342
if (array_key_exists(1, $arr) && $arr[1] === 'foo') {
343343
$extract = array_splice($arr, 0, 0, 'hello');
344-
assertType('non-empty-array&hasOffsetValue(1, \'foo\')', $arr);
344+
assertType('non-empty-array<(int<0, max>|string), mixed>&hasOffsetValue(1, \'foo\')', $arr);
345345
assertType('array{}', $extract);
346346
}
347347
}
@@ -384,3 +384,11 @@ function lists(array $arr): void
384384
assertType('list<string>', $arr);
385385
assertType('list<string>', $extract);
386386
}
387+
388+
/** @param array<string, string> $arr */
389+
function more(array $arr): void
390+
{
391+
$extract = array_splice($arr, 0, 1, [17 => 'foo', 18 => 'bar']);
392+
assertType('non-empty-array<0|1|string, string>', $arr);
393+
assertType('array<string, string>', $extract);
394+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Bug14037;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @param array<10|20|30|'a', mixed> $a
9+
*/
10+
function splice(array $a): void {
11+
array_splice($a, 0, 0);
12+
assertType("array<'a'|int<0, max>, mixed>", $a);
13+
}
14+
15+
/**
16+
* @param array<int<10, 30>|'a', mixed> $a
17+
*/
18+
function splice2(array $a): void {
19+
array_splice($a, 0, 0);
20+
assertType("array<'a'|int<0, max>, mixed>", $a);
21+
}

tests/PHPStan/Analyser/nsrt/bug-4743.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function splice(int $offset, int $length): void
2828
{
2929
$newNodes = array_splice($this->nodes, $offset, $length);
3030

31-
assertType('array<T of Bug4743\\Node (class Bug4743\\NodeList, argument)>', $this->nodes);
31+
assertType('array<(int<0, max>|string), T of Bug4743\\Node (class Bug4743\\NodeList, argument)>', $this->nodes);
3232
assertType('array<T of Bug4743\\Node (class Bug4743\\NodeList, argument)>', $newNodes);
3333
}
3434
}

tests/PHPStan/Analyser/nsrt/bug-5017.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function doBar($items)
2727
while ($items) {
2828
assertType('non-empty-array<int>', $items);
2929
$batch = array_splice($items, 0, 2);
30-
assertType('array<int>', $items);
30+
assertType('array<(int<0, max>|string), int>', $items);
3131
assertType('array<int>', $batch);
3232
}
3333
}
@@ -49,7 +49,7 @@ public function doBar3(array $ints, array $strings)
4949
{
5050
$removed = array_splice($ints, 0, 2, $strings);
5151
assertType('array<int>', $removed);
52-
assertType('array<int|string>', $ints);
52+
assertType('array<(int<0, max>|string), int|string>', $ints);
5353
assertType('array<string>', $strings);
5454
}
5555

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug8257;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
interface TreeMapper
8+
{
9+
/**
10+
* @template T of object
11+
*
12+
* @param string|class-string<T> $signature
13+
* @param mixed $source
14+
* @return (
15+
* $signature is class-string<T>
16+
* ? T
17+
* : mixed
18+
* )
19+
*/
20+
public function map(string $signature, $source);
21+
}
22+
23+
/** @var TreeMapper $tm */
24+
$tm;
25+
26+
class A {}
27+
28+
assertType('Bug8257\A', $tm->map(...[A::class, []]));

tests/PHPStan/Rules/Classes/InstantiationRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,12 @@ public function testBug13440(): void
579579
$this->analyse([__DIR__ . '/data/bug-13440.php'], []);
580580
}
581581

582+
#[RequiresPhp('>= 8.0')]
583+
public function testBug12363(): void
584+
{
585+
$this->analyse([__DIR__ . '/../Methods/data/bug-12363.php'], []);
586+
}
587+
582588
public function testNewStaticWithConsistentConstructor(): void
583589
{
584590
$this->analyse([__DIR__ . '/data/instantiation-new-static-consistent-constructor.php'], [

tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2735,4 +2735,10 @@ public function testBug10559(): void
27352735
$this->analyse([__DIR__ . '/data/bug-10559.php'], []);
27362736
}
27372737

2738+
#[RequiresPhp('>= 8.0')]
2739+
public function testBug12363(): void
2740+
{
2741+
$this->analyse([__DIR__ . '/data/bug-12363.php'], []);
2742+
}
2743+
27382744
}

0 commit comments

Comments
 (0)