Skip to content

Commit f680629

Browse files
committed
IntersectionType - always describe list as list
1 parent 8606348 commit f680629

14 files changed

+161
-43
lines changed

phpstan-baseline.neon

+12
Original file line numberDiff line numberDiff line change
@@ -1311,6 +1311,12 @@ parameters:
13111311
count: 3
13121312
path: src/Type/IntersectionType.php
13131313

1314+
-
1315+
message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#'
1316+
identifier: phpstanApi.instanceofType
1317+
count: 1
1318+
path: src/Type/IntersectionType.php
1319+
13141320
-
13151321
message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#'
13161322
identifier: phpstanApi.instanceofType
@@ -1323,6 +1329,12 @@ parameters:
13231329
count: 2
13241330
path: src/Type/IntersectionType.php
13251331

1332+
-
1333+
message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#'
1334+
identifier: phpstanApi.instanceofType
1335+
count: 1
1336+
path: src/Type/IntersectionType.php
1337+
13261338
-
13271339
message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#'
13281340
identifier: phpstanApi.instanceofType

src/Type/IntersectionType.php

+33
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use PHPStan\Type\Accessory\AccessoryNumericStringType;
2828
use PHPStan\Type\Accessory\AccessoryType;
2929
use PHPStan\Type\Accessory\NonEmptyArrayType;
30+
use PHPStan\Type\Constant\ConstantArrayType;
3031
use PHPStan\Type\Constant\ConstantIntegerType;
3132
use PHPStan\Type\Generic\TemplateType;
3233
use PHPStan\Type\Generic\TemplateTypeMap;
@@ -45,8 +46,10 @@
4546
use function md5;
4647
use function sprintf;
4748
use function str_starts_with;
49+
use function strcasecmp;
4850
use function strlen;
4951
use function substr;
52+
use function usort;
5053

5154
/** @api */
5255
class IntersectionType implements CompoundType
@@ -292,13 +295,43 @@ public function describe(VerbosityLevel $level): string
292295
return $level->handle(
293296
function () use ($level): string {
294297
$typeNames = [];
298+
$isList = $this->isList()->yes();
299+
$valueType = null;
295300
foreach ($this->getSortedTypes() as $type) {
301+
if ($isList) {
302+
if ($type instanceof ArrayType || $type instanceof ConstantArrayType) {
303+
$valueType = $type->getIterableValueType();
304+
continue;
305+
}
306+
if ($type instanceof NonEmptyArrayType) {
307+
continue;
308+
}
309+
}
296310
if ($type instanceof AccessoryType) {
297311
continue;
298312
}
299313
$typeNames[] = $type->generalize(GeneralizePrecision::lessSpecific())->describe($level);
300314
}
301315

316+
if ($isList) {
317+
$isMixedValueType = $valueType instanceof MixedType && $valueType->describe(VerbosityLevel::precise()) === 'mixed' && !$valueType->isExplicitMixed();
318+
$innerType = '';
319+
if ($valueType !== null && !$isMixedValueType) {
320+
$innerType = sprintf('<%s>', $valueType->describe($level));
321+
}
322+
323+
$typeNames[] = 'list' . $innerType;
324+
}
325+
326+
usort($typeNames, static function ($a, $b) {
327+
$cmp = strcasecmp($a, $b);
328+
if ($cmp !== 0) {
329+
return $cmp;
330+
}
331+
332+
return $a <=> $b;
333+
});
334+
302335
return implode('&', $typeNames);
303336
},
304337
fn (): string => $this->describeItself($level, true),

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ public function testUnresolvableParameter(): void
843843
{
844844
$errors = $this->runAnalyse(__DIR__ . '/data/unresolvable-parameter.php');
845845
$this->assertCount(3, $errors);
846-
$this->assertSame('Parameter #2 $array of function array_map expects array, array<int, string>|false given.', $errors[0]->getMessage());
846+
$this->assertSame('Parameter #2 $array of function array_map expects array, list<string>|false given.', $errors[0]->getMessage());
847847
$this->assertSame(18, $errors[0]->getLine());
848848
$this->assertSame('Method UnresolvableParameter\Collection::pipeInto() has parameter $class with no type specified.', $errors[1]->getMessage());
849849
$this->assertSame(30, $errors[1]->getLine());
@@ -892,7 +892,7 @@ public function testBug7554(): void
892892
$errors = $this->runAnalyse(__DIR__ . '/data/bug-7554.php');
893893
$this->assertCount(2, $errors);
894894

895-
$this->assertSame(sprintf('Parameter #1 $%s of function count expects array|Countable, array<int, array<int, int|string>>|false given.', PHP_VERSION_ID < 80000 ? 'var' : 'value'), $errors[0]->getMessage());
895+
$this->assertSame(sprintf('Parameter #1 $%s of function count expects array|Countable, list<array<int, int<0, max>|string>>|false given.', PHP_VERSION_ID < 80000 ? 'var' : 'value'), $errors[0]->getMessage());
896896
$this->assertSame(26, $errors[0]->getLine());
897897

898898
$this->assertSame('Cannot access offset int<1, max> on list<array{string, int<0, max>}>|false.', $errors[1]->getMessage());

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public function broken(int $key)
3434
if ($item) {
3535
assertType("T of mixed~(0|0.0|''|'0'|array{}|false|null) (class Bug4117Types\GenericList, argument)", $item);
3636
} else {
37-
assertType("(array{}&T of mixed~null (class Bug4117Types\GenericList, argument))|(0.0&T of mixed~null (class Bug4117Types\GenericList, argument))|(0&T of mixed~null (class Bug4117Types\GenericList, argument))|(''&T of mixed~null (class Bug4117Types\GenericList, argument))|('0'&T of mixed~null (class Bug4117Types\GenericList, argument))|(T of mixed~null (class Bug4117Types\GenericList, argument)&false)|null", $item);
37+
assertType("(T of mixed~null (class Bug4117Types\GenericList, argument)&false)|(0.0&T of mixed~null (class Bug4117Types\GenericList, argument))|(0&T of mixed~null (class Bug4117Types\GenericList, argument))|(array{}&T of mixed~null (class Bug4117Types\GenericList, argument))|(''&T of mixed~null (class Bug4117Types\GenericList, argument))|('0'&T of mixed~null (class Bug4117Types\GenericList, argument))|null", $item);
3838
}
3939

4040
assertType('T of mixed~null (class Bug4117Types\GenericList, argument)|null', $item);

tests/PHPStan/Analyser/nsrt/constant-array-optional-set.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ class Bar
6060
*/
6161
public function doFoo($nextAutoIndexes)
6262
{
63-
assertType('non-empty-list<int>|int', $nextAutoIndexes);
63+
assertType('int|non-empty-list<int>', $nextAutoIndexes);
6464
if (is_int($nextAutoIndexes)) {
6565
assertType('int', $nextAutoIndexes);
6666
} else {
6767
assertType('non-empty-list<int>', $nextAutoIndexes);
6868
}
69-
assertType('non-empty-list<int>|int', $nextAutoIndexes);
69+
assertType('int|non-empty-list<int>', $nextAutoIndexes);
7070
}
7171

7272
/**
@@ -75,7 +75,7 @@ public function doFoo($nextAutoIndexes)
7575
*/
7676
public function doBar($nextAutoIndexes)
7777
{
78-
assertType('non-empty-list<int>|int', $nextAutoIndexes);
78+
assertType('int|non-empty-list<int>', $nextAutoIndexes);
7979
if (is_int($nextAutoIndexes)) {
8080
$nextAutoIndexes = [$nextAutoIndexes];
8181
assertType('array{int}', $nextAutoIndexes);

tests/PHPStan/Analyser/nsrt/count-maybe.php

+15-15
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ function doFoo4($maybeCountable, int $mode): void
8686
if (count($maybeCountable, $mode) > 0) {
8787
assertType('non-empty-list<int>', $maybeCountable);
8888
} else {
89-
assertType('list<int>|float', $maybeCountable);
89+
assertType('float|list<int>', $maybeCountable);
9090
}
91-
assertType('list<int>|float', $maybeCountable);
91+
assertType('float|list<int>', $maybeCountable);
9292
}
9393

9494
/**
@@ -100,9 +100,9 @@ function doFoo5($maybeCountable, $maybeMode): void
100100
if (count($maybeCountable, $maybeMode) > 0) {
101101
assertType('non-empty-list<int>', $maybeCountable);
102102
} else {
103-
assertType('list<int>|float', $maybeCountable);
103+
assertType('float|list<int>', $maybeCountable);
104104
}
105-
assertType('list<int>|float', $maybeCountable);
105+
assertType('float|list<int>', $maybeCountable);
106106
}
107107

108108
/**
@@ -113,9 +113,9 @@ function doFoo6($maybeCountable, float $invalidMode): void
113113
if (count($maybeCountable, $invalidMode) > 0) {
114114
assertType('non-empty-list<int>', $maybeCountable);
115115
} else {
116-
assertType('list<int>|float', $maybeCountable);
116+
assertType('float|list<int>', $maybeCountable);
117117
}
118-
assertType('list<int>|float', $maybeCountable);
118+
assertType('float|list<int>', $maybeCountable);
119119
}
120120

121121
/**
@@ -124,11 +124,11 @@ function doFoo6($maybeCountable, float $invalidMode): void
124124
function doFoo7($maybeCountable, int $mode): void
125125
{
126126
if (count($maybeCountable, $mode) > 0) {
127-
assertType('non-empty-list<int>|Countable', $maybeCountable);
127+
assertType('Countable|non-empty-list<int>', $maybeCountable);
128128
} else {
129-
assertType('list<int>|Countable|float', $maybeCountable);
129+
assertType('Countable|float|list<int>', $maybeCountable);
130130
}
131-
assertType('list<int>|Countable|float', $maybeCountable);
131+
assertType('Countable|float|list<int>', $maybeCountable);
132132
}
133133

134134
/**
@@ -138,11 +138,11 @@ function doFoo7($maybeCountable, int $mode): void
138138
function doFoo8($maybeCountable, $maybeMode): void
139139
{
140140
if (count($maybeCountable, $maybeMode) > 0) {
141-
assertType('non-empty-list<int>|Countable', $maybeCountable);
141+
assertType('Countable|non-empty-list<int>', $maybeCountable);
142142
} else {
143-
assertType('list<int>|Countable|float', $maybeCountable);
143+
assertType('Countable|float|list<int>', $maybeCountable);
144144
}
145-
assertType('list<int>|Countable|float', $maybeCountable);
145+
assertType('Countable|float|list<int>', $maybeCountable);
146146
}
147147

148148
/**
@@ -151,11 +151,11 @@ function doFoo8($maybeCountable, $maybeMode): void
151151
function doFoo9($maybeCountable, float $invalidMode): void
152152
{
153153
if (count($maybeCountable, $invalidMode) > 0) {
154-
assertType('non-empty-list<int>|Countable', $maybeCountable);
154+
assertType('Countable|non-empty-list<int>', $maybeCountable);
155155
} else {
156-
assertType('list<int>|Countable|float', $maybeCountable);
156+
assertType('Countable|float|list<int>', $maybeCountable);
157157
}
158-
assertType('list<int>|Countable|float', $maybeCountable);
158+
assertType('Countable|float|list<int>', $maybeCountable);
159159
}
160160

161161
function doFooBar1(array $countable, int $mode): void

tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function testCheckWithMaybes(): void
3232
10,
3333
],
3434
[
35-
'Argument of an invalid type array<int, int>|false supplied for foreach, only iterables are supported.',
35+
'Argument of an invalid type list<int>|false supplied for foreach, only iterables are supported.',
3636
19,
3737
],
3838
[

tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public function testImplode(): void
6464
{
6565
$this->analyse([__DIR__ . '/data/implode.php'], [
6666
[
67-
'Parameter #2 $array of function implode expects array<string>, array<int, array<int, string>|string> given.',
67+
'Parameter #2 $array of function implode expects array<string>, array<int, list<string>|string> given.',
6868
9,
6969
],
7070
[

tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ public function testBug3946(): void
154154
{
155155
$this->analyse([__DIR__ . '/data/bug-3946.php'], [
156156
[
157-
'Parameter #1 $keys of function array_combine expects an array of values castable to string, array<int, array<int, string>|Bug3946\stdClass|float|int|string> given.',
157+
'Parameter #1 $keys of function array_combine expects an array of values castable to string, array<int, Bug3946\stdClass|float|int|list<string>|string> given.',
158158
8,
159159
],
160160
]);

tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ public function testListWithNullablesChecked(): void
173173
$this->checkNullables = true;
174174
$this->analyse([__DIR__ . '/data/return-list-nullables.php'], [
175175
[
176-
'Function ReturnListNullables\doFoo() should return array<string>|null but returns array<int, string|null>.',
176+
'Function ReturnListNullables\doFoo() should return array<string>|null but returns list<string|null>.',
177177
16,
178178
],
179179
]);

tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php

+15-15
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function testRule(): void
2626
{
2727
$this->analyse([__DIR__ . '/data/sort-param-castable-to-string-functions.php'], $this->hackParameterNames([
2828
[
29-
'Parameter #1 $array of function array_unique expects an array of values castable to string, array<int, array<int, string>> given.',
29+
'Parameter #1 $array of function array_unique expects an array of values castable to string, array<int, list<string>> given.',
3030
16,
3131
],
3232
[
@@ -38,27 +38,27 @@ public function testRule(): void
3838
20,
3939
],
4040
[
41-
'Parameter #1 $array of function rsort expects an array of values castable to string, array<int, array<int, string>> given.',
41+
'Parameter #1 $array of function rsort expects an array of values castable to string, list<array<int, string>> given.',
4242
21,
4343
],
4444
[
45-
'Parameter #1 $array of function asort expects an array of values castable to string, array<int, array<int, string>> given.',
45+
'Parameter #1 $array of function asort expects an array of values castable to string, list<array<int, string>> given.',
4646
22,
4747
],
4848
[
49-
'Parameter #1 $array of function arsort expects an array of values castable to string, array<int, array<int, string>> given.',
49+
'Parameter #1 $array of function arsort expects an array of values castable to string, array<int, list<string>> given.',
5050
23,
5151
],
5252
[
53-
'Parameter #1 $array of function sort expects an array of values castable to string, array<int, array<int, string>> given.',
53+
'Parameter #1 $array of function sort expects an array of values castable to string, array<int, list<string>> given.',
5454
25,
5555
],
5656
[
57-
'Parameter #1 $array of function rsort expects an array of values castable to string, array<int, array<int, string>> given.',
57+
'Parameter #1 $array of function rsort expects an array of values castable to string, list<array<int, string>> given.',
5858
26,
5959
],
6060
[
61-
'Parameter #1 $array of function asort expects an array of values castable to string, array<int, array<int, string>> given.',
61+
'Parameter #1 $array of function asort expects an array of values castable to string, list<array<int, string>> given.',
6262
27,
6363
],
6464
[
@@ -70,11 +70,11 @@ public function testRule(): void
7070
32,
7171
],
7272
[
73-
'Parameter #1 $array of function sort expects an array of values castable to string, array<int, array<int, string>> given.',
73+
'Parameter #1 $array of function sort expects an array of values castable to string, array<int, list<string>> given.',
7474
33,
7575
],
7676
[
77-
'Parameter #1 $array of function sort expects an array of values castable to string and float, array<int, array<int, string>> given.',
77+
'Parameter #1 $array of function sort expects an array of values castable to string and float, list<array<int, string>> given.',
7878
34,
7979
],
8080
]));
@@ -88,23 +88,23 @@ public function testNamedArguments(): void
8888

8989
$this->analyse([__DIR__ . '/data/sort-param-castable-to-string-functions-named-args.php'], [
9090
[
91-
'Parameter $array of function array_unique expects an array of values castable to string, array<int, array<int, string>> given.',
91+
'Parameter $array of function array_unique expects an array of values castable to string, array<int, list<string>> given.',
9292
7,
9393
],
9494
[
9595
'Parameter $array of function sort expects an array of values castable to string, array<int, array<int, string>> given.',
9696
9,
9797
],
9898
[
99-
'Parameter $array of function rsort expects an array of values castable to string, array<int, array<int, string>> given.',
99+
'Parameter $array of function rsort expects an array of values castable to string, list<array<int, string>> given.',
100100
10,
101101
],
102102
[
103-
'Parameter $array of function asort expects an array of values castable to string, array<int, array<int, string>> given.',
103+
'Parameter $array of function asort expects an array of values castable to string, list<array<int, string>> given.',
104104
11,
105105
],
106106
[
107-
'Parameter $array of function arsort expects an array of values castable to string, array<int, array<int, string>> given.',
107+
'Parameter $array of function arsort expects an array of values castable to string, array<int, list<string>> given.',
108108
12,
109109
],
110110
]);
@@ -126,11 +126,11 @@ public function testEnum(): void
126126
14,
127127
],
128128
[
129-
'Parameter #1 $array of function rsort expects an array of values castable to string, array<int, SortParamCastableToStringFunctionsEnum\\FooEnum> given.',
129+
'Parameter #1 $array of function rsort expects an array of values castable to string, list<SortParamCastableToStringFunctionsEnum\FooEnum::A> given.',
130130
15,
131131
],
132132
[
133-
'Parameter #1 $array of function asort expects an array of values castable to string, array<int, SortParamCastableToStringFunctionsEnum\\FooEnum> given.',
133+
'Parameter #1 $array of function asort expects an array of values castable to string, list<SortParamCastableToStringFunctionsEnum\FooEnum::A> given.',
134134
16,
135135
],
136136
[

tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -237,15 +237,15 @@ public function testReturnTypeRule(): void
237237
759,
238238
],
239239
[
240-
'Method ReturnTypes\ArrayFillKeysIssue::getIPs2() should return array<string, array<ReturnTypes\Foo>> but returns array<string, array<int, ReturnTypes\Bar>>.',
240+
'Method ReturnTypes\ArrayFillKeysIssue::getIPs2() should return array<string, array<ReturnTypes\Foo>> but returns array<string, list<ReturnTypes\Bar>>.',
241241
817,
242242
],
243243
[
244244
'Method ReturnTypes\AssertThisInstanceOf::doBar() should return $this(ReturnTypes\AssertThisInstanceOf) but returns ReturnTypes\AssertThisInstanceOf&ReturnTypes\FooInterface.',
245245
840,
246246
],
247247
[
248-
'Method ReturnTypes\NestedArrayCheck::doFoo() should return array<string, bool> but returns array<string, array<int, string>>.',
248+
'Method ReturnTypes\NestedArrayCheck::doFoo() should return array<string, bool> but returns array<string, list<string>>.',
249249
860,
250250
],
251251
[

tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public function testRule(): void
7070
70,
7171
],
7272
[
73-
'PHPDoc tag @phpstan-assert for $array has no value type specified in iterable type array<int, mixed>.',
73+
'PHPDoc tag @phpstan-assert for $array has no value type specified in iterable type list.',
7474
88,
7575
'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type',
7676
],

0 commit comments

Comments
 (0)