Skip to content

Commit

Permalink
Improve return types of pow()
Browse files Browse the repository at this point in the history
  • Loading branch information
robchett committed May 14, 2023
1 parent 5370492 commit 603dafd
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 2 deletions.
1 change: 0 additions & 1 deletion dictionaries/CallMap.php
Expand Up @@ -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<string, mixed>'],
'Postal\Parser::parse_address' => ['array<string,string>', 'address'=>'string', 'options='=>'array<string, string>'],
'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'],
Expand Down
1 change: 0 additions & 1 deletion dictionaries/CallMap_historical.php
Expand Up @@ -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'],
Expand Down
2 changes: 2 additions & 0 deletions src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php
Expand Up @@ -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;
Expand Down Expand Up @@ -103,6 +104,7 @@ public function __construct()
$this->registerClass(RoundReturnTypeProvider::class);
$this->registerClass(MbInternalEncodingReturnTypeProvider::class);
$this->registerClass(DateReturnTypeProvider::class);
$this->registerClass(PowReturnTypeProvider::class);
}

/**
Expand Down
@@ -0,0 +1,92 @@
<?php

namespace Psalm\Internal\Provider\ReturnTypeProvider;

use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
use Psalm\Type;
use Psalm\Type\Atomic\TFloat;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TLiteralFloat;
use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Union;

use function count;

/**
* @internal
*/
class PowReturnTypeProvider implements FunctionReturnTypeProviderInterface
{
/**
* @return array<lowercase-string>
*/
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()]);

Check failure on line 84 in src/Psalm/Internal/Provider/ReturnTypeProvider/PowReturnTypeProvider.php

View workflow job for this annotation

GitHub Actions / build

InvalidArgument

src/Psalm/Internal/Provider/ReturnTypeProvider/PowReturnTypeProvider.php:84:30: InvalidArgument: Argument 1 of Psalm\Type\Union::__construct expects non-empty-array<array-key, Psalm\Type\Atomic>, but list{Psalm\Type\Union} provided (see https://psalm.dev/004)
}
if ($first_arg_is_float || $second_arg_is_float) {
return new Union([Type::getFloat()]);

Check failure on line 87 in src/Psalm/Internal/Provider/ReturnTypeProvider/PowReturnTypeProvider.php

View workflow job for this annotation

GitHub Actions / build

InvalidArgument

src/Psalm/Internal/Provider/ReturnTypeProvider/PowReturnTypeProvider.php:87:30: InvalidArgument: Argument 1 of Psalm\Type\Union::__construct expects non-empty-array<array-key, Psalm\Type\Atomic>, but list{Psalm\Type\Union} provided (see https://psalm.dev/004)
}

return new Union([Type::getInt(), Type::getFloat()]);

Check failure on line 90 in src/Psalm/Internal/Provider/ReturnTypeProvider/PowReturnTypeProvider.php

View workflow job for this annotation

GitHub Actions / build

InvalidArgument

src/Psalm/Internal/Provider/ReturnTypeProvider/PowReturnTypeProvider.php:90:26: InvalidArgument: Argument 1 of Psalm\Type\Union::__construct expects non-empty-array<array-key, Psalm\Type\Atomic>, but list{Psalm\Type\Union, Psalm\Type\Union} provided (see https://psalm.dev/004)
}
}
42 changes: 42 additions & 0 deletions tests/ReturnTypeProvider/PowReturnTypeProviderTest.php
@@ -0,0 +1,42 @@
<?php

namespace Psalm\Tests\ReturnTypeProvider;

use Psalm\Tests\TestCase;
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;

class PowReturnTypeProviderTest extends TestCase
{
use ValidCodeAnalysisTestTrait;

public function providerValidCodeParse(): iterable
{
yield 'test' => [
'code' => '<?php
function getInt(): int {
return 1;
}
function getFloat(): float {
return 1.0;
}
$int = getInt();
$float = getFloat();
$a = pow($int, $int);
$b = pow($int, $float);
$c = pow($float, $int);
$d = pow(1000, 1000);
$e = pow(0, 1000);
$f = pow(1000, 0);
',
'assertions' => [
'$a===' => 'int',
'$b===' => 'float',
'$c===' => 'float',
'$d===' => 'float(INF)',
'$e===' => '0',
'$f===' => '1',
],
];
}
}

0 comments on commit 603dafd

Please sign in to comment.