From 1e5525603bfd0a696a39e92a58ce200d84d2200b Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 17 Oct 2022 11:45:38 +0200 Subject: [PATCH] Let TypeCombiner return disjoint int ranges --- .../Fetch/VariableFetchAnalyzer.php | 2 +- .../Internal/Provider/ParserCacheProvider.php | 5 +- src/Psalm/Internal/Provider/Providers.php | 2 +- .../MinMaxReturnTypeProvider.php | 1 + .../Internal/Provider/StatementsProvider.php | 2 +- src/Psalm/Internal/Type/TypeCombiner.php | 61 ++++++++++++------- src/Psalm/Type/Reconciler.php | 2 + 7 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index 1872097914e..740daa859d7 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -778,7 +778,7 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ ]), ]; - if ($codebase_analysis_php_version_id >= 81000) { + if ($codebase_analysis_php_version_id >= 8_10_00) { $values['full_path'] = new Union([ new TString(), new TNonEmptyList(Type::getString()), diff --git a/src/Psalm/Internal/Provider/ParserCacheProvider.php b/src/Psalm/Internal/Provider/ParserCacheProvider.php index e1345240cee..559a11ecf63 100644 --- a/src/Psalm/Internal/Provider/ParserCacheProvider.php +++ b/src/Psalm/Internal/Provider/ParserCacheProvider.php @@ -11,6 +11,8 @@ use UnexpectedValueException; use function clearstatcache; +use function error_log; +use function file_get_contents; use function file_put_contents; use function filemtime; use function gettype; @@ -31,7 +33,6 @@ use function unserialize; use const DIRECTORY_SEPARATOR; -use const E_USER_ERROR; use const JSON_THROW_ON_ERROR; use const LOCK_EX; use const PHP_VERSION_ID; @@ -348,7 +349,7 @@ public function deleteOldParserCaches(float $time_before): int private function getParserCacheKey(string $file_path): string { - if (PHP_VERSION_ID >= 80100) { + if (PHP_VERSION_ID >= 8_01_00) { $hash = hash('xxh128', $file_path); } else { $hash = hash('md4', $file_path); diff --git a/src/Psalm/Internal/Provider/Providers.php b/src/Psalm/Internal/Provider/Providers.php index f8392f88242..864dd92a17f 100644 --- a/src/Psalm/Internal/Provider/Providers.php +++ b/src/Psalm/Internal/Provider/Providers.php @@ -90,7 +90,7 @@ public static function safeFileGetContents(string $path): string break; } $max_wait_cycles--; - usleep(50000); + usleep(50_000); } if (!$has_lock) { diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php index 5942a356976..3f9643448ea 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php @@ -21,6 +21,7 @@ use function array_filter; use function assert; use function count; +use function get_class; use function in_array; use function max; use function min; diff --git a/src/Psalm/Internal/Provider/StatementsProvider.php b/src/Psalm/Internal/Provider/StatementsProvider.php index e1c19fe232a..6094a952726 100644 --- a/src/Psalm/Internal/Provider/StatementsProvider.php +++ b/src/Psalm/Internal/Provider/StatementsProvider.php @@ -139,7 +139,7 @@ public function getStatementsForFile( $config = Config::getInstance(); - if (PHP_VERSION_ID >= 80100) { + if (PHP_VERSION_ID >= 8_01_00) { $file_content_hash = hash('xxh128', $version . $file_contents); } else { $file_content_hash = hash('md4', $version . $file_contents); diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index 6d25b1f40c2..b4657bfc08a 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -1168,7 +1168,16 @@ private static function scrapeIntProperties( if ($type instanceof TLiteralInt) { if ($combination->ints !== null && count($combination->ints) < $literal_limit) { - $combination->ints[$type_key] = $type; + $contains_literal = false; + if (isset($combination->value_types['int']) + && $combination->value_types['int'] instanceof TIntRange + && $combination->value_types['int']->contains($type->value) + ) { + $contains_literal = true; + } + if (!$contains_literal) { + $combination->ints[$type_key] = $type; + } } else { $combination->ints[$type_key] = $type; @@ -1177,20 +1186,20 @@ private static function scrapeIntProperties( static fn($int): bool => $int->value < 0 ); - if (isset($combination->value_types['int'])) { + if (isset($combination->value_types['int']) + && $combination->value_types['int'] instanceof TIntRange + ) { $current_int_type = $combination->value_types['int']; - if ($current_int_type instanceof TIntRange) { - foreach ($combination->ints as $int) { - if (!$current_int_type->contains($int->value)) { - $current_int_type->min_bound = TIntRange::getNewLowestBound( - $current_int_type->min_bound, - $int->value - ); - $current_int_type->max_bound = TIntRange::getNewHighestBound( - $current_int_type->max_bound, - $int->value - ); - } + foreach ($combination->ints as $int) { + if (!$current_int_type->contains($int->value)) { + $current_int_type->min_bound = TIntRange::getNewLowestBound( + $current_int_type->min_bound, + $int->value + ); + $current_int_type->max_bound = TIntRange::getNewHighestBound( + $current_int_type->max_bound, + $int->value + ); } } } @@ -1213,34 +1222,40 @@ private static function scrapeIntProperties( ) { $combination->value_types['int'] = new TInt(); } + $combination->ints = null; } elseif ($type instanceof TIntRange) { $type = clone $type; if ($combination->ints) { - foreach ($combination->ints as $int) { - if (!$type->contains($int->value)) { - $type->min_bound = TIntRange::getNewLowestBound($type->min_bound, $int->value); - $type->max_bound = TIntRange::getNewHighestBound($type->max_bound, $int->value); + foreach ($combination->ints as $k => $int) { + if ($type->contains($int->value)) { + unset($combination->ints[$k]); + } elseif ($type->max_bound === $int->value-1) { + $type->max_bound++; + unset($combination->ints[$k]); + } elseif ($type->min_bound === $int->value+1) { + $type->min_bound--; + unset($combination->ints[$k]); } } $combination->value_types['int'] = $type; - } elseif (!isset($combination->value_types['int'])) { - $combination->value_types['int'] = $type; - } else { + } elseif (isset($combination->value_types['int'])) { $old_type = $combination->value_types['int']; if ($old_type instanceof TIntRange) { $type->min_bound = TIntRange::getNewLowestBound($old_type->min_bound, $type->min_bound); $type->max_bound = TIntRange::getNewHighestBound($old_type->max_bound, $type->max_bound); } else { $type = new TInt(); + $combination->ints = null; } $combination->value_types['int'] = $type; + } else { + $combination->value_types['int'] = $type; } } else { $combination->value_types['int'] = $type; + $combination->ints = null; } - - $combination->ints = null; } } diff --git a/src/Psalm/Type/Reconciler.php b/src/Psalm/Type/Reconciler.php index 19b706bcb62..9e6910391f2 100644 --- a/src/Psalm/Type/Reconciler.php +++ b/src/Psalm/Type/Reconciler.php @@ -549,6 +549,8 @@ public static function breakUpPathIntoParts(string $path): array $escape_char = !$escape_char; } + // TODO: Add support for disjoint int ranges in offset checks + /** @psalm-suppress PossiblyUndefinedIntArrayOffset */ $parts[$parts_offset] .= $char; continue; }