mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 12:24:49 +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:
parent
2daa27548e
commit
0b5a828f6f
@ -300,7 +300,7 @@ class FunctionCallReturnTypeFetcher
|
||||
return new Type\Union([
|
||||
$atomic_types['array']->count !== null
|
||||
? 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([
|
||||
$atomic_types['array']->count !== null
|
||||
? 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))
|
||||
]);
|
||||
}
|
||||
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TLiteralInt(0),
|
||||
new Type\Atomic\TPositiveInt
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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' => [
|
||||
'<?php
|
||||
function foo(string $s) : void {
|
||||
@ -1820,6 +1897,22 @@ class FunctionCallTest extends TestCase
|
||||
}',
|
||||
'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' => [
|
||||
'<?php
|
||||
declare(strict_types=1);
|
||||
|
Loading…
Reference in New Issue
Block a user