Skip to content

Commit

Permalink
#4997 added more precise type inference for count() returning 0 o…
Browse files Browse the repository at this point in the history
…r `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.
  • Loading branch information
Ocramius authored Jan 13, 2021
1 parent 1afce4d commit a53cc23
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -300,15 +300,15 @@ private static function getReturnTypeFromCallMapWithArgs(
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
]);
}

if ($atomic_types['array'] instanceof Type\Atomic\TNonEmptyList) {
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
]);
}

Expand All @@ -319,6 +319,11 @@ private static function getReturnTypeFromCallMapWithArgs(
new Type\Atomic\TLiteralInt(count($atomic_types['array']->properties))
]);
}

return new Type\Union([
new Type\Atomic\TLiteralInt(0),
new Type\Atomic\TPositiveInt
]);
}
}
}
Expand Down
93 changes: 93 additions & 0 deletions tests/FunctionCallTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,83 @@ function example($x) : void {
}
}'
],
'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 {
Expand Down Expand Up @@ -1820,6 +1897,22 @@ function example($x) : void {
}',
'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);
Expand Down

0 comments on commit a53cc23

Please sign in to comment.