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

Lines changed: 12 additions & 0 deletions
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

Lines changed: 33 additions & 0 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 3 additions & 3 deletions
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

Lines changed: 15 additions & 15 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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
]);

0 commit comments

Comments
 (0)