Skip to content

Commit

Permalink
IntersectionType - always describe list as list
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Oct 6, 2024
1 parent 8606348 commit f680629
Show file tree
Hide file tree
Showing 14 changed files with 161 additions and 43 deletions.
12 changes: 12 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,12 @@ parameters:
count: 3
path: src/Type/IntersectionType.php

-
message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#'
identifier: phpstanApi.instanceofType
count: 1
path: src/Type/IntersectionType.php

-
message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#'
identifier: phpstanApi.instanceofType
Expand All @@ -1323,6 +1329,12 @@ parameters:
count: 2
path: src/Type/IntersectionType.php

-
message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#'
identifier: phpstanApi.instanceofType
count: 1
path: src/Type/IntersectionType.php

-
message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#'
identifier: phpstanApi.instanceofType
Expand Down
33 changes: 33 additions & 0 deletions src/Type/IntersectionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use PHPStan\Type\Accessory\AccessoryNumericStringType;
use PHPStan\Type\Accessory\AccessoryType;
use PHPStan\Type\Accessory\NonEmptyArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\Generic\TemplateTypeMap;
Expand All @@ -45,8 +46,10 @@
use function md5;
use function sprintf;
use function str_starts_with;
use function strcasecmp;
use function strlen;
use function substr;
use function usort;

/** @api */
class IntersectionType implements CompoundType
Expand Down Expand Up @@ -292,13 +295,43 @@ public function describe(VerbosityLevel $level): string
return $level->handle(
function () use ($level): string {
$typeNames = [];
$isList = $this->isList()->yes();
$valueType = null;
foreach ($this->getSortedTypes() as $type) {
if ($isList) {
if ($type instanceof ArrayType || $type instanceof ConstantArrayType) {
$valueType = $type->getIterableValueType();
continue;
}
if ($type instanceof NonEmptyArrayType) {
continue;
}
}
if ($type instanceof AccessoryType) {
continue;
}
$typeNames[] = $type->generalize(GeneralizePrecision::lessSpecific())->describe($level);
}

if ($isList) {
$isMixedValueType = $valueType instanceof MixedType && $valueType->describe(VerbosityLevel::precise()) === 'mixed' && !$valueType->isExplicitMixed();
$innerType = '';
if ($valueType !== null && !$isMixedValueType) {
$innerType = sprintf('<%s>', $valueType->describe($level));
}

$typeNames[] = 'list' . $innerType;
}

usort($typeNames, static function ($a, $b) {
$cmp = strcasecmp($a, $b);
if ($cmp !== 0) {
return $cmp;
}

return $a <=> $b;
});

return implode('&', $typeNames);
},
fn (): string => $this->describeItself($level, true),
Expand Down
4 changes: 2 additions & 2 deletions tests/PHPStan/Analyser/AnalyserIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -843,7 +843,7 @@ public function testUnresolvableParameter(): void
{
$errors = $this->runAnalyse(__DIR__ . '/data/unresolvable-parameter.php');
$this->assertCount(3, $errors);
$this->assertSame('Parameter #2 $array of function array_map expects array, array<int, string>|false given.', $errors[0]->getMessage());
$this->assertSame('Parameter #2 $array of function array_map expects array, list<string>|false given.', $errors[0]->getMessage());
$this->assertSame(18, $errors[0]->getLine());
$this->assertSame('Method UnresolvableParameter\Collection::pipeInto() has parameter $class with no type specified.', $errors[1]->getMessage());
$this->assertSame(30, $errors[1]->getLine());
Expand Down Expand Up @@ -892,7 +892,7 @@ public function testBug7554(): void
$errors = $this->runAnalyse(__DIR__ . '/data/bug-7554.php');
$this->assertCount(2, $errors);

$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());
$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());
$this->assertSame(26, $errors[0]->getLine());

$this->assertSame('Cannot access offset int<1, max> on list<array{string, int<0, max>}>|false.', $errors[1]->getMessage());
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/bug-4117.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function broken(int $key)
if ($item) {
assertType("T of mixed~(0|0.0|''|'0'|array{}|false|null) (class Bug4117Types\GenericList, argument)", $item);
} else {
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);
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);
}

assertType('T of mixed~null (class Bug4117Types\GenericList, argument)|null', $item);
Expand Down
6 changes: 3 additions & 3 deletions tests/PHPStan/Analyser/nsrt/constant-array-optional-set.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ class Bar
*/
public function doFoo($nextAutoIndexes)
{
assertType('non-empty-list<int>|int', $nextAutoIndexes);
assertType('int|non-empty-list<int>', $nextAutoIndexes);
if (is_int($nextAutoIndexes)) {
assertType('int', $nextAutoIndexes);
} else {
assertType('non-empty-list<int>', $nextAutoIndexes);
}
assertType('non-empty-list<int>|int', $nextAutoIndexes);
assertType('int|non-empty-list<int>', $nextAutoIndexes);
}

/**
Expand All @@ -75,7 +75,7 @@ public function doFoo($nextAutoIndexes)
*/
public function doBar($nextAutoIndexes)
{
assertType('non-empty-list<int>|int', $nextAutoIndexes);
assertType('int|non-empty-list<int>', $nextAutoIndexes);
if (is_int($nextAutoIndexes)) {
$nextAutoIndexes = [$nextAutoIndexes];
assertType('array{int}', $nextAutoIndexes);
Expand Down
30 changes: 15 additions & 15 deletions tests/PHPStan/Analyser/nsrt/count-maybe.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ function doFoo4($maybeCountable, int $mode): void
if (count($maybeCountable, $mode) > 0) {
assertType('non-empty-list<int>', $maybeCountable);
} else {
assertType('list<int>|float', $maybeCountable);
assertType('float|list<int>', $maybeCountable);
}
assertType('list<int>|float', $maybeCountable);
assertType('float|list<int>', $maybeCountable);
}

/**
Expand All @@ -100,9 +100,9 @@ function doFoo5($maybeCountable, $maybeMode): void
if (count($maybeCountable, $maybeMode) > 0) {
assertType('non-empty-list<int>', $maybeCountable);
} else {
assertType('list<int>|float', $maybeCountable);
assertType('float|list<int>', $maybeCountable);
}
assertType('list<int>|float', $maybeCountable);
assertType('float|list<int>', $maybeCountable);
}

/**
Expand All @@ -113,9 +113,9 @@ function doFoo6($maybeCountable, float $invalidMode): void
if (count($maybeCountable, $invalidMode) > 0) {
assertType('non-empty-list<int>', $maybeCountable);
} else {
assertType('list<int>|float', $maybeCountable);
assertType('float|list<int>', $maybeCountable);
}
assertType('list<int>|float', $maybeCountable);
assertType('float|list<int>', $maybeCountable);
}

/**
Expand All @@ -124,11 +124,11 @@ function doFoo6($maybeCountable, float $invalidMode): void
function doFoo7($maybeCountable, int $mode): void
{
if (count($maybeCountable, $mode) > 0) {
assertType('non-empty-list<int>|Countable', $maybeCountable);
assertType('Countable|non-empty-list<int>', $maybeCountable);
} else {
assertType('list<int>|Countable|float', $maybeCountable);
assertType('Countable|float|list<int>', $maybeCountable);
}
assertType('list<int>|Countable|float', $maybeCountable);
assertType('Countable|float|list<int>', $maybeCountable);
}

/**
Expand All @@ -138,11 +138,11 @@ function doFoo7($maybeCountable, int $mode): void
function doFoo8($maybeCountable, $maybeMode): void
{
if (count($maybeCountable, $maybeMode) > 0) {
assertType('non-empty-list<int>|Countable', $maybeCountable);
assertType('Countable|non-empty-list<int>', $maybeCountable);
} else {
assertType('list<int>|Countable|float', $maybeCountable);
assertType('Countable|float|list<int>', $maybeCountable);
}
assertType('list<int>|Countable|float', $maybeCountable);
assertType('Countable|float|list<int>', $maybeCountable);
}

/**
Expand All @@ -151,11 +151,11 @@ function doFoo8($maybeCountable, $maybeMode): void
function doFoo9($maybeCountable, float $invalidMode): void
{
if (count($maybeCountable, $invalidMode) > 0) {
assertType('non-empty-list<int>|Countable', $maybeCountable);
assertType('Countable|non-empty-list<int>', $maybeCountable);
} else {
assertType('list<int>|Countable|float', $maybeCountable);
assertType('Countable|float|list<int>', $maybeCountable);
}
assertType('list<int>|Countable|float', $maybeCountable);
assertType('Countable|float|list<int>', $maybeCountable);
}

function doFooBar1(array $countable, int $mode): void
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function testCheckWithMaybes(): void
10,
],
[
'Argument of an invalid type array<int, int>|false supplied for foreach, only iterables are supported.',
'Argument of an invalid type list<int>|false supplied for foreach, only iterables are supported.',
19,
],
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public function testImplode(): void
{
$this->analyse([__DIR__ . '/data/implode.php'], [
[
'Parameter #2 $array of function implode expects array<string>, array<int, array<int, string>|string> given.',
'Parameter #2 $array of function implode expects array<string>, array<int, list<string>|string> given.',
9,
],
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public function testBug3946(): void
{
$this->analyse([__DIR__ . '/data/bug-3946.php'], [
[
'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.',
'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.',
8,
],
]);
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public function testListWithNullablesChecked(): void
$this->checkNullables = true;
$this->analyse([__DIR__ . '/data/return-list-nullables.php'], [
[
'Function ReturnListNullables\doFoo() should return array<string>|null but returns array<int, string|null>.',
'Function ReturnListNullables\doFoo() should return array<string>|null but returns list<string|null>.',
16,
],
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function testRule(): void
{
$this->analyse([__DIR__ . '/data/sort-param-castable-to-string-functions.php'], $this->hackParameterNames([
[
'Parameter #1 $array of function array_unique expects an array of values castable to string, array<int, array<int, string>> given.',
'Parameter #1 $array of function array_unique expects an array of values castable to string, array<int, list<string>> given.',
16,
],
[
Expand All @@ -38,27 +38,27 @@ public function testRule(): void
20,
],
[
'Parameter #1 $array of function rsort expects an array of values castable to string, array<int, array<int, string>> given.',
'Parameter #1 $array of function rsort expects an array of values castable to string, list<array<int, string>> given.',
21,
],
[
'Parameter #1 $array of function asort expects an array of values castable to string, array<int, array<int, string>> given.',
'Parameter #1 $array of function asort expects an array of values castable to string, list<array<int, string>> given.',
22,
],
[
'Parameter #1 $array of function arsort expects an array of values castable to string, array<int, array<int, string>> given.',
'Parameter #1 $array of function arsort expects an array of values castable to string, array<int, list<string>> given.',
23,
],
[
'Parameter #1 $array of function sort expects an array of values castable to string, array<int, array<int, string>> given.',
'Parameter #1 $array of function sort expects an array of values castable to string, array<int, list<string>> given.',
25,
],
[
'Parameter #1 $array of function rsort expects an array of values castable to string, array<int, array<int, string>> given.',
'Parameter #1 $array of function rsort expects an array of values castable to string, list<array<int, string>> given.',
26,
],
[
'Parameter #1 $array of function asort expects an array of values castable to string, array<int, array<int, string>> given.',
'Parameter #1 $array of function asort expects an array of values castable to string, list<array<int, string>> given.',
27,
],
[
Expand All @@ -70,11 +70,11 @@ public function testRule(): void
32,
],
[
'Parameter #1 $array of function sort expects an array of values castable to string, array<int, array<int, string>> given.',
'Parameter #1 $array of function sort expects an array of values castable to string, array<int, list<string>> given.',
33,
],
[
'Parameter #1 $array of function sort expects an array of values castable to string and float, array<int, array<int, string>> given.',
'Parameter #1 $array of function sort expects an array of values castable to string and float, list<array<int, string>> given.',
34,
],
]));
Expand All @@ -88,23 +88,23 @@ public function testNamedArguments(): void

$this->analyse([__DIR__ . '/data/sort-param-castable-to-string-functions-named-args.php'], [
[
'Parameter $array of function array_unique expects an array of values castable to string, array<int, array<int, string>> given.',
'Parameter $array of function array_unique expects an array of values castable to string, array<int, list<string>> given.',
7,
],
[
'Parameter $array of function sort expects an array of values castable to string, array<int, array<int, string>> given.',
9,
],
[
'Parameter $array of function rsort expects an array of values castable to string, array<int, array<int, string>> given.',
'Parameter $array of function rsort expects an array of values castable to string, list<array<int, string>> given.',
10,
],
[
'Parameter $array of function asort expects an array of values castable to string, array<int, array<int, string>> given.',
'Parameter $array of function asort expects an array of values castable to string, list<array<int, string>> given.',
11,
],
[
'Parameter $array of function arsort expects an array of values castable to string, array<int, array<int, string>> given.',
'Parameter $array of function arsort expects an array of values castable to string, array<int, list<string>> given.',
12,
],
]);
Expand All @@ -126,11 +126,11 @@ public function testEnum(): void
14,
],
[
'Parameter #1 $array of function rsort expects an array of values castable to string, array<int, SortParamCastableToStringFunctionsEnum\\FooEnum> given.',
'Parameter #1 $array of function rsort expects an array of values castable to string, list<SortParamCastableToStringFunctionsEnum\FooEnum::A> given.',
15,
],
[
'Parameter #1 $array of function asort expects an array of values castable to string, array<int, SortParamCastableToStringFunctionsEnum\\FooEnum> given.',
'Parameter #1 $array of function asort expects an array of values castable to string, list<SortParamCastableToStringFunctionsEnum\FooEnum::A> given.',
16,
],
[
Expand Down
4 changes: 2 additions & 2 deletions tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -237,15 +237,15 @@ public function testReturnTypeRule(): void
759,
],
[
'Method ReturnTypes\ArrayFillKeysIssue::getIPs2() should return array<string, array<ReturnTypes\Foo>> but returns array<string, array<int, ReturnTypes\Bar>>.',
'Method ReturnTypes\ArrayFillKeysIssue::getIPs2() should return array<string, array<ReturnTypes\Foo>> but returns array<string, list<ReturnTypes\Bar>>.',
817,
],
[
'Method ReturnTypes\AssertThisInstanceOf::doBar() should return $this(ReturnTypes\AssertThisInstanceOf) but returns ReturnTypes\AssertThisInstanceOf&ReturnTypes\FooInterface.',
840,
],
[
'Method ReturnTypes\NestedArrayCheck::doFoo() should return array<string, bool> but returns array<string, array<int, string>>.',
'Method ReturnTypes\NestedArrayCheck::doFoo() should return array<string, bool> but returns array<string, list<string>>.',
860,
],
[
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public function testRule(): void
70,
],
[
'PHPDoc tag @phpstan-assert for $array has no value type specified in iterable type array<int, mixed>.',
'PHPDoc tag @phpstan-assert for $array has no value type specified in iterable type list.',
88,
'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type',
],
Expand Down
Loading

0 comments on commit f680629

Please sign in to comment.