Skip to content

Commit

Permalink
Cherry-pick: Try to provide literal int types when possible (fixes vi…
Browse files Browse the repository at this point in the history
…meo#6966) (vimeo#7071)

* Fixed vimeo#6966

* Only accept >= 0 values for mode argument in round()

* Made round() only return float or literal float values and remove unneeded test

* Registered RoundReturnTypeProvider

* Updated cast analyzer to handle single string literal int values as literal ints

* Fixed psalm errors

* Fix invalid property accesses

* Addressed comments

* Added Tests

* Marked RoundReturnTypeProvider as internal

* Fixed CS
  • Loading branch information
ricardoboss authored and kkmuffme committed Sep 15, 2022
1 parent 53cd4c1 commit 5ab90d4
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 3 deletions.
2 changes: 1 addition & 1 deletion dictionaries/CallMap.php
Expand Up @@ -11714,7 +11714,7 @@
'rewind' => ['bool', 'stream'=>'resource'],
'rewinddir' => ['null|false', 'dir_handle='=>'resource'],
'rmdir' => ['bool', 'directory'=>'string', 'context='=>'resource'],
'round' => ['float', 'num'=>'float', 'precision='=>'int', 'mode='=>'int'],
'round' => ['float', 'num'=>'float', 'precision='=>'int', 'mode='=>'0|positive-int'],
'rpm_close' => ['bool', 'rpmr'=>'resource'],
'rpm_get_tag' => ['mixed', 'rpmr'=>'resource', 'tagnum'=>'int'],
'rpm_is_valid' => ['bool', 'filename'=>'string'],
Expand Down
2 changes: 1 addition & 1 deletion dictionaries/CallMap_historical.php
Expand Up @@ -14751,7 +14751,7 @@
'rewind' => ['bool', 'stream'=>'resource'],
'rewinddir' => ['null|false', 'dir_handle='=>'resource'],
'rmdir' => ['bool', 'directory'=>'string', 'context='=>'resource'],
'round' => ['float', 'num'=>'float', 'precision='=>'int', 'mode='=>'int'],
'round' => ['float', 'num'=>'float', 'precision='=>'int', 'mode='=>'0|positive-int'],
'rpm_close' => ['bool', 'rpmr'=>'resource'],
'rpm_get_tag' => ['mixed', 'rpmr'=>'resource', 'tagnum'=>'int'],
'rpm_is_valid' => ['bool', 'filename'=>'string'],
Expand Down
2 changes: 2 additions & 0 deletions src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php
Expand Up @@ -35,6 +35,7 @@
use Psalm\Internal\Provider\ReturnTypeProvider\MktimeReturnTypeProvider;
use Psalm\Internal\Provider\ReturnTypeProvider\ParseUrlReturnTypeProvider;
use Psalm\Internal\Provider\ReturnTypeProvider\RandReturnTypeProvider;
use Psalm\Internal\Provider\ReturnTypeProvider\RoundReturnTypeProvider;
use Psalm\Internal\Provider\ReturnTypeProvider\StrReplaceReturnTypeProvider;
use Psalm\Internal\Provider\ReturnTypeProvider\StrTrReturnTypeProvider;
use Psalm\Internal\Provider\ReturnTypeProvider\TriggerErrorReturnTypeProvider;
Expand Down Expand Up @@ -109,6 +110,7 @@ public function __construct()
$this->registerClass(TriggerErrorReturnTypeProvider::class);
$this->registerClass(RandReturnTypeProvider::class);
$this->registerClass(InArrayReturnTypeProvider::class);
$this->registerClass(RoundReturnTypeProvider::class);
}

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

declare(strict_types=1);

namespace Psalm\Internal\Provider\ReturnTypeProvider;

use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
use Psalm\Type;

use function array_values;
use function count;
use function round;

use const PHP_ROUND_HALF_UP;

/**
* @internal
*/
class RoundReturnTypeProvider implements FunctionReturnTypeProviderInterface
{
/**
* @return array<lowercase-string>
*/
public static function getFunctionIds(): array
{
return ['round'];
}

public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Type\Union
{
$call_args = $event->getCallArgs();
if (count($call_args) === 0) {
return null;
}

$statements_source = $event->getStatementsSource();
$nodeTypeProvider = $statements_source->getNodeTypeProvider();

$num_arg = $nodeTypeProvider->getType($call_args[0]->value);

$precision_val = 0;
if ($statements_source instanceof StatementsAnalyzer && count($call_args) > 1) {
$type = $statements_source->node_data->getType($call_args[1]->value);

if ($type !== null && $type->isSingle()) {
$atomic_type = array_values($type->getAtomicTypes())[0];
if ($atomic_type instanceof Type\Atomic\TLiteralInt) {
$precision_val = $atomic_type->value;
}
}
}

$mode_val = PHP_ROUND_HALF_UP;
if ($statements_source instanceof StatementsAnalyzer && count($call_args) > 2) {
$type = $statements_source->node_data->getType($call_args[2]->value);

if ($type !== null && $type->isSingle()) {
$atomic_type = array_values($type->getAtomicTypes())[0];
if ($atomic_type instanceof Type\Atomic\TLiteralInt) {
/** @var positive-int|0 $mode_val */
$mode_val = $atomic_type->value;
}
}
}

if ($num_arg !== null && $num_arg->isSingle()) {
$num_type = array_values($num_arg->getAtomicTypes())[0];
if ($num_type instanceof Type\Atomic\TLiteralFloat || $num_type instanceof Type\Atomic\TLiteralInt) {
$rounded_val = round($num_type->value, $precision_val, $mode_val);
return new Type\Union([new Type\Atomic\TLiteralFloat($rounded_val)]);
}
}

return new Type\Union([new Type\Atomic\TFloat()]);
}
}
9 changes: 8 additions & 1 deletion tests/FunctionCallTest.php
Expand Up @@ -31,7 +31,6 @@ function filter(array $strings): array {
}
'
],

'typedArrayWithDefault' => [
'<?php
class A {}
Expand Down Expand Up @@ -1812,6 +1811,14 @@ function sayHello(string $needle): void {
'$b===' => 'lowercase-string',
],
],
'round_literalValue' => [
'<?php
$a = round(10.363, 2);
',
'assertions' => [
'$a===' => 'float(10.36)',
],
],
];
}

Expand Down
8 changes: 8 additions & 0 deletions tests/TypeReconciliation/ValueTest.php
Expand Up @@ -909,6 +909,14 @@ function foo(string $s) : void {
$interval = \DateInterval::createFromDateString("30 дней");
if ($interval === false) {}',
],
'literalInt' => [
'<?php
$a = (int)"5";
',
'assertions' => [
'$a===' => '5',
],
],
];
}

Expand Down

0 comments on commit 5ab90d4

Please sign in to comment.