diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index b6436c18be7..501742a44a8 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -9384,7 +9384,6 @@ 'posix_uname' => ['array{sysname: string, nodename: string, release: string, version: string, machine: string, domainname: string}|false'], 'Postal\Expand::expand_address' => ['string[]', 'address'=>'string', 'options='=>'array'], 'Postal\Parser::parse_address' => ['array', 'address'=>'string', 'options='=>'array'], -'pow' => ['float|int', 'num'=>'int|float', 'exponent'=>'int|float'], 'preg_filter' => ['string|string[]|null', 'pattern'=>'string|string[]', 'replacement'=>'string|string[]', 'subject'=>'string|string[]', 'limit='=>'int', '&w_count='=>'int'], 'preg_grep' => ['array|false', 'pattern'=>'string', 'array'=>'array', 'flags='=>'int'], 'preg_last_error' => ['int'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 28b1cbb6fc6..1efeea9f314 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -13535,7 +13535,6 @@ 'posix_times' => ['array{ticks: int, utime: int, stime: int, cutime: int, cstime: int}|false'], 'posix_ttyname' => ['string|false', 'file_descriptor'=>'resource|int'], 'posix_uname' => ['array{sysname: string, nodename: string, release: string, version: string, machine: string, domainname: string}|false'], - 'pow' => ['float|int', 'num'=>'int|float', 'exponent'=>'int|float'], 'preg_filter' => ['string|string[]|null', 'pattern'=>'string|string[]', 'replacement'=>'string|string[]', 'subject'=>'string|string[]', 'limit='=>'int', '&w_count='=>'int'], 'preg_grep' => ['array|false', 'pattern'=>'string', 'array'=>'array', 'flags='=>'int'], 'preg_last_error' => ['int'], diff --git a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php index ad1827b52c7..de1e27d7a5b 100644 --- a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php @@ -36,6 +36,7 @@ use Psalm\Internal\Provider\ReturnTypeProvider\MinMaxReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\MktimeReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\ParseUrlReturnTypeProvider; +use Psalm\Internal\Provider\ReturnTypeProvider\PowReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\RandReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\RoundReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\StrReplaceReturnTypeProvider; @@ -103,6 +104,7 @@ public function __construct() $this->registerClass(RoundReturnTypeProvider::class); $this->registerClass(MbInternalEncodingReturnTypeProvider::class); $this->registerClass(DateReturnTypeProvider::class); + $this->registerClass(PowReturnTypeProvider::class); } /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/PowReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/PowReturnTypeProvider.php new file mode 100644 index 00000000000..885726e188a --- /dev/null +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/PowReturnTypeProvider.php @@ -0,0 +1,92 @@ + + */ + public static function getFunctionIds(): array + { + return ['pow']; + } + + public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Union + { + $call_args = $event->getCallArgs(); + + if (count($call_args) !== 2) { + return null; + } + + $first_arg = $event->getStatementsSource()->getNodeTypeProvider()->getType($call_args[0]->value); + $second_arg = $event->getStatementsSource()->getNodeTypeProvider()->getType($call_args[1]->value); + + $first_arg_literal = null; + $first_arg_is_int = false; + $first_arg_is_float = false; + if ($first_arg !== null && $first_arg->isSingle()) { + $first_atomic_type = $first_arg->getSingleAtomic(); + if ($first_atomic_type instanceof TInt) { + $first_arg_is_int = true; + } elseif ($first_atomic_type instanceof TFloat) { + $first_arg_is_float = true; + } + if ($first_atomic_type instanceof TLiteralInt + || $first_atomic_type instanceof TLiteralFloat + ) { + $first_arg_literal = $first_atomic_type->value; + } + } + + $second_arg_literal = null; + $second_arg_is_int = false; + $second_arg_is_float = false; + if ($second_arg !== null && $second_arg->isSingle()) { + $second_atomic_type = $second_arg->getSingleAtomic(); + if ($second_atomic_type instanceof TInt) { + $second_arg_is_int = true; + } elseif ($second_atomic_type instanceof TFloat) { + $second_arg_is_float = true; + } + if ($second_atomic_type instanceof TLiteralInt + || $second_atomic_type instanceof TLiteralFloat + ) { + $second_arg_literal = $second_atomic_type->value; + } + } + + if ($first_arg_literal === 0) { + return Type::getInt(true, 0); + } + if ($second_arg_literal === 0) { + return Type::getInt(true, 1); + } + if ($first_arg_literal !== null && $second_arg_literal !== null) { + return Type::getFloat($first_arg_literal ** $second_arg_literal); + } + if ($first_arg_is_int && $second_arg_is_int) { + return new Union([Type::getInt()]); + } + if ($first_arg_is_float || $second_arg_is_float) { + return new Union([Type::getFloat()]); + } + + return new Union([Type::getInt(), Type::getFloat()]); + } +} diff --git a/tests/ReturnTypeProvider/PowReturnTypeProviderTest.php b/tests/ReturnTypeProvider/PowReturnTypeProviderTest.php new file mode 100644 index 00000000000..f428518564a --- /dev/null +++ b/tests/ReturnTypeProvider/PowReturnTypeProviderTest.php @@ -0,0 +1,42 @@ + [ + 'code' => ' [ + '$a===' => 'int', + '$b===' => 'float', + '$c===' => 'float', + '$d===' => 'float(INF)', + '$e===' => '0', + '$f===' => '1', + ], + ]; + } +}