From 2585404f01a308d29c3905bb831ea6e75af8ab40 Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Sun, 5 Dec 2021 16:59:35 +0100 Subject: [PATCH 01/11] Fixed vimeo/psalm#6966 --- .../RoundReturnTypeProvider.php | 57 +++++++++++++++++++ tests/ReturnTypeProvider/RoundTest.php | 45 +++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php create mode 100644 tests/ReturnTypeProvider/RoundTest.php diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php new file mode 100644 index 00000000000..ff3790fbb97 --- /dev/null +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php @@ -0,0 +1,57 @@ + + */ + 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); + + if (count($call_args) > 1) { + $precision_val = $call_args[1]->value; + } else { + $precision_val = 0; + } + + if ($num_arg !== null && $num_arg->isSingle()) { + $num_type = array_values($num_arg->getAtomicTypes())[0]; + if ($num_type instanceof Type\Atomic\TFloat) { + if ($precision_val > 0) { + return new Type\Union([new Type\Atomic\TFloat()]); + } + + return new Type\Union([new Type\Atomic\TInt()]); + } + + if ($num_type instanceof Type\Atomic\TInt) { + return new Type\Union([new Type\Atomic\TInt()]); + } + } + + return new Type\Union([new Type\Atomic\TInt(), new Type\Atomic\TFloat()]); + } +} diff --git a/tests/ReturnTypeProvider/RoundTest.php b/tests/ReturnTypeProvider/RoundTest.php new file mode 100644 index 00000000000..74b6eed64a9 --- /dev/null +++ b/tests/ReturnTypeProvider/RoundTest.php @@ -0,0 +1,45 @@ + [ + ' Date: Sun, 5 Dec 2021 17:46:39 +0100 Subject: [PATCH 02/11] Only accept >= 0 values for mode argument in round() --- dictionaries/CallMap.php | 2 +- dictionaries/CallMap_historical.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 9f31a7a44d4..312a282ce5b 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -11670,7 +11670,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'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 362a181db8c..aed823b6d56 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -14709,7 +14709,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'], From d72a7022deabf6562eacd90d3c05c100243ba9c8 Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Sun, 5 Dec 2021 17:47:17 +0100 Subject: [PATCH 03/11] Made round() only return float or literal float values and remove unneeded test --- .../RoundReturnTypeProvider.php | 24 +++++----- tests/ReturnTypeProvider/RoundTest.php | 45 ------------------- 2 files changed, 13 insertions(+), 56 deletions(-) delete mode 100644 tests/ReturnTypeProvider/RoundTest.php diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php index ff3790fbb97..847b9de08ea 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php @@ -8,6 +8,9 @@ use function array_values; use function count; +use function round; + +use const PHP_ROUND_HALF_UP; class RoundReturnTypeProvider implements FunctionReturnTypeProviderInterface { @@ -37,21 +40,20 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $precision_val = 0; } + if (count($call_args) > 2) { + $mode_val = $call_args[2]->value; + } else { + $mode_val = PHP_ROUND_HALF_UP; + } + if ($num_arg !== null && $num_arg->isSingle()) { $num_type = array_values($num_arg->getAtomicTypes())[0]; - if ($num_type instanceof Type\Atomic\TFloat) { - if ($precision_val > 0) { - return new Type\Union([new Type\Atomic\TFloat()]); - } - - return new Type\Union([new Type\Atomic\TInt()]); - } - - if ($num_type instanceof Type\Atomic\TInt) { - return new Type\Union([new Type\Atomic\TInt()]); + 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\TInt(), new Type\Atomic\TFloat()]); + return new Type\Union([new Type\Atomic\TFloat()]); } } diff --git a/tests/ReturnTypeProvider/RoundTest.php b/tests/ReturnTypeProvider/RoundTest.php deleted file mode 100644 index 74b6eed64a9..00000000000 --- a/tests/ReturnTypeProvider/RoundTest.php +++ /dev/null @@ -1,45 +0,0 @@ - [ - ' Date: Sun, 5 Dec 2021 17:47:31 +0100 Subject: [PATCH 04/11] Registered RoundReturnTypeProvider --- src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php index 06390ab8069..686892baf23 100644 --- a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php @@ -38,6 +38,7 @@ use Psalm\Internal\Provider\ReturnTypeProvider\StrTrReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\TriggerErrorReturnTypeProvider; use Psalm\Internal\Provider\ReturnTypeProvider\VersionCompareReturnTypeProvider; +use Psalm\Internal\Provider\ReturnTypeProvider\RoundReturnTypeProvider; use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent; use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface; use Psalm\StatementsSource; @@ -95,6 +96,7 @@ public function __construct() $this->registerClass(TriggerErrorReturnTypeProvider::class); $this->registerClass(RandReturnTypeProvider::class); $this->registerClass(InArrayReturnTypeProvider::class); + $this->registerClass(RoundReturnTypeProvider::class); } /** From 59f1d844410e4cb4f119da36a2e99d0cc42f007e Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Sun, 5 Dec 2021 18:04:39 +0100 Subject: [PATCH 05/11] Updated cast analyzer to handle single string literal int values as literal ints --- .../Statements/Expression/CastAnalyzer.php | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php index 3d34b3f44f2..103ae06cfc9 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -64,7 +64,6 @@ public static function analyze( return false; } - $as_int = true; $valid_int_type = null; $maybe_type = $statements_analyzer->node_data->getType($stmt->expr); @@ -74,36 +73,32 @@ public static function analyze( if (!$maybe_type->from_calculation) { self::handleRedundantCast($maybe_type, $statements_analyzer, $stmt); } + + if ($maybe_type->isSingleStringLiteral()) { + $valid_int_type = new Union([ + new TLiteralInt((int)$maybe_type->getSingleStringLiteral()->value), + ]); + } } if (count($maybe_type->getAtomicTypes()) === 1 - && $maybe_type->getSingleAtomic() instanceof TBool) { - $as_int = false; - $type = new Union([ + && $maybe_type->getSingleAtomic() instanceof Type\Atomic\TBool) { + $valid_int_type = new Union([ new TLiteralInt(0), new TLiteralInt(1), ]); - - if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph - ) { - $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 ($statements_analyzer->data_flow_graph instanceof VariableUseGraph - ) { - $type->parent_nodes = $maybe_type->parent_nodes ?? []; - } - - $statements_analyzer->node_data->setType($stmt, $type); + if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph + ) { + $type->parent_nodes = $maybe_type->parent_nodes ?? []; } + $statements_analyzer->node_data->setType($stmt, $type); + return true; } From 96a98161c6791c684f1df4d061f7df2fb441af2f Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Sun, 5 Dec 2021 18:17:05 +0100 Subject: [PATCH 06/11] Fixed psalm errors --- .../Provider/ReturnTypeProvider/RoundReturnTypeProvider.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php index 847b9de08ea..21ef4ab229e 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php @@ -35,13 +35,14 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $num_arg = $nodeTypeProvider->getType($call_args[0]->value); if (count($call_args) > 1) { - $precision_val = $call_args[1]->value; + $precision_val = (int)$call_args[1]->value; } else { $precision_val = 0; } if (count($call_args) > 2) { - $mode_val = $call_args[2]->value; + /** @var positive-int|0 $mode_val */ + $mode_val = (int)$call_args[2]->value; } else { $mode_val = PHP_ROUND_HALF_UP; } From 9bc74bd600b29f78fa8a327d34b4921f1456f109 Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Mon, 6 Dec 2021 02:01:45 +0100 Subject: [PATCH 07/11] Fix invalid property accesses --- .../RoundReturnTypeProvider.php | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php index 21ef4ab229e..fd8297f6c17 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php @@ -2,6 +2,7 @@ namespace Psalm\Internal\Provider\ReturnTypeProvider; +use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent; use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface; use Psalm\Type; @@ -34,17 +35,29 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $num_arg = $nodeTypeProvider->getType($call_args[0]->value); - if (count($call_args) > 1) { - $precision_val = (int)$call_args[1]->value; - } else { - $precision_val = 0; + $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; + } + } } - if (count($call_args) > 2) { - /** @var positive-int|0 $mode_val */ - $mode_val = (int)$call_args[2]->value; - } else { - $mode_val = PHP_ROUND_HALF_UP; + $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()) { From 62a09233e1b65c8aa93cfe17a901e5596a1183b0 Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Fri, 10 Dec 2021 00:36:16 +0100 Subject: [PATCH 08/11] Addressed comments --- .../Statements/Expression/CastAnalyzer.php | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php index 103ae06cfc9..b82824b8600 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -65,6 +65,7 @@ public static function analyze( } $valid_int_type = null; + $type_parent_nodes = null; $maybe_type = $statements_analyzer->node_data->getType($stmt->expr); if ($maybe_type) { @@ -73,12 +74,8 @@ public static function analyze( if (!$maybe_type->from_calculation) { self::handleRedundantCast($maybe_type, $statements_analyzer, $stmt); } - - if ($maybe_type->isSingleStringLiteral()) { - $valid_int_type = new Union([ - new TLiteralInt((int)$maybe_type->getSingleStringLiteral()->value), - ]); - } + } elseif ($maybe_type->isSingleStringLiteral()) { + $valid_int_type = Type::getInt(false, (int)$maybe_type->getSingleStringLiteral()->value); } if (count($maybe_type->getAtomicTypes()) === 1 @@ -88,13 +85,15 @@ public static function analyze( new TLiteralInt(1), ]); } + + if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph) { + $type_parent_nodes = $maybe_type->parent_nodes; + } } $type = $valid_int_type ?? Type::getInt(); - - if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph - ) { - $type->parent_nodes = $maybe_type->parent_nodes ?? []; + if ($type_parent_nodes !== null) { + $type->parent_nodes = $type_parent_nodes; } $statements_analyzer->node_data->setType($stmt, $type); From ffe631244fad57e050d70868daa6bdf88219ab11 Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Sun, 16 Jan 2022 21:17:07 +0100 Subject: [PATCH 09/11] Added Tests --- tests/FunctionCallTest.php | 8 ++++++++ tests/TypeReconciliation/ValueTest.php | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index 7bacb736297..00fb3f40f4c 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -1785,6 +1785,14 @@ function sayHello(string $needle): void { 'ignored_issues' => [], 'php_version' => '8.0', ], + 'round_literalValue' => [ + 'code' => ' [ + '$a===' => 'float(10.36)', + ], + ], ]; } diff --git a/tests/TypeReconciliation/ValueTest.php b/tests/TypeReconciliation/ValueTest.php index 5a4ad3b3d37..3a0e12963e5 100644 --- a/tests/TypeReconciliation/ValueTest.php +++ b/tests/TypeReconciliation/ValueTest.php @@ -904,6 +904,14 @@ function foo(string $s) : void { if (empty($s)) {} }', ], + 'literalInt' => [ + 'code' => ' [ + '$a===' => '5', + ], + ], ]; } From edb72f79dcd65aae61ddd5d5daec2b7f0e8176b6 Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Sun, 16 Jan 2022 21:18:08 +0100 Subject: [PATCH 10/11] Marked RoundReturnTypeProvider as internal --- .../Provider/ReturnTypeProvider/RoundReturnTypeProvider.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php index fd8297f6c17..c2d1afa640e 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php @@ -13,6 +13,9 @@ use const PHP_ROUND_HALF_UP; +/** + * @internal + */ class RoundReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** From 7ce99338141789fa9c9628d1a7bfafa007582e78 Mon Sep 17 00:00:00 2001 From: Ricardo Boss Date: Sun, 16 Jan 2022 21:26:32 +0100 Subject: [PATCH 11/11] Fixed CS --- .../Internal/Analyzer/Statements/Expression/CastAnalyzer.php | 1 - src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php | 2 +- .../Provider/ReturnTypeProvider/RoundReturnTypeProvider.php | 4 +++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php index b82824b8600..a92bc92e7f7 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -22,7 +22,6 @@ use Psalm\Type; use Psalm\Type\Atomic\Scalar; use Psalm\Type\Atomic\TArray; -use Psalm\Type\Atomic\TBool; use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TFloat; use Psalm\Type\Atomic\TInt; diff --git a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php index 686892baf23..cb8dc08aeb8 100644 --- a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php @@ -34,11 +34,11 @@ 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; use Psalm\Internal\Provider\ReturnTypeProvider\VersionCompareReturnTypeProvider; -use Psalm\Internal\Provider\ReturnTypeProvider\RoundReturnTypeProvider; use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent; use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface; use Psalm\StatementsSource; diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php index c2d1afa640e..eb8ee561f54 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/RoundReturnTypeProvider.php @@ -1,4 +1,6 @@ -