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 15, 2023
1 parent 5370492 commit 100907b
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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 Type::getInt();
}
if ($first_arg_is_float || $second_arg_is_float) {
return Type::getFloat();
}

return new Union([new TInt(), new TFloat()]);
}
}
42 changes: 42 additions & 0 deletions tests/ReturnTypeProvider/PowReturnTypeProviderTest.php
Original file line number Diff line number Diff line change
@@ -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 100907b

Please sign in to comment.