1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

#4997 added more precise type inference for count() returning 0 or positive-int on known arrays (#4999)

* #4997 added more precise stub for `count()` returning `0` or `positive-int` on known types

* #4997 updated `count()` to support `\SimpleXmlElement` and `\ResourceBundle` counting, as well as handling hardcoded 2-element-arrays cases

This patch:

 * adds support for `count(\SimpleXmlElement)` (https://www.php.net/manual/en/simplexmlelement.count.php)
 * adds support for `count(\ResourceBundle)` (https://www.php.net/manual/en/resourcebundle.count.php)
 * removes usage of global constants from stub (not supported - see https://www.php.net/manual/en/function.count.php)
 * adds support for identifying fixed-element-count arrays, for example `count(callable&array)`, which is always `2`

* #4997 adapted `FunctionCallReturnTypeFetcher` to infer `TPositiveInt` for `count(TNonEmptyArray)` and `count(TNonEmptyList)`

* The `FunctionCallReturnTypeFetcher` is responsible for defining the precise type of a `\count(T)`
expression when given a `T`, so we baked the whole type resolution for `positive-int`, `0` and
`positive-int|0` directly in there.

While this complicates things, it is also true that it is not possible right now (for the stubs)
to provide the level of detail around `count()` that is required by the type inference system
for such a complex function with so many different semantics.
This commit is contained in:
Marco Pivetta 2021-01-13 15:48:38 +01:00 committed by Daniil Gentili
parent 2daa27548e
commit 0b5a828f6f
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
2 changed files with 100 additions and 2 deletions

View File

@ -300,7 +300,7 @@ class FunctionCallReturnTypeFetcher
return new Type\Union([ return new Type\Union([
$atomic_types['array']->count !== null $atomic_types['array']->count !== null
? new Type\Atomic\TLiteralInt($atomic_types['array']->count) ? new Type\Atomic\TLiteralInt($atomic_types['array']->count)
: new Type\Atomic\TInt : new Type\Atomic\TPositiveInt
]); ]);
} }
@ -308,7 +308,7 @@ class FunctionCallReturnTypeFetcher
return new Type\Union([ return new Type\Union([
$atomic_types['array']->count !== null $atomic_types['array']->count !== null
? new Type\Atomic\TLiteralInt($atomic_types['array']->count) ? new Type\Atomic\TLiteralInt($atomic_types['array']->count)
: new Type\Atomic\TInt : new Type\Atomic\TPositiveInt
]); ]);
} }
@ -319,6 +319,11 @@ class FunctionCallReturnTypeFetcher
new Type\Atomic\TLiteralInt(count($atomic_types['array']->properties)) new Type\Atomic\TLiteralInt(count($atomic_types['array']->properties))
]); ]);
} }
return new Type\Union([
new Type\Atomic\TLiteralInt(0),
new Type\Atomic\TPositiveInt
]);
} }
} }
} }

View File

@ -1054,6 +1054,83 @@ class FunctionCallTest extends TestCase
} }
}' }'
], ],
'countNonEmptyArrayShouldBePositiveInt' => [
'<?php
/**
* @psalm-pure
* @param non-empty-list $x
* @return positive-int
*/
function example($x) : int {
return count($x);
}',
],
'countListShouldBeZeroOrPositive' => [
'<?php
/**
* @psalm-pure
* @param list $x
* @return positive-int|0
*/
function example($x) : int {
return count($x);
}',
],
'countArrayShouldBeZeroOrPositive' => [
'<?php
/**
* @psalm-pure
* @param array $x
* @return positive-int|0
*/
function example($x) : int {
return count($x);
}',
],
'countEmptyArrayShouldBeZero' => [
'<?php
/**
* @psalm-pure
* @param array<empty, empty> $x
* @return 0
*/
function example($x) : int {
return count($x);
}',
],
'countConstantSizeArrayShouldBeConstantInteger' => [
'<?php
/**
* @psalm-pure
* @param array{int, int, string} $x
* @return 3
*/
function example($x) : int {
return count($x);
}',
],
'countCallableArrayShouldBe2' => [
'<?php
/**
* @psalm-pure
* @return 2
*/
function example(callable $x) : int {
assert(is_array($x));
return count($x);
}',
],
'countOnPureObjectIsPure' => [
'<?php
class PureCountable implements \Countable {
/** @psalm-pure */
public function count(): int { return 1; }
}
/** @psalm-pure */
function example(PureCountable $x) : int {
return count($x);
}',
],
'refineWithTraitExists' => [ 'refineWithTraitExists' => [
'<?php '<?php
function foo(string $s) : void { function foo(string $s) : void {
@ -1820,6 +1897,22 @@ class FunctionCallTest extends TestCase
}', }',
'error_message' => 'TypeDoesNotContainType', 'error_message' => 'TypeDoesNotContainType',
], ],
'countOnObjectCannotBePositive' => [
'<?php
/** @return positive-int|0 */
function example(\Countable $x) : int {
return count($x);
}',
'error_message' => 'LessSpecificReturnStatement',
],
'countOnUnknownObjectCannotBePure' => [
'<?php
/** @psalm-pure */
function example(\Countable $x) : int {
return count($x);
}',
'error_message' => 'ImpureFunctionCall',
],
'coerceCallMapArgsInStrictMode' => [ 'coerceCallMapArgsInStrictMode' => [
'<?php '<?php
declare(strict_types=1); declare(strict_types=1);