Skip to content

Commit bb6cfec

Browse files
committed
Do not merge BenevolentUnionType with UnionType
1 parent a0e688c commit bb6cfec

14 files changed

+463
-85
lines changed

src/Type/TypeCombinator.php

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -145,25 +145,12 @@ public static function union(Type ...$types): Type
145145
return new NeverType();
146146
}
147147

148-
$benevolentTypes = [];
149-
$benevolentUnionObject = null;
150148
// transform A | (B | C) to A | B | C
151149
for ($i = 0; $i < $typesCount; $i++) {
152-
if ($types[$i] instanceof BenevolentUnionType) {
153-
if ($types[$i] instanceof TemplateBenevolentUnionType && $benevolentUnionObject === null) {
154-
$benevolentUnionObject = $types[$i];
155-
}
156-
$benevolentTypesCount = 0;
157-
$typesInner = $types[$i]->getTypes();
158-
foreach ($typesInner as $benevolentInnerType) {
159-
$benevolentTypesCount++;
160-
$benevolentTypes[$benevolentInnerType->describe(VerbosityLevel::value())] = $benevolentInnerType;
161-
}
162-
array_splice($types, $i, 1, $typesInner);
163-
$typesCount += $benevolentTypesCount - 1;
150+
if (!($types[$i] instanceof UnionType)) {
164151
continue;
165152
}
166-
if (!($types[$i] instanceof UnionType)) {
153+
if ($types[$i] instanceof BenevolentUnionType) {
167154
continue;
168155
}
169156
if ($types[$i] instanceof TemplateType) {
@@ -347,25 +334,6 @@ public static function union(Type ...$types): Type
347334
return $types[0];
348335
}
349336

350-
if ($benevolentTypes !== []) {
351-
$tempTypes = $types;
352-
foreach ($tempTypes as $i => $type) {
353-
if (!isset($benevolentTypes[$type->describe(VerbosityLevel::value())])) {
354-
break;
355-
}
356-
357-
unset($tempTypes[$i]);
358-
}
359-
360-
if ($tempTypes === []) {
361-
if ($benevolentUnionObject instanceof TemplateBenevolentUnionType) {
362-
return $benevolentUnionObject->withTypes($types);
363-
}
364-
365-
return new BenevolentUnionType($types);
366-
}
367-
}
368-
369337
return new UnionType($types, true);
370338
}
371339

src/Type/UnionType.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ public function __construct(private array $types, private bool $normalized = fal
7070
if (!($type instanceof UnionType)) {
7171
continue;
7272
}
73+
if ($type instanceof BenevolentUnionType) {
74+
continue;
75+
}
7376
if ($type instanceof TemplateType) {
7477
continue;
7578
}

tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4942,11 +4942,11 @@ public function dataArrayFunctions(): array
49424942
'array_search(new stdClass, $generalStringKeys, true)',
49434943
],
49444944
[
4945-
'int|string|false',
4945+
'(int|string)|false',
49464946
'array_search($mixed, $array, true)',
49474947
],
49484948
[
4949-
'int|string|false',
4949+
'(int|string)|false',
49504950
'array_search($mixed, $array, false)',
49514951
],
49524952
[
@@ -5006,15 +5006,15 @@ public function dataArrayFunctions(): array
50065006
'array_search(\'id\', doFoo() ? $thisDoesNotExistAndIsMixedInUnion : false, true)',
50075007
],
50085008
[
5009-
'int|string|false',
5009+
'(int|string)|false',
50105010
'array_search(1, $generalIntegers, true)',
50115011
],
50125012
[
5013-
'int|string|false',
5013+
'(int|string)|false',
50145014
'array_search(1, $generalIntegers, false)',
50155015
],
50165016
[
5017-
'int|string|false',
5017+
'(int|string)|false',
50185018
'array_search(1, $generalIntegers)',
50195019
],
50205020
[
@@ -8820,11 +8820,11 @@ public function dataPhp73Functions(): array
88208820
'json_decode($mixed, false, 512, $integer | JSON_THROW_ON_ERROR | JSON_NUMERIC_CHECK)',
88218821
],
88228822
[
8823-
'int|string|null',
8823+
'(int|string)|null',
88248824
'array_key_first($mixedArray)',
88258825
],
88268826
[
8827-
'int|string|null',
8827+
'(int|string)|null',
88288828
'array_key_last($mixedArray)',
88298829
],
88308830
[
@@ -8904,7 +8904,7 @@ public function dataPhp73Functions(): array
89048904
'$hrtime3',
89058905
],
89068906
[
8907-
'array{int, int}|float|int',
8907+
'(float|int)|array{int, int}',
89088908
'$hrtime4',
89098909
],
89108910
];

tests/PHPStan/Analyser/nsrt/array-search-php7.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ class Foo
1212
public function mixedAndSubtractedArray($mixed, string $string): void
1313
{
1414
if (is_array($mixed)) {
15-
assertType('int|string|false', array_search('foo', $mixed, true));
16-
assertType('int|string|false', array_search('foo', $mixed));
17-
assertType('int|string|false', array_search($string, $mixed, true));
15+
assertType('(int|string)|false', array_search('foo', $mixed, true));
16+
assertType('(int|string)|false', array_search('foo', $mixed));
17+
assertType('(int|string)|false', array_search($string, $mixed, true));
1818
} else {
1919
assertType('mixed~array', $mixed);
2020
assertType('null', array_search('foo', $mixed, true));

tests/PHPStan/Analyser/nsrt/array-search-php8.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ class Foo
1212
public function mixedAndSubtractedArray($mixed, string $string): void
1313
{
1414
if (is_array($mixed)) {
15-
assertType('int|string|false', array_search('foo', $mixed, true));
16-
assertType('int|string|false', array_search('foo', $mixed));
17-
assertType('int|string|false', array_search($string, $mixed, true));
15+
assertType('(int|string)|false', array_search('foo', $mixed, true));
16+
assertType('(int|string)|false', array_search('foo', $mixed));
17+
assertType('(int|string)|false', array_search($string, $mixed, true));
1818
} else {
1919
assertType('mixed~array', $mixed);
2020
assertType('*NEVER*', array_search('foo', $mixed, true));

tests/PHPStan/Analyser/nsrt/array-search.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ class Foo
1010
public function nonEmpty(array $arr, string $string): void
1111
{
1212
/** @var non-empty-array<string> $arr */
13-
assertType('int|string|false', array_search('foo', $arr, true));
14-
assertType('int|string|false', array_search('foo', $arr));
15-
assertType('int|string|false', array_search($string, $arr, true));
13+
assertType('(int|string)|false', array_search('foo', $arr, true));
14+
assertType('(int|string)|false', array_search('foo', $arr));
15+
assertType('(int|string)|false', array_search($string, $arr, true));
1616
}
1717

1818
public function normalArrays(array $arr, string $string): void
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug7279;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @template T
9+
*/
10+
class Timeline {}
11+
class Percentage {}
12+
13+
/**
14+
* @template K of array-key
15+
* @template T
16+
*
17+
* @param array<K, T> $array
18+
* @param (callable(T, K): bool) $fn
19+
*
20+
* @return ($array is non-empty-array ? T|null : null)
21+
*/
22+
function find(array $array, callable $fn): mixed
23+
{
24+
foreach ($array as $key => $value) {
25+
if ($fn($value, $key)) {
26+
return $value;
27+
}
28+
}
29+
30+
return null;
31+
}
32+
33+
/**
34+
* @template K of array-key
35+
* @template T
36+
*
37+
* @param array<K, T> $array
38+
* @param (callable(T, K): bool) $fn
39+
*
40+
* @return ($array is non-empty-array ? K|null : null)
41+
*/
42+
function findKey(array $array, callable $fn): string|int|null
43+
{
44+
foreach ($array as $key => $value) {
45+
if ($fn($value, $key)) {
46+
return $key;
47+
}
48+
}
49+
50+
return null;
51+
}
52+
53+
/**
54+
* @param callable(mixed): bool $callback
55+
* @param array<never, never> $emptyList
56+
* @param array{} $emptyMap
57+
* @param array<int, string> $unknownList
58+
* @param array{id?: int, name?: string} $unknownMap
59+
* @param non-empty-array<int, Timeline<Percentage>> $nonEmptyList
60+
* @param array{work: Timeline<Percentage>} $nonEmptyMap
61+
*/
62+
function doFoo(callable $callback, array $emptyList, array $emptyMap, array $unknownList, array $unknownMap, array $nonEmptyList, array $nonEmptyMap): void
63+
{
64+
// Everything works great for find()
65+
66+
assertType('null', find([], $callback));
67+
assertType('null', find($emptyList, $callback));
68+
assertType('null', find($emptyMap, $callback));
69+
70+
assertType('string|null', find($unknownList, $callback));
71+
assertType('int|string|null', find($unknownMap, $callback));
72+
73+
assertType('Bug7279\Timeline<Bug7279\Percentage>|null', find($nonEmptyList, $callback));
74+
assertType('Bug7279\Timeline<Bug7279\Percentage>|null', find($nonEmptyMap, $callback));
75+
76+
// But everything goes to hell for findKey() ?!?
77+
78+
assertType('null', findKey([], $callback));
79+
assertType('null', findKey($emptyList, $callback));
80+
assertType('null', findKey($emptyMap, $callback));
81+
82+
assertType('int|null', findKey($unknownList, $callback));
83+
assertType("'id'|'name'|null", findKey($unknownMap, $callback));
84+
85+
assertType('int|null', findKey($nonEmptyList, $callback));
86+
assertType("'work'|null", findKey($nonEmptyMap, $callback));
87+
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
<?php declare(strict_types=1);
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types=1);
24

35
namespace Bug7291;
46

0 commit comments

Comments
 (0)