From 4e7ff473a5c0f0a07fa92ced1bfd78a86d3dd322 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Dec 2022 12:06:57 +0100 Subject: [PATCH 01/22] Fix #8940 --- src/Psalm/Internal/Type/TypeCombiner.php | 32 ++++++++++++++++++++++++ tests/ArgTest.php | 25 ++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index fe2a8f34dce..4e34282a2d3 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -718,6 +718,38 @@ private static function scrapeTypeProperties( ->setPossiblyUndefined(true); } + if ($type->fallback_params) { + foreach ($missing_entries as $k => $_) { + foreach ($type->fallback_params[1]->getAtomicTypes() as $t) { + if ($t instanceof TArrayKey) { + break; + } + if ($t instanceof TString && is_string($k)) { + break; + } + if ($t instanceof TInt && is_int($k)) { + if ($t instanceof TIntRange && !$t->contains($k)) { + continue; + } + break; + } + if ($t instanceof TLiteralInt && $k === $t->value) { + break; + } + if ($t instanceof TLiteralString && $k === $t->value) { + break; + } + continue 2; + } + $combination->objectlike_entries[$k] = Type::combineUnionTypes( + $combination->objectlike_entries[$k], + $type->fallback_params[1], + $codebase, + $overwrite_empty_array, + ); + } + } + if (!$type->is_list) { $combination->all_arrays_lists = false; } elseif ($combination->all_arrays_lists !== false) { diff --git a/tests/ArgTest.php b/tests/ArgTest.php index fd2f3afe892..9d3361daac6 100644 --- a/tests/ArgTest.php +++ b/tests/ArgTest.php @@ -15,6 +15,31 @@ class ArgTest extends TestCase public function providerValidCodeParse(): iterable { return [ + 'arrayCombine' => [ + 'code' => ' + */ + function ret() { + return [1, 1, 1]; + } + ' + ], + 'arrayCombine2' => [ + 'code' => ' + */ + function ret() { + return [1, 1, 1]; + } + + $result = ret(); + ', + 'assertions' => [ + '$result===' => 'array{0?: 1, test1?: 0, test2?: 0, ..., 1>} ' + ] + ], 'argumentUnpackingLiteral' => [ 'code' => ' Date: Mon, 19 Dec 2022 12:09:07 +0100 Subject: [PATCH 02/22] cs-fix --- src/Psalm/Internal/Type/TypeCombiner.php | 1 + tests/ArgTest.php | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index 4e34282a2d3..f94b509a4e6 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -66,6 +66,7 @@ use function get_class; use function is_int; use function is_numeric; +use function is_string; use function min; use function strpos; use function strtolower; diff --git a/tests/ArgTest.php b/tests/ArgTest.php index 9d3361daac6..3b7f41a799f 100644 --- a/tests/ArgTest.php +++ b/tests/ArgTest.php @@ -23,7 +23,7 @@ public function providerValidCodeParse(): iterable function ret() { return [1, 1, 1]; } - ' + ', ], 'arrayCombine2' => [ 'code' => ' [ - '$result===' => 'array{0?: 1, test1?: 0, test2?: 0, ..., 1>} ' - ] + '$result===' => 'array{0?: 1, test1?: 0, test2?: 0, ..., 1>} ', + ], ], 'argumentUnpackingLiteral' => [ 'code' => ' Date: Mon, 19 Dec 2022 12:29:27 +0100 Subject: [PATCH 03/22] Fixes --- src/Psalm/Internal/Type/TypeCombination.php | 37 ++++++++++++ src/Psalm/Internal/Type/TypeCombiner.php | 67 +++++++++------------ tests/ArgTest.php | 25 -------- tests/ReturnTypeTest.php | 51 ++++++++++++++++ 4 files changed, 117 insertions(+), 63 deletions(-) diff --git a/src/Psalm/Internal/Type/TypeCombination.php b/src/Psalm/Internal/Type/TypeCombination.php index 8531377860d..863ae77f932 100644 --- a/src/Psalm/Internal/Type/TypeCombination.php +++ b/src/Psalm/Internal/Type/TypeCombination.php @@ -3,15 +3,22 @@ namespace Psalm\Internal\Type; use Psalm\Type\Atomic; +use Psalm\Type\Atomic\TArrayKey; +use Psalm\Type\Atomic\TInt; +use Psalm\Type\Atomic\TIntRange; use Psalm\Type\Atomic\TIterable; use Psalm\Type\Atomic\TLiteralFloat; use Psalm\Type\Atomic\TLiteralInt; use Psalm\Type\Atomic\TLiteralString; use Psalm\Type\Atomic\TNamedObject; use Psalm\Type\Atomic\TObject; +use Psalm\Type\Atomic\TString; use Psalm\Type\Atomic\TTemplateParam; use Psalm\Type\Union; +use function is_int; +use function is_string; + /** * @internal */ @@ -88,4 +95,34 @@ class TypeCombination /** @var array */ public array $class_string_map_as_types = []; + + /** + * @psalm-assert-if-true !null $this->objectlike_key_type + * @psalm-assert-if-true !null $this->objectlike_value_type + * @param array-key $k + */ + public function fallbackKeyContains($k): bool + { + if (!$this->objectlike_key_type) { + return false; + } + foreach ($this->objectlike_key_type->getAtomicTypes() as $t) { + if ($t instanceof TArrayKey) { + return true; + } elseif ($t instanceof TLiteralInt || $t instanceof TLiteralString) { + if ($t->value === $k) { + return true; + } + } elseif ($t instanceof TIntRange) { + if (is_int($k) && $t->contains($k)) { + return true; + } + } elseif ($t instanceof TString && is_string($k)) { + return true; + } elseif ($t instanceof TInt && is_int($k)) { + return true; + } + } + return false; + } } diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index f94b509a4e6..5056c18dcdf 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -66,7 +66,6 @@ use function get_class; use function is_int; use function is_numeric; -use function is_string; use function min; use function strpos; use function strtolower; @@ -650,26 +649,12 @@ private static function scrapeTypeProperties( $combination->objectlike_sealed = $combination->objectlike_sealed && $type->fallback_params === null; - if ($type->fallback_params) { - $combination->objectlike_key_type = Type::combineUnionTypes( - $type->fallback_params[0], - $combination->objectlike_key_type, - $codebase, - $overwrite_empty_array, - ); - $combination->objectlike_value_type = Type::combineUnionTypes( - $type->fallback_params[1], - $combination->objectlike_value_type, - $codebase, - $overwrite_empty_array, - ); - } - $has_defined_keys = false; foreach ($type->properties as $candidate_property_name => $candidate_property_type) { $value_type = $combination->objectlike_entries[$candidate_property_name] ?? null; + if (!$value_type) { $combination->objectlike_entries[$candidate_property_name] = $candidate_property_type ->setPossiblyUndefined($existing_objectlike_entries @@ -693,9 +678,33 @@ private static function scrapeTypeProperties( $has_defined_keys = true; } + if ($combination->fallbackKeyContains($candidate_property_name)) { + $combination->objectlike_entries[$candidate_property_name] = Type::combineUnionTypes( + $combination->objectlike_entries[$candidate_property_name], + $combination->objectlike_value_type, + $codebase, + $overwrite_empty_array, + ); + } + unset($missing_entries[$candidate_property_name]); } + if ($type->fallback_params) { + $combination->objectlike_key_type = Type::combineUnionTypes( + $type->fallback_params[0], + $combination->objectlike_key_type, + $codebase, + $overwrite_empty_array, + ); + $combination->objectlike_value_type = Type::combineUnionTypes( + $type->fallback_params[1], + $combination->objectlike_value_type, + $codebase, + $overwrite_empty_array, + ); + } + if (!$has_defined_keys) { $combination->array_always_filled = false; } @@ -719,32 +728,14 @@ private static function scrapeTypeProperties( ->setPossiblyUndefined(true); } - if ($type->fallback_params) { + if ($combination->objectlike_value_type) { foreach ($missing_entries as $k => $_) { - foreach ($type->fallback_params[1]->getAtomicTypes() as $t) { - if ($t instanceof TArrayKey) { - break; - } - if ($t instanceof TString && is_string($k)) { - break; - } - if ($t instanceof TInt && is_int($k)) { - if ($t instanceof TIntRange && !$t->contains($k)) { - continue; - } - break; - } - if ($t instanceof TLiteralInt && $k === $t->value) { - break; - } - if ($t instanceof TLiteralString && $k === $t->value) { - break; - } - continue 2; + if (!$combination->fallbackKeyContains($k)) { + continue; } $combination->objectlike_entries[$k] = Type::combineUnionTypes( $combination->objectlike_entries[$k], - $type->fallback_params[1], + $combination->objectlike_value_type, $codebase, $overwrite_empty_array, ); diff --git a/tests/ArgTest.php b/tests/ArgTest.php index 3b7f41a799f..fd2f3afe892 100644 --- a/tests/ArgTest.php +++ b/tests/ArgTest.php @@ -15,31 +15,6 @@ class ArgTest extends TestCase public function providerValidCodeParse(): iterable { return [ - 'arrayCombine' => [ - 'code' => ' - */ - function ret() { - return [1, 1, 1]; - } - ', - ], - 'arrayCombine2' => [ - 'code' => ' - */ - function ret() { - return [1, 1, 1]; - } - - $result = ret(); - ', - 'assertions' => [ - '$result===' => 'array{0?: 1, test1?: 0, test2?: 0, ..., 1>} ', - ], - ], 'argumentUnpackingLiteral' => [ 'code' => ' [ + 'code' => ' + */ + function ret() { + return [new a, new a, new a]; + } + + $result = ret(); + ', + 'assertions' => [ + '$result===' => 'list{0?: 0|a, 1?: 0|a, ..., a>}', + ], + ], + 'arrayCombineInv' => [ + 'code' => '|list{0, 0} + */ + function ret() { + return [new a, new a, new a]; + } + + $result = ret(); + ', + 'assertions' => [ + '$result===' => 'list{0?: 0|a, 1?: 0|a, ..., a>}', + ], + ], + 'arrayCombine2' => [ + 'code' => ' + */ + function ret() { + return [1, 1, 1]; + } + + $result = ret(); + ', + 'assertions' => [ + '$result===' => 'array{0?: a, test1?: 0, test2?: 0, ..., a>} ', + ], + ], 'returnTypeAfterUselessNullCheck' => [ 'code' => ' Date: Mon, 19 Dec 2022 12:55:32 +0100 Subject: [PATCH 04/22] Begin fixing #8942 --- .../Assignment/ArrayAssignmentAnalyzer.php | 19 +++++----- src/Psalm/Type/Atomic/TKeyedArray.php | 26 +++++++++++++ tests/ArrayAccessTest.php | 38 +++++++++++++++++++ 3 files changed, 74 insertions(+), 9 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php index 1b64ce3805f..f1890d3ded1 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php @@ -650,17 +650,18 @@ private static function updateArrayAssignmentChildType( } else { assert($array_atomic_type_list !== null); $array_atomic_type = new TKeyedArray( - array_fill( + [...array_fill( + $atomic_root_type_array->getMinCount(), + count($atomic_root_type_array->properties)-1, + $array_atomic_type_list + ), ...array_fill( 0, - count($atomic_root_type_array->properties), - $array_atomic_type_list, - ), + count($atomic_root_type_array->properties)-1, + Type::getNever() + )], null, - [ - Type::getListKey(), - $array_atomic_type_list, - ], - true, + null, + true ); } $from_countable_object_like = true; diff --git a/src/Psalm/Type/Atomic/TKeyedArray.php b/src/Psalm/Type/Atomic/TKeyedArray.php index df48a09e3f6..11a1ccf4a89 100644 --- a/src/Psalm/Type/Atomic/TKeyedArray.php +++ b/src/Psalm/Type/Atomic/TKeyedArray.php @@ -85,6 +85,19 @@ public function __construct( $this->fallback_params = $fallback_params; $this->is_list = $is_list; $this->from_docblock = $from_docblock; + if ($this->is_list) { + $last_k = -1; + $had_possibly_undefined = false; + ksort($this->properties); + foreach ($this->properties as $k => $v) { + if (is_string($k) || $last_k !== ($k-1) || ($had_possibly_undefined && !$v->possibly_undefined)) { + $this->is_list = false; + break; + } + $had_possibly_undefined = $v->possibly_undefined || $had_possibly_undefined; + $last_k = $k; + } + } } /** @@ -98,6 +111,19 @@ public function setProperties(array $properties): self } $cloned = clone $this; $cloned->properties = $properties; + if ($cloned->is_list) { + $last_k = -1; + $had_possibly_undefined = false; + ksort($cloned->properties); + foreach ($cloned->properties as $k => $v) { + if (is_string($k) || $last_k !== ($k-1) || ($had_possibly_undefined && !$v->possibly_undefined)) { + $cloned->is_list = false; + break; + } + $had_possibly_undefined = $v->possibly_undefined || $had_possibly_undefined; + $last_k = $k; + } + } return $cloned; } diff --git a/tests/ArrayAccessTest.php b/tests/ArrayAccessTest.php index 2ea42045234..7538885ef76 100644 --- a/tests/ArrayAccessTest.php +++ b/tests/ArrayAccessTest.php @@ -433,6 +433,44 @@ function foo(array $a, int $b): void { public function providerValidCodeParse(): iterable { return [ + 'testBuildList' => [ + 'code' => ' [ + '$pre===' => 'list{0?: 0|1, 1?: 1}', + '$a===' => 'list{0: 0|1|2, 1?: 1|2, 2?: 2}', + ] + ], + 'testBuildListOther' => [ + 'code' => ' [ + '$list===' => "list{0: 'A'|'B'|'C', 1?: 'C'}" + ] + ], 'instanceOfStringOffset' => [ 'code' => ' Date: Mon, 19 Dec 2022 13:04:24 +0100 Subject: [PATCH 05/22] Fix some tests --- .../Expression/Assignment/ArrayAssignmentAnalyzer.php | 6 +++--- src/Psalm/Type/Atomic/TKeyedArray.php | 1 + tests/ArrayAccessTest.php | 6 +++--- tests/ReturnTypeTest.php | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php index f1890d3ded1..7c3c8011997 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php @@ -653,15 +653,15 @@ private static function updateArrayAssignmentChildType( [...array_fill( $atomic_root_type_array->getMinCount(), count($atomic_root_type_array->properties)-1, - $array_atomic_type_list + $array_atomic_type_list, ), ...array_fill( 0, count($atomic_root_type_array->properties)-1, - Type::getNever() + Type::getNever(), )], null, null, - true + true, ); } $from_countable_object_like = true; diff --git a/src/Psalm/Type/Atomic/TKeyedArray.php b/src/Psalm/Type/Atomic/TKeyedArray.php index 11a1ccf4a89..f673af9d0c9 100644 --- a/src/Psalm/Type/Atomic/TKeyedArray.php +++ b/src/Psalm/Type/Atomic/TKeyedArray.php @@ -25,6 +25,7 @@ use function implode; use function is_int; use function is_string; +use function ksort; use function preg_match; use function sort; use function str_replace; diff --git a/tests/ArrayAccessTest.php b/tests/ArrayAccessTest.php index 7538885ef76..e4fba8b6a9e 100644 --- a/tests/ArrayAccessTest.php +++ b/tests/ArrayAccessTest.php @@ -453,7 +453,7 @@ public function providerValidCodeParse(): iterable 'assertions' => [ '$pre===' => 'list{0?: 0|1, 1?: 1}', '$a===' => 'list{0: 0|1|2, 1?: 1|2, 2?: 2}', - ] + ], ], 'testBuildListOther' => [ 'code' => ' [ - '$list===' => "list{0: 'A'|'B'|'C', 1?: 'C'}" - ] + '$list===' => "list{0: 'A'|'B'|'C', 1?: 'C'}", + ], ], 'instanceOfStringOffset' => [ 'code' => ' */ function ret() { - return [1, 1, 1]; + return [new a, new a, new a]; } $result = ret(); From 8088f046cd558be2457cc989646357873ba3a024 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Dec 2022 13:10:17 +0100 Subject: [PATCH 06/22] Fix --- tests/ReturnTypeTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ReturnTypeTest.php b/tests/ReturnTypeTest.php index c8a0d44d216..8097202ea73 100644 --- a/tests/ReturnTypeTest.php +++ b/tests/ReturnTypeTest.php @@ -63,7 +63,7 @@ function ret() { $result = ret(); ', 'assertions' => [ - '$result===' => 'array{0?: a, test1?: 0, test2?: 0, ..., a>} ', + '$result===' => 'array{0?: a, test1?: 0, test2?: 0, ..., a>}', ], ], 'returnTypeAfterUselessNullCheck' => [ From 326ba91812a87da1e908e6f3a2a78b91cab21c72 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Dec 2022 13:49:42 +0100 Subject: [PATCH 07/22] Update --- .../ArrayColumnReturnTypeProvider.php | 10 ++++++++-- tests/ArrayFunctionCallTest.php | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php index fa3dd6e62ae..391928762cd 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php @@ -89,9 +89,14 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $properties = []; $ok = true; $last_custom_key = -1; - $is_list = $input_array->is_list || $key_column_name !== null; + $is_list = true; $had_possibly_undefined = false; - foreach ($input_array->properties as $key => $property) { + + // This incorrectly assumes that the array is sorted, may be problematic + // Will be fixed when order is enforced + $incr_key = -1; + foreach ($input_array->properties as $property) { + ++$incr_key; $row_shape = self::getRowShape( $property, $statements_source, @@ -122,6 +127,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $result_element_type = $property; } + $key = $incr_key; if ($key_column_name !== null) { if (isset($row_shape->properties[$key_column_name])) { $result_key_type = $row_shape->properties[$key_column_name]; diff --git a/tests/ArrayFunctionCallTest.php b/tests/ArrayFunctionCallTest.php index 5ccaa5e24b8..7d739413bb1 100644 --- a/tests/ArrayFunctionCallTest.php +++ b/tests/ArrayFunctionCallTest.php @@ -1600,6 +1600,8 @@ function makeKeyedArray(): array { return []; } /** @var array{a: array{v: "a", k: 0}, b: array{v: "b", k: 1}, c?: array{v: "c", k: 2}} */ $aa = []; $k = array_column($aa, null, "k"); + + $l = array_column(["test" => ["v" => "a"], "test2" => ["v" => "b"]], "v"); ', 'assertions' => [ '$a===' => "list{'a', 'b', 'c', 'd'}", @@ -1613,6 +1615,7 @@ function makeKeyedArray(): array { return []; } '$i===' => "array{a: 0, b?: 1}", '$j===' => "array{0: array{k: 0, v: 'a'}, 1?: array{k: 1, v: 'b'}, 2: array{k: 2, v: 'c'}}", '$k===' => "list{0: array{k: 0, v: 'a'}, 1: array{k: 1, v: 'b'}, 2?: array{k: 2, v: 'c'}}", + '$l===' => "list{'a', 'b'}", ], ], 'splatArrayIntersect' => [ From e923229204765304edb334090fbfcff84141ffeb Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Dec 2022 14:01:00 +0100 Subject: [PATCH 08/22] Fix array_merge --- .../ArrayMergeReturnTypeProvider.php | 1 - tests/ArrayFunctionCallTest.php | 11 +++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php index d9873f0925d..394988baee1 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php @@ -149,7 +149,6 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev if (!isset($generic_properties[$key]) || ( !$type->possibly_undefined && !$unpacking_possibly_empty - && $is_replace )) { if ($unpacking_possibly_empty) { $type = $type->setPossiblyUndefined(true); diff --git a/tests/ArrayFunctionCallTest.php b/tests/ArrayFunctionCallTest.php index 7d739413bb1..a9ed7df4663 100644 --- a/tests/ArrayFunctionCallTest.php +++ b/tests/ArrayFunctionCallTest.php @@ -211,6 +211,17 @@ function getInts(): array { return [123]; } 'ignored_issues' => [], 'php_version' => '8.0', ], + 'arrayMergeOverWrite' => [ + 'code' => ' "a1"]; + $a2 = ["a" => "a2"]; + + $result = array_merge($a1, $a2); + ', + 'assertions' => [ + '$result===' => "array{a: 'a2'}" + ] + ], 'arrayMergeListOfShapes' => [ 'code' => ' Date: Mon, 19 Dec 2022 14:27:41 +0100 Subject: [PATCH 09/22] Update --- .../ReturnTypeProvider/ArrayColumnReturnTypeProvider.php | 7 ++++--- tests/ArrayFunctionCallTest.php | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php index 391928762cd..dbb3f3dfb36 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php @@ -94,9 +94,8 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev // This incorrectly assumes that the array is sorted, may be problematic // Will be fixed when order is enforced - $incr_key = -1; + $key = -1; foreach ($input_array->properties as $property) { - ++$incr_key; $row_shape = self::getRowShape( $property, $statements_source, @@ -127,7 +126,6 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $result_element_type = $property; } - $key = $incr_key; if ($key_column_name !== null) { if (isset($row_shape->properties[$key_column_name])) { $result_key_type = $row_shape->properties[$key_column_name]; @@ -148,6 +146,9 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $ok = false; break; } + } else { + /** @psalm-suppress StringIncrement Actually always an int in this branch */ + ++$key; } $properties[$key] = $result_element_type->setPossiblyUndefined( diff --git a/tests/ArrayFunctionCallTest.php b/tests/ArrayFunctionCallTest.php index a9ed7df4663..bbd6421afe6 100644 --- a/tests/ArrayFunctionCallTest.php +++ b/tests/ArrayFunctionCallTest.php @@ -1623,7 +1623,7 @@ function makeKeyedArray(): array { return []; } '$f===' => "array{0: 'd', 1: 'c', 2: 'b', 3: 'a'}", '$g===' => "list{array{k: 0, v: 'a'}, array{k: 1, v: 'b'}, array{k: 2, v: 'c'}, array{k: 3, v: 'd'}}", '$h===' => "list{array{k: 0}, array{k: 1}, array{k: 2}}", - '$i===' => "array{a: 0, b?: 1}", + '$i===' => "list{0: 0, 1?: 1}", '$j===' => "array{0: array{k: 0, v: 'a'}, 1?: array{k: 1, v: 'b'}, 2: array{k: 2, v: 'c'}}", '$k===' => "list{0: array{k: 0, v: 'a'}, 1: array{k: 1, v: 'b'}, 2?: array{k: 2, v: 'c'}}", '$l===' => "list{'a', 'b'}", From b9424a88cb6041c3f545ffc9a43ee46bd7067052 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Dec 2022 17:22:00 +0100 Subject: [PATCH 10/22] Update --- .../Assignment/ArrayAssignmentAnalyzer.php | 4 ---- .../Expression/Fetch/ArrayFetchAnalyzer.php | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php index 7c3c8011997..61754028fe5 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php @@ -654,10 +654,6 @@ private static function updateArrayAssignmentChildType( $atomic_root_type_array->getMinCount(), count($atomic_root_type_array->properties)-1, $array_atomic_type_list, - ), ...array_fill( - 0, - count($atomic_root_type_array->properties)-1, - Type::getNever(), )], null, null, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index 835fb722ac2..ce30c345b1b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -1533,6 +1533,21 @@ private static function handleArrayAccessOnKeyedArray( $properties[$key_value->value] ?? null, $replacement_type, ); + if (is_int($key_value->value) + && !$stmt->dim + && $type->is_list + && $type->properties[$key_value->value-1]->possibly_undefined + ) { + for ($x = 0; $x < $key_value->value; $x++) { + $properties[$x] = Type::combineUnionTypes( + $properties[$x], + $replacement_type, + ); + } + $properties[$key_value->value] = $properties[$key_value->value]-> + setPossiblyUndefined(true) + ; + } } $array_access_type = Type::combineUnionTypes( From ce06f4e0d060c5e6bcdf93f39a259dbca924c642 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Dec 2022 17:27:49 +0100 Subject: [PATCH 11/22] Fix --- .../Statements/Expression/Fetch/ArrayFetchAnalyzer.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index ce30c345b1b..42e5ab1d710 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -1544,6 +1544,9 @@ private static function handleArrayAccessOnKeyedArray( $replacement_type, ); } + $properties[0] = $properties[0]->setPossiblyUndefined( + $replacement_type->possibly_undefined + ); $properties[$key_value->value] = $properties[$key_value->value]-> setPossiblyUndefined(true) ; From 2617b68ca9450609846b86c9759b8d21a2faaa43 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Dec 2022 17:40:45 +0100 Subject: [PATCH 12/22] Simplify --- .../Expression/Assignment/ArrayAssignmentAnalyzer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php index 61754028fe5..172b8fb5563 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php @@ -650,11 +650,11 @@ private static function updateArrayAssignmentChildType( } else { assert($array_atomic_type_list !== null); $array_atomic_type = new TKeyedArray( - [...array_fill( + array_fill( $atomic_root_type_array->getMinCount(), count($atomic_root_type_array->properties)-1, $array_atomic_type_list, - )], + ), null, null, true, From 4ea5329087cba81a8964f0b15cfcb2fbc4d80d18 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Dec 2022 17:49:09 +0100 Subject: [PATCH 13/22] Never assign never to an array --- src/Psalm/Type/Reconciler.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Psalm/Type/Reconciler.php b/src/Psalm/Type/Reconciler.php index 9bf4d261d63..006850317e5 100644 --- a/src/Psalm/Type/Reconciler.php +++ b/src/Psalm/Type/Reconciler.php @@ -1093,6 +1093,10 @@ private static function adjustTKeyedArrayType( array &$changed_var_ids, Union $result_type ): void { + if ($result_type->isNever()) { + return; + } + array_pop($key_parts); $array_key = array_pop($key_parts); array_pop($key_parts); From ab2692fdd31d6431a21c6836ebde76d451dcf996 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Dec 2022 18:31:43 +0100 Subject: [PATCH 14/22] Update --- psalm-baseline.xml | 21 +++++++++---------- .../Assignment/ArrayAssignmentAnalyzer.php | 12 ++++++----- .../Expression/Fetch/ArrayFetchAnalyzer.php | 2 +- src/Psalm/Internal/Type/TypeCombiner.php | 4 +--- src/Psalm/Type/Atomic/TKeyedArray.php | 8 +++++-- src/Psalm/Type/Reconciler.php | 4 ---- tests/ArrayFunctionCallTest.php | 4 ++-- 7 files changed, 27 insertions(+), 28 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index c8dee018001..227420cf7b7 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $comment_block->tags['variablesfrom'][0] @@ -182,6 +182,9 @@ + + $properties[0] + $stmt_type $stmt_type @@ -231,6 +234,11 @@ $check_type_string + + + $options['tcp'] ?? null + + $identifier_name @@ -390,9 +398,6 @@ $class_strings ?: null - - $is_replace - @@ -461,17 +466,11 @@ - - $chars[$i - 1] - - - $type_tokens[$i - 1] - $type_tokens[$i - 1] + $type_tokens[$i - 1] $type_tokens[$i - 1] $type_tokens[$i - 1] $type_tokens[$i - 1] - $type_tokens[$i - 2] diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php index 172b8fb5563..4f11f44c7ab 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php @@ -649,12 +649,14 @@ private static function updateArrayAssignmentChildType( ]); } else { assert($array_atomic_type_list !== null); + $array_atomic_type = array_fill( + $atomic_root_type_array->getMinCount(), + count($atomic_root_type_array->properties)-1, + $array_atomic_type_list, + ); + assert(count($array_atomic_type) > 0); $array_atomic_type = new TKeyedArray( - array_fill( - $atomic_root_type_array->getMinCount(), - count($atomic_root_type_array->properties)-1, - $array_atomic_type_list, - ), + $array_atomic_type, null, null, true, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index 42e5ab1d710..d0d52d581a4 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -1545,7 +1545,7 @@ private static function handleArrayAccessOnKeyedArray( ); } $properties[0] = $properties[0]->setPossiblyUndefined( - $replacement_type->possibly_undefined + $replacement_type->possibly_undefined, ); $properties[$key_value->value] = $properties[$key_value->value]-> setPossiblyUndefined(true) diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index 5056c18dcdf..0132e4e6bf4 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -676,9 +676,7 @@ private static function scrapeTypeProperties( if (!$candidate_property_type->possibly_undefined) { $has_defined_keys = true; - } - - if ($combination->fallbackKeyContains($candidate_property_name)) { + } elseif ($combination->fallbackKeyContains($candidate_property_name)) { $combination->objectlike_entries[$candidate_property_name] = Type::combineUnionTypes( $combination->objectlike_entries[$candidate_property_name], $combination->objectlike_value_type, diff --git a/src/Psalm/Type/Atomic/TKeyedArray.php b/src/Psalm/Type/Atomic/TKeyedArray.php index f673af9d0c9..b78fa8654da 100644 --- a/src/Psalm/Type/Atomic/TKeyedArray.php +++ b/src/Psalm/Type/Atomic/TKeyedArray.php @@ -95,7 +95,9 @@ public function __construct( $this->is_list = false; break; } - $had_possibly_undefined = $v->possibly_undefined || $had_possibly_undefined; + if ($v->possibly_undefined) { + $had_possibly_undefined = true; + } $last_k = $k; } } @@ -121,7 +123,9 @@ public function setProperties(array $properties): self $cloned->is_list = false; break; } - $had_possibly_undefined = $v->possibly_undefined || $had_possibly_undefined; + if ($v->possibly_undefined) { + $had_possibly_undefined = true; + } $last_k = $k; } } diff --git a/src/Psalm/Type/Reconciler.php b/src/Psalm/Type/Reconciler.php index 006850317e5..9bf4d261d63 100644 --- a/src/Psalm/Type/Reconciler.php +++ b/src/Psalm/Type/Reconciler.php @@ -1093,10 +1093,6 @@ private static function adjustTKeyedArrayType( array &$changed_var_ids, Union $result_type ): void { - if ($result_type->isNever()) { - return; - } - array_pop($key_parts); $array_key = array_pop($key_parts); array_pop($key_parts); diff --git a/tests/ArrayFunctionCallTest.php b/tests/ArrayFunctionCallTest.php index bbd6421afe6..ae228a438bb 100644 --- a/tests/ArrayFunctionCallTest.php +++ b/tests/ArrayFunctionCallTest.php @@ -219,8 +219,8 @@ function getInts(): array { return [123]; } $result = array_merge($a1, $a2); ', 'assertions' => [ - '$result===' => "array{a: 'a2'}" - ] + '$result===' => "array{a: 'a2'}", + ], ], 'arrayMergeListOfShapes' => [ 'code' => ' Date: Mon, 19 Dec 2022 19:24:03 +0100 Subject: [PATCH 15/22] Fix tests --- src/Psalm/Internal/Type/TypeCombiner.php | 6 +++++- tests/TypeCombinationTest.php | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index 0132e4e6bf4..89abc13b52c 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -676,7 +676,11 @@ private static function scrapeTypeProperties( if (!$candidate_property_type->possibly_undefined) { $has_defined_keys = true; - } elseif ($combination->fallbackKeyContains($candidate_property_name)) { + } + + if (($candidate_property_type->possibly_undefined || ($value_type->possibly_undefined ?? true)) + && $combination->fallbackKeyContains($candidate_property_name) + ) { $combination->objectlike_entries[$candidate_property_name] = Type::combineUnionTypes( $combination->objectlike_entries[$candidate_property_name], $combination->objectlike_value_type, diff --git a/tests/TypeCombinationTest.php b/tests/TypeCombinationTest.php index 071b4b8d617..f116e3703a9 100644 --- a/tests/TypeCombinationTest.php +++ b/tests/TypeCombinationTest.php @@ -7,6 +7,8 @@ use Psalm\Type; use Psalm\Type\Atomic; +use function array_reverse; + class TypeCombinationTest extends TestCase { use ValidCodeAnalysisTestTrait; @@ -30,6 +32,11 @@ public function testValidTypeCombination(string $expected, array $types): void $expected, TypeCombiner::combine($converted_types)->getId(), ); + + $this->assertSame( + $expected, + TypeCombiner::combine(array_reverse($converted_types))->getId(), + ); } public function providerValidCodeParse(): iterable @@ -90,6 +97,20 @@ function expectsTraversableOrArray($_a): void public function providerTestValidTypeCombination(): array { return [ + 'complexArrayFallback1' => [ + 'array{other_references: list|null, taint_trace: list>|null, ...}', + [ + 'array{other_references: list|null, taint_trace: null}&array', + 'array{other_references: list|null, taint_trace: list>}&array', + ], + ], + 'complexArrayFallback2' => [ + 'list{0?: 0|a, 1?: 0|a, ..., a>}', + [ + 'list', + 'list{0, 0}', + ], + ], 'intOrString' => [ 'int|string', [ From 194f71052cc17f6c5b614638f2087d066be70e03 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Dec 2022 21:11:10 +0100 Subject: [PATCH 16/22] Add failing test --- tests/ArrayAccessTest.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/ArrayAccessTest.php b/tests/ArrayAccessTest.php index e4fba8b6a9e..58d58a24459 100644 --- a/tests/ArrayAccessTest.php +++ b/tests/ArrayAccessTest.php @@ -471,6 +471,21 @@ public function providerValidCodeParse(): iterable '$list===' => "list{0: 'A'|'B'|'C', 1?: 'C'}", ], ], + 'testBuildList3' => [ + 'code' => ' [ + '$list===' => "list{0: 0, 1: 1|2|3, 2?: 2|3, 3?: 3}", + ], + ], 'instanceOfStringOffset' => [ 'code' => ' Date: Mon, 19 Dec 2022 21:16:03 +0100 Subject: [PATCH 17/22] Fix --- .../Expression/Fetch/ArrayFetchAnalyzer.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index d0d52d581a4..34c39cb6a72 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -1538,15 +1538,20 @@ private static function handleArrayAccessOnKeyedArray( && $type->is_list && $type->properties[$key_value->value-1]->possibly_undefined ) { + $first = true; for ($x = 0; $x < $key_value->value; $x++) { + if (!$properties[$x]->possibly_undefined) { + continue; + } $properties[$x] = Type::combineUnionTypes( $properties[$x], $replacement_type, ); + if ($first) { + $first = false; + $properties[$x] = $properties[$x]->setPossiblyUndefined(true); + } } - $properties[0] = $properties[0]->setPossiblyUndefined( - $replacement_type->possibly_undefined, - ); $properties[$key_value->value] = $properties[$key_value->value]-> setPossiblyUndefined(true) ; From dc5a4576a57aae8ffccdce5aaddbbaab1375eafe Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Dec 2022 21:25:06 +0100 Subject: [PATCH 18/22] Fix --- .../Statements/Expression/Fetch/ArrayFetchAnalyzer.php | 5 ++++- tests/ArrayAccessTest.php | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index 34c39cb6a72..6c6f88fc360 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -1549,9 +1549,12 @@ private static function handleArrayAccessOnKeyedArray( ); if ($first) { $first = false; - $properties[$x] = $properties[$x]->setPossiblyUndefined(true); + $properties[$x] = $properties[$x]->setPossiblyUndefined(false); } } + if ($first) { + $properties[0] = $properties[0]->setPossiblyUndefined(false); + } $properties[$key_value->value] = $properties[$key_value->value]-> setPossiblyUndefined(true) ; diff --git a/tests/ArrayAccessTest.php b/tests/ArrayAccessTest.php index 58d58a24459..113458d2da8 100644 --- a/tests/ArrayAccessTest.php +++ b/tests/ArrayAccessTest.php @@ -483,7 +483,7 @@ public function providerValidCodeParse(): iterable $a []= 3; ', 'assertions' => [ - '$list===' => "list{0: 0, 1: 1|2|3, 2?: 2|3, 3?: 3}", + '$a===' => "list{0: 0, 1: 1|2|3, 2?: 2|3, 3?: 3}", ], ], 'instanceOfStringOffset' => [ From 750e2da542a062d74da56ede14d326cb4e746ad1 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Dec 2022 21:29:13 +0100 Subject: [PATCH 19/22] Cleanup --- .../Statements/Expression/Fetch/ArrayFetchAnalyzer.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index 6c6f88fc360..b252981f5b1 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -1552,9 +1552,6 @@ private static function handleArrayAccessOnKeyedArray( $properties[$x] = $properties[$x]->setPossiblyUndefined(false); } } - if ($first) { - $properties[0] = $properties[0]->setPossiblyUndefined(false); - } $properties[$key_value->value] = $properties[$key_value->value]-> setPossiblyUndefined(true) ; From d71e7af46af89ea23f804c56a488d0a8601f36d6 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Dec 2022 22:08:36 +0100 Subject: [PATCH 20/22] Update BC docs for future deprecation --- UPGRADING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index fcbe714b195..ec3723717d6 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -7,6 +7,8 @@ - [BC] The only optional boolean parameter of `TKeyedArray::getGenericArrayType` was removed, and was replaced with a string parameter with a different meaning. +- [BC] The `TDependentListKey` type was removed, replacing it with an optional property of the `TIntRange` type. + # Upgrading from Psalm 4 to Psalm 5 ## Changed From 8013b0eea250c299101ddc1ed9293ce1bf8d174f Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Dec 2022 22:14:32 +0100 Subject: [PATCH 21/22] Replace some LNumber usages --- UPGRADING.md | 2 +- .../Analyzer/Statements/Block/ForAnalyzer.php | 7 ++++--- .../Analyzer/Statements/Block/WhileAnalyzer.php | 3 ++- .../Statements/Expression/IncludeAnalyzer.php | 11 ++++++++++- .../ArrayRandReturnTypeProvider.php | 13 +++++++++---- .../ExplodeReturnTypeProvider.php | 5 +++-- 6 files changed, 29 insertions(+), 12 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index ec3723717d6..01f1a67ddda 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -7,7 +7,7 @@ - [BC] The only optional boolean parameter of `TKeyedArray::getGenericArrayType` was removed, and was replaced with a string parameter with a different meaning. -- [BC] The `TDependentListKey` type was removed, replacing it with an optional property of the `TIntRange` type. +- [BC] The `TDependentListKey` type was removed and replaced with an optional property of the `TIntRange` type. # Upgrading from Psalm 4 to Psalm 5 ## Changed diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/ForAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/ForAnalyzer.php index 598f0a305da..4c0dd4ee16f 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/ForAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/ForAnalyzer.php @@ -106,14 +106,15 @@ public static function analyze( if (count($stmt->init) === 1 && count($stmt->cond) === 1 && $cond instanceof PhpParser\Node\Expr\BinaryOp - && $cond->right instanceof PhpParser\Node\Scalar\LNumber + && ($cond_value = $statements_analyzer->node_data->getType($cond->right)) + && ($cond_value->isSingleIntLiteral() || $cond_value->isSingleStringLiteral()) && $cond->left instanceof PhpParser\Node\Expr\Variable && is_string($cond->left->name) && isset($init_var_types[$cond->left->name]) && $init_var_types[$cond->left->name]->isSingleIntLiteral() ) { - $init_value = $init_var_types[$cond->left->name]->getSingleIntLiteral()->value; - $cond_value = $cond->right->value; + $init_value = $init_var_types[$cond->left->name]->getSingleLiteral()->value; + $cond_value = $cond_value->getSingleLiteral()->value; if ($cond instanceof PhpParser\Node\Expr\BinaryOp\Smaller && $init_value < $cond_value) { $always_enters_loop = true; diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/WhileAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/WhileAnalyzer.php index e3a52f67e76..a1fd1707891 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/WhileAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/WhileAnalyzer.php @@ -27,7 +27,8 @@ public static function analyze( Context $context ): ?bool { $while_true = ($stmt->cond instanceof PhpParser\Node\Expr\ConstFetch && $stmt->cond->name->parts === ['true']) - || ($stmt->cond instanceof PhpParser\Node\Scalar\LNumber && $stmt->cond->value > 0); + || (($t = $statements_analyzer->node_data->getType($stmt->cond)) + && $t->isAlwaysTruthy()); $pre_context = null; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php index 2cae6ff6af9..0a7af6d594f 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php @@ -334,7 +334,16 @@ public static function getPathTo( if ($stmt->getArgs()[1]->value instanceof PhpParser\Node\Scalar\LNumber) { $dir_level = $stmt->getArgs()[1]->value->value; } else { - return null; + if ($statements_analyzer) { + $t = $statements_analyzer->node_data->getType($stmt->getArgs()[1]->value); + if ($t && $t->isSingleIntLiteral()) { + $dir_level = $t->getSingleIntLiteral()->value; + } else { + return null; + } + } else { + return null; + } } } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayRandReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayRandReturnTypeProvider.php index 56b478edf35..f4a6689051a 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayRandReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayRandReturnTypeProvider.php @@ -2,7 +2,6 @@ namespace Psalm\Internal\Provider\ReturnTypeProvider; -use PhpParser; use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent; use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface; @@ -54,15 +53,21 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $key_type = $first_arg_array->getGenericKeyType(); } - if (!$second_arg - || ($second_arg instanceof PhpParser\Node\Scalar\LNumber && $second_arg->value === 1) + if (!$second_arg) { + return $key_type; + } + + $second_arg_type = $statements_source->node_data->getType($second_arg); + if ($second_arg_type + && $second_arg_type->isSingleIntLiteral() + && $second_arg_type->getSingleIntLiteral()->value === 1 ) { return $key_type; } $arr_type = Type::getList($key_type); - if ($second_arg instanceof PhpParser\Node\Scalar\LNumber) { + if ($second_arg_type && $second_arg_type->isSingleIntLiteral()) { return $arr_type; } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php index 15512b6382a..f1d3363fd11 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php @@ -47,8 +47,9 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $can_return_empty = isset($call_args[2]) && ( - !$call_args[2]->value instanceof PhpParser\Node\Scalar\LNumber - || $call_args[2]->value->value < 0 + !($third_arg_type = $statements_source->node_data->getType($call_args[2]->value)) + || !$third_arg_type->isSingleIntLiteral() + || $third_arg_type->getSingleIntLiteral()->value < 0 ); if ($call_args[0]->value instanceof PhpParser\Node\Scalar\String_) { From 825f46f9dc55eb3c154f1ea120af78ca0dcc03ac Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Dec 2022 22:23:31 +0100 Subject: [PATCH 22/22] Fix test --- tests/ArrayFunctionCallTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ArrayFunctionCallTest.php b/tests/ArrayFunctionCallTest.php index ae228a438bb..05947471ff0 100644 --- a/tests/ArrayFunctionCallTest.php +++ b/tests/ArrayFunctionCallTest.php @@ -837,7 +837,7 @@ function foo($a) '$vars' => 'array{x: string, y: string}', '$c' => 'string', '$e' => 'list', - '$f' => 'list|string', + '$f' => 'list', ], ], 'arrayKeysNoEmpty' => [