From 5bb9ebf8226ecc5924cf7cc2bdc3c6077bc8bad4 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Fri, 3 May 2024 21:43:19 +0200 Subject: [PATCH] Casting int-range should keep literals Fix https://github.com/vimeo/psalm/issues/10940 500 limit taken from TypeCombiner --- .../Statements/Expression/CastAnalyzer.php | 24 ++++++++++++++++++- tests/CastTest.php | 10 ++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php index 5832bb159fe..dbb659f9ff6 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -28,6 +28,7 @@ use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TFloat; use Psalm\Type\Atomic\TInt; +use Psalm\Type\Atomic\TIntRange; use Psalm\Type\Atomic\TKeyedArray; use Psalm\Type\Atomic\TList; use Psalm\Type\Atomic\TLiteralFloat; @@ -53,6 +54,7 @@ use function array_pop; use function array_values; use function get_class; +use function range; use function strtolower; /** @@ -537,6 +539,18 @@ public static function castFloatAttempt( continue; } + if ($atomic_type instanceof TIntRange + && $atomic_type->min_bound !== null + && $atomic_type->max_bound !== null + && ($atomic_type->max_bound - $atomic_type->min_bound) < 500 + ) { + foreach (range($atomic_type->min_bound, $atomic_type->max_bound) as $literal_int_value) { + $valid_floats[] = new TLiteralFloat((float) $literal_int_value); + } + + continue; + } + if ($atomic_type instanceof TInt) { if ($atomic_type instanceof TLiteralInt) { $valid_floats[] = new TLiteralFloat((float) $atomic_type->value); @@ -721,9 +735,17 @@ public static function castStringAttempt( || $atomic_type instanceof TNumeric ) { if ($atomic_type instanceof TLiteralInt || $atomic_type instanceof TLiteralFloat) { - $castable_types[] = Type::getAtomicStringFromLiteral((string) $atomic_type->value); + $valid_strings[] = Type::getAtomicStringFromLiteral((string) $atomic_type->value); } elseif ($atomic_type instanceof TNonspecificLiteralInt) { $castable_types[] = new TNonspecificLiteralString(); + } elseif ($atomic_type instanceof TIntRange + && $atomic_type->min_bound !== null + && $atomic_type->max_bound !== null + && ($atomic_type->max_bound - $atomic_type->min_bound) < 500 + ) { + foreach (range($atomic_type->min_bound, $atomic_type->max_bound) as $literal_int_value) { + $valid_strings[] = Type::getAtomicStringFromLiteral((string) $literal_int_value); + } } else { $castable_types[] = new TNumericString(); } diff --git a/tests/CastTest.php b/tests/CastTest.php index fe93e5d7b3f..414aa70aedb 100644 --- a/tests/CastTest.php +++ b/tests/CastTest.php @@ -49,5 +49,15 @@ public function providerValidCodeParse(): iterable '$a===' => 'array{a: int, b: string, ...}', ], ]; + yield 'castIntRangeToString' => [ + 'code' => ' */ + $int_range = 2; + $string = (string) $int_range; + ', + 'assertions' => [ + '$string===' => "'-1'|'-2'|'-3'|'-4'|'-5'|'0'|'1'|'2'|'3'", + ], + ]; } }