From 4ed0fe934f3902ab8d7a3e26bcdd41d9ea3cf6eb Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Wed, 6 Dec 2023 14:12:19 +0100 Subject: [PATCH 1/5] Fix shaped array class string key combination --- .../Assignment/ArrayAssignmentAnalyzer.php | 4 +++- src/Psalm/Internal/Type/TypeCombination.php | 3 +++ src/Psalm/Internal/Type/TypeCombiner.php | 12 +++++++++++- src/Psalm/Type/Reconciler.php | 4 +++- tests/ArrayAssignmentTest.php | 18 ++++++++++++++++++ 5 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php index 576fe45a011..01150233a66 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php @@ -351,7 +351,9 @@ private static function updateTypeWithKeyValues( if (!$has_matching_objectlike_property && !$has_matching_string) { $properties = []; $classStrings = []; - $current_type = $current_type->setPossiblyUndefined(count($key_values) > 1); + $current_type = $current_type->setPossiblyUndefined( + $current_type->possibly_undefined || count($key_values) > 1, + ); foreach ($key_values as $key_value) { $properties[$key_value->value] = $current_type; if ($key_value instanceof TLiteralClassString) { diff --git a/src/Psalm/Internal/Type/TypeCombination.php b/src/Psalm/Internal/Type/TypeCombination.php index 0706315d1ac..0e4cc225383 100644 --- a/src/Psalm/Internal/Type/TypeCombination.php +++ b/src/Psalm/Internal/Type/TypeCombination.php @@ -55,6 +55,9 @@ final class TypeCombination /** @var array */ public array $objectlike_entries = []; + /** @var array */ + public array $objectlike_class_string_keys = []; + public bool $objectlike_sealed = true; public ?Union $objectlike_key_type = null; diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index 3f841da4fa3..b48387acb8c 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -667,6 +667,7 @@ private static function scrapeTypeProperties( $has_defined_keys = false; + $class_strings = $type->class_strings ?? []; foreach ($type->properties as $candidate_property_name => $candidate_property_type) { $value_type = $combination->objectlike_entries[$candidate_property_name] ?? null; @@ -705,6 +706,15 @@ private static function scrapeTypeProperties( ); } + if (isset($combination->objectlike_class_string_keys[$candidate_property_name])) { + $combination->objectlike_class_string_keys[$candidate_property_name] = + $combination->objectlike_class_string_keys[$candidate_property_name] + && ($class_strings[$candidate_property_name] ?? false); + } else { + $combination->objectlike_class_string_keys[$candidate_property_name] = + ($class_strings[$candidate_property_name] ?? false); + } + unset($missing_entries[$candidate_property_name]); } @@ -1421,7 +1431,7 @@ private static function handleKeyedArrayEntries( } else { $objectlike = new TKeyedArray( $combination->objectlike_entries, - null, + $combination->objectlike_class_string_keys, $sealed || $fallback_key_type === null || $fallback_value_type === null ? null : [$fallback_key_type, $fallback_value_type], diff --git a/src/Psalm/Type/Reconciler.php b/src/Psalm/Type/Reconciler.php index f5e288639cf..baae339644d 100644 --- a/src/Psalm/Type/Reconciler.php +++ b/src/Psalm/Type/Reconciler.php @@ -1132,7 +1132,9 @@ private static function adjustTKeyedArrayType( $base_key = implode($key_parts); - $result_type = $result_type->setPossiblyUndefined(count($array_key_offsets) > 1); + $result_type = $result_type->setPossiblyUndefined( + $result_type->possibly_undefined || count($array_key_offsets) > 1, + ); foreach ($array_key_offsets as $array_key_offset) { if (isset($existing_types[$base_key]) && $array_key_offset !== false) { diff --git a/tests/ArrayAssignmentTest.php b/tests/ArrayAssignmentTest.php index 364b6def325..790f901b398 100644 --- a/tests/ArrayAssignmentTest.php +++ b/tests/ArrayAssignmentTest.php @@ -55,6 +55,24 @@ public function providerValidCodeParse(): iterable '$resultOpt===' => 'array{a?: true, b?: true}', ], ], + 'assignUnionOfLiteralsClassKeys' => [ + 'code' => ' $v) { + $vv = new $k; + }', + 'assertions' => [ + '$result===' => 'array{a::class: true, b::class: true}', + ], + ], 'genericArrayCreationWithSingleIntValue' => [ 'code' => ' Date: Wed, 6 Dec 2023 14:23:45 +0100 Subject: [PATCH 2/5] Fix --- src/Psalm/Internal/Type/TypeCombiner.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index b48387acb8c..2854b1cb25f 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -1431,7 +1431,7 @@ private static function handleKeyedArrayEntries( } else { $objectlike = new TKeyedArray( $combination->objectlike_entries, - $combination->objectlike_class_string_keys, + array_filter($combination->objectlike_class_string_keys), $sealed || $fallback_key_type === null || $fallback_value_type === null ? null : [$fallback_key_type, $fallback_value_type], From d3b7f3f0b4c27a3436dc3c5d25df56c1c28e5cc4 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Wed, 6 Dec 2023 14:47:24 +0100 Subject: [PATCH 3/5] Fix --- src/Psalm/Internal/Type/TypeCombination.php | 2 +- src/Psalm/Internal/Type/TypeCombiner.php | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Type/TypeCombination.php b/src/Psalm/Internal/Type/TypeCombination.php index 0e4cc225383..94e64793e8b 100644 --- a/src/Psalm/Internal/Type/TypeCombination.php +++ b/src/Psalm/Internal/Type/TypeCombination.php @@ -55,7 +55,7 @@ final class TypeCombination /** @var array */ public array $objectlike_entries = []; - /** @var array */ + /** @var array */ public array $objectlike_class_string_keys = []; public bool $objectlike_sealed = true; diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index 2854b1cb25f..773e3f71a47 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -706,6 +706,12 @@ private static function scrapeTypeProperties( ); } + unset($missing_entries[$candidate_property_name]); + + if (is_int($candidate_property_name)) { + continue; + } + if (isset($combination->objectlike_class_string_keys[$candidate_property_name])) { $combination->objectlike_class_string_keys[$candidate_property_name] = $combination->objectlike_class_string_keys[$candidate_property_name] @@ -714,8 +720,6 @@ private static function scrapeTypeProperties( $combination->objectlike_class_string_keys[$candidate_property_name] = ($class_strings[$candidate_property_name] ?? false); } - - unset($missing_entries[$candidate_property_name]); } if ($type->fallback_params) { From 76458e0b50d8871366b0f72c257562dafc592859 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Wed, 6 Dec 2023 14:52:54 +0100 Subject: [PATCH 4/5] Add test --- tests/ArrayAssignmentTest.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/ArrayAssignmentTest.php b/tests/ArrayAssignmentTest.php index 790f901b398..713756ad2a9 100644 --- a/tests/ArrayAssignmentTest.php +++ b/tests/ArrayAssignmentTest.php @@ -73,6 +73,20 @@ class b {} '$result===' => 'array{a::class: true, b::class: true}', ], ], + 'assignUnionOfLiteralsClassKeys2' => [ + 'code' => ' true]; + + foreach ([a::class, b::class] as $k) { + $result[$k] = true; + }', + 'assertions' => [ + '$result===' => 'array{a::class: true, b::class: true, c: true}', + ], + ], 'genericArrayCreationWithSingleIntValue' => [ 'code' => ' Date: Thu, 7 Dec 2023 13:04:59 +0100 Subject: [PATCH 5/5] Commit just first part of fix for now --- tests/ArrayAssignmentTest.php | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tests/ArrayAssignmentTest.php b/tests/ArrayAssignmentTest.php index 713756ad2a9..790f901b398 100644 --- a/tests/ArrayAssignmentTest.php +++ b/tests/ArrayAssignmentTest.php @@ -73,20 +73,6 @@ class b {} '$result===' => 'array{a::class: true, b::class: true}', ], ], - 'assignUnionOfLiteralsClassKeys2' => [ - 'code' => ' true]; - - foreach ([a::class, b::class] as $k) { - $result[$k] = true; - }', - 'assertions' => [ - '$result===' => 'array{a::class: true, b::class: true, c: true}', - ], - ], 'genericArrayCreationWithSingleIntValue' => [ 'code' => '