From 0b5a828f6f937eb89431d84f041d63daa9260165 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Wed, 13 Jan 2021 15:48:38 +0100 Subject: [PATCH] #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. --- .../Call/FunctionCallReturnTypeFetcher.php | 9 +- tests/FunctionCallTest.php | 93 +++++++++++++++++++ 2 files changed, 100 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php index d780b69f225..59449e096d7 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php @@ -300,7 +300,7 @@ 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 ]); } @@ -308,7 +308,7 @@ 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 ]); } @@ -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 + ]); } } } diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index ea53d00bf15..ba1141ca506 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -1054,6 +1054,83 @@ function example($x) : void { } }' ], + 'countNonEmptyArrayShouldBePositiveInt' => [ + ' [ + ' [ + ' [ + ' $x + * @return 0 + */ + function example($x) : int { + return count($x); + }', + ], + 'countConstantSizeArrayShouldBeConstantInteger' => [ + ' [ + ' [ + ' [ ' 'TypeDoesNotContainType', ], + 'countOnObjectCannotBePositive' => [ + ' 'LessSpecificReturnStatement', + ], + 'countOnUnknownObjectCannotBePure' => [ + ' 'ImpureFunctionCall', + ], 'coerceCallMapArgsInStrictMode' => [ '