mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
* Fixed vimeo/psalm#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
This commit is contained in:
parent
91902943bd
commit
26dd4c5b8f
@ -11670,7 +11670,7 @@ return [
|
|||||||
'rewind' => ['bool', 'stream'=>'resource'],
|
'rewind' => ['bool', 'stream'=>'resource'],
|
||||||
'rewinddir' => ['null|false', 'dir_handle='=>'resource'],
|
'rewinddir' => ['null|false', 'dir_handle='=>'resource'],
|
||||||
'rmdir' => ['bool', 'directory'=>'string', 'context='=>'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_close' => ['bool', 'rpmr'=>'resource'],
|
||||||
'rpm_get_tag' => ['mixed', 'rpmr'=>'resource', 'tagnum'=>'int'],
|
'rpm_get_tag' => ['mixed', 'rpmr'=>'resource', 'tagnum'=>'int'],
|
||||||
'rpm_is_valid' => ['bool', 'filename'=>'string'],
|
'rpm_is_valid' => ['bool', 'filename'=>'string'],
|
||||||
|
@ -14709,7 +14709,7 @@ return [
|
|||||||
'rewind' => ['bool', 'stream'=>'resource'],
|
'rewind' => ['bool', 'stream'=>'resource'],
|
||||||
'rewinddir' => ['null|false', 'dir_handle='=>'resource'],
|
'rewinddir' => ['null|false', 'dir_handle='=>'resource'],
|
||||||
'rmdir' => ['bool', 'directory'=>'string', 'context='=>'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_close' => ['bool', 'rpmr'=>'resource'],
|
||||||
'rpm_get_tag' => ['mixed', 'rpmr'=>'resource', 'tagnum'=>'int'],
|
'rpm_get_tag' => ['mixed', 'rpmr'=>'resource', 'tagnum'=>'int'],
|
||||||
'rpm_is_valid' => ['bool', 'filename'=>'string'],
|
'rpm_is_valid' => ['bool', 'filename'=>'string'],
|
||||||
|
@ -22,7 +22,6 @@ use Psalm\IssueBuffer;
|
|||||||
use Psalm\Type;
|
use Psalm\Type;
|
||||||
use Psalm\Type\Atomic\Scalar;
|
use Psalm\Type\Atomic\Scalar;
|
||||||
use Psalm\Type\Atomic\TArray;
|
use Psalm\Type\Atomic\TArray;
|
||||||
use Psalm\Type\Atomic\TBool;
|
|
||||||
use Psalm\Type\Atomic\TFalse;
|
use Psalm\Type\Atomic\TFalse;
|
||||||
use Psalm\Type\Atomic\TFloat;
|
use Psalm\Type\Atomic\TFloat;
|
||||||
use Psalm\Type\Atomic\TInt;
|
use Psalm\Type\Atomic\TInt;
|
||||||
@ -64,8 +63,8 @@ class CastAnalyzer
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$as_int = true;
|
|
||||||
$valid_int_type = null;
|
$valid_int_type = null;
|
||||||
|
$type_parent_nodes = null;
|
||||||
$maybe_type = $statements_analyzer->node_data->getType($stmt->expr);
|
$maybe_type = $statements_analyzer->node_data->getType($stmt->expr);
|
||||||
|
|
||||||
if ($maybe_type) {
|
if ($maybe_type) {
|
||||||
@ -74,36 +73,30 @@ class CastAnalyzer
|
|||||||
if (!$maybe_type->from_calculation) {
|
if (!$maybe_type->from_calculation) {
|
||||||
self::handleRedundantCast($maybe_type, $statements_analyzer, $stmt);
|
self::handleRedundantCast($maybe_type, $statements_analyzer, $stmt);
|
||||||
}
|
}
|
||||||
|
} elseif ($maybe_type->isSingleStringLiteral()) {
|
||||||
|
$valid_int_type = Type::getInt(false, (int)$maybe_type->getSingleStringLiteral()->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($maybe_type->getAtomicTypes()) === 1
|
if (count($maybe_type->getAtomicTypes()) === 1
|
||||||
&& $maybe_type->getSingleAtomic() instanceof TBool) {
|
&& $maybe_type->getSingleAtomic() instanceof Type\Atomic\TBool) {
|
||||||
$as_int = false;
|
$valid_int_type = new Union([
|
||||||
$type = new Union([
|
|
||||||
new TLiteralInt(0),
|
new TLiteralInt(0),
|
||||||
new TLiteralInt(1),
|
new TLiteralInt(1),
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph
|
if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph) {
|
||||||
) {
|
$type_parent_nodes = $maybe_type->parent_nodes;
|
||||||
$type->parent_nodes = $maybe_type->parent_nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
$statements_analyzer->node_data->setType($stmt, $type);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($as_int) {
|
$type = $valid_int_type ?? Type::getInt();
|
||||||
$type = $valid_int_type ?? Type::getInt();
|
if ($type_parent_nodes !== null) {
|
||||||
|
$type->parent_nodes = $type_parent_nodes;
|
||||||
if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph
|
|
||||||
) {
|
|
||||||
$type->parent_nodes = $maybe_type->parent_nodes ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$statements_analyzer->node_data->setType($stmt, $type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$statements_analyzer->node_data->setType($stmt, $type);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ use Psalm\Internal\Provider\ReturnTypeProvider\MinMaxReturnTypeProvider;
|
|||||||
use Psalm\Internal\Provider\ReturnTypeProvider\MktimeReturnTypeProvider;
|
use Psalm\Internal\Provider\ReturnTypeProvider\MktimeReturnTypeProvider;
|
||||||
use Psalm\Internal\Provider\ReturnTypeProvider\ParseUrlReturnTypeProvider;
|
use Psalm\Internal\Provider\ReturnTypeProvider\ParseUrlReturnTypeProvider;
|
||||||
use Psalm\Internal\Provider\ReturnTypeProvider\RandReturnTypeProvider;
|
use Psalm\Internal\Provider\ReturnTypeProvider\RandReturnTypeProvider;
|
||||||
|
use Psalm\Internal\Provider\ReturnTypeProvider\RoundReturnTypeProvider;
|
||||||
use Psalm\Internal\Provider\ReturnTypeProvider\StrReplaceReturnTypeProvider;
|
use Psalm\Internal\Provider\ReturnTypeProvider\StrReplaceReturnTypeProvider;
|
||||||
use Psalm\Internal\Provider\ReturnTypeProvider\StrTrReturnTypeProvider;
|
use Psalm\Internal\Provider\ReturnTypeProvider\StrTrReturnTypeProvider;
|
||||||
use Psalm\Internal\Provider\ReturnTypeProvider\TriggerErrorReturnTypeProvider;
|
use Psalm\Internal\Provider\ReturnTypeProvider\TriggerErrorReturnTypeProvider;
|
||||||
@ -95,6 +96,7 @@ class FunctionReturnTypeProvider
|
|||||||
$this->registerClass(TriggerErrorReturnTypeProvider::class);
|
$this->registerClass(TriggerErrorReturnTypeProvider::class);
|
||||||
$this->registerClass(RandReturnTypeProvider::class);
|
$this->registerClass(RandReturnTypeProvider::class);
|
||||||
$this->registerClass(InArrayReturnTypeProvider::class);
|
$this->registerClass(InArrayReturnTypeProvider::class);
|
||||||
|
$this->registerClass(RoundReturnTypeProvider::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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()]);
|
||||||
|
}
|
||||||
|
}
|
@ -1785,6 +1785,14 @@ class FunctionCallTest extends TestCase
|
|||||||
'ignored_issues' => [],
|
'ignored_issues' => [],
|
||||||
'php_version' => '8.0',
|
'php_version' => '8.0',
|
||||||
],
|
],
|
||||||
|
'round_literalValue' => [
|
||||||
|
'code' => '<?php
|
||||||
|
$a = round(10.363, 2);
|
||||||
|
',
|
||||||
|
'assertions' => [
|
||||||
|
'$a===' => 'float(10.36)',
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -904,6 +904,14 @@ class ValueTest extends TestCase
|
|||||||
if (empty($s)) {}
|
if (empty($s)) {}
|
||||||
}',
|
}',
|
||||||
],
|
],
|
||||||
|
'literalInt' => [
|
||||||
|
'code' => '<?php
|
||||||
|
$a = (int)"5";
|
||||||
|
',
|
||||||
|
'assertions' => [
|
||||||
|
'$a===' => '5',
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user