From 344cca19376c3951c94c6d2fab09219d0079aee6 Mon Sep 17 00:00:00 2001 From: Matt Brown Date: Wed, 9 Nov 2022 20:21:52 -0500 Subject: [PATCH] Prevent array{a: Foo} going cleanly into array --- .../Statements/Block/ForeachAnalyzer.php | 2 +- .../Statements/Expression/ArrayAnalyzer.php | 1 - .../Statements/Expression/AssertionFinder.php | 2 +- .../Assignment/ArrayAssignmentAnalyzer.php | 5 +- .../Expression/AssignmentAnalyzer.php | 4 +- .../BinaryOp/ArithmeticOpAnalyzer.php | 37 ++++++++- .../Expression/Call/ArgumentsAnalyzer.php | 2 +- .../Call/FunctionCallReturnTypeFetcher.php | 6 +- .../Statements/Expression/CastAnalyzer.php | 2 +- .../Expression/Fetch/ArrayFetchAnalyzer.php | 61 +++++++------- .../Fetch/VariableFetchAnalyzer.php | 1 - .../Expression/SimpleTypeInferer.php | 1 - .../Analyzer/Statements/UnsetAnalyzer.php | 18 ++--- .../Codebase/ConstantTypeResolver.php | 11 ++- src/Psalm/Internal/Codebase/Methods.php | 2 +- .../ArrayFilterReturnTypeProvider.php | 7 +- .../ArrayMapReturnTypeProvider.php | 13 +-- .../ArrayMergeReturnTypeProvider.php | 8 +- .../ArrayPopReturnTypeProvider.php | 2 +- .../GetObjectVarsReturnTypeProvider.php | 3 +- .../ParseUrlReturnTypeProvider.php | 3 +- .../Internal/Type/AssertionReconciler.php | 2 - .../Type/Comparator/KeyedArrayComparator.php | 4 +- .../Type/SimpleAssertionReconciler.php | 15 ++-- .../Type/SimpleNegatedAssertionReconciler.php | 2 +- src/Psalm/Internal/Type/TypeCombiner.php | 51 ++++++------ src/Psalm/Internal/Type/TypeExpander.php | 4 +- src/Psalm/Internal/Type/TypeParser.php | 45 ++++++----- src/Psalm/Type/Atomic/TKeyedArray.php | 81 +++++++++---------- src/Psalm/Type/Reconciler.php | 22 +++-- tests/PropertiesOfTest.php | 6 +- .../ReturnTypeProvider/GetObjectVarsTest.php | 8 +- tests/Template/PropertiesOfTemplateTest.php | 18 ++--- tests/TypeReconciliation/ReconcilerTest.php | 4 +- 34 files changed, 235 insertions(+), 218 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php index e8a03340dd1..011bb2c45b8 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php @@ -470,7 +470,7 @@ public static function checkIteratorType( || $iterator_atomic_type instanceof TList ) { if ($iterator_atomic_type instanceof TKeyedArray) { - if ($iterator_atomic_type->sealed) { + if ($iterator_atomic_type->fallback_value_type === null) { $all_possibly_undefined = true; foreach ($iterator_atomic_type->properties as $prop) { if (!$prop->possibly_undefined) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php index 8c5d7c2d895..016d87b7a4e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php @@ -123,7 +123,6 @@ public static function analyze( $atomic_type = new TKeyedArray( $array_creation_info->property_types, $array_creation_info->class_strings, - $array_creation_info->can_create_objectlike, $array_creation_info->can_create_objectlike ? null : ($item_key_type ?? Type::getArrayKey()), $array_creation_info->can_create_objectlike ? null : ($item_value_type ?? Type::getMixed()), $array_creation_info->all_list diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php index f17b6cc5496..39dc6fd39bc 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php @@ -3606,7 +3606,7 @@ private static function getInarrayAssertions( $value_type = $atomic_type->type_param; } elseif ($atomic_type instanceof TKeyedArray) { $value_type = $atomic_type->getGenericValueType(); - $is_sealed = $atomic_type->sealed; + $is_sealed = $atomic_type->fallback_value_type === null; } else { $value_type = $atomic_type->type_params[1]; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php index acd3ca26f5e..4427dedc399 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php @@ -375,8 +375,7 @@ private static function updateTypeWithKeyValues( [$key_value->value => $current_type], $key_value instanceof TLiteralClassString ? [$key_value->value => true] - : null, - true + : null ); $array_assignment_type = new Union([ @@ -614,7 +613,7 @@ private static function updateArrayAssignmentChildType( /** @psalm-suppress InaccessibleProperty We just created this object */ $array_atomic_type->count = $atomic_root_types['array']->count; } elseif ($atomic_root_types['array'] instanceof TKeyedArray - && $atomic_root_types['array']->sealed + && $atomic_root_types['array']->fallback_value_type === null ) { /** @psalm-suppress InaccessibleProperty We just created this object */ $array_atomic_type->count = count($atomic_root_types['array']->properties); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php index 9155c2b1082..f19fea14844 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php @@ -1274,7 +1274,7 @@ private static function analyzeDestructuringAssignment( continue; } - if ($assign_value_atomic_type->sealed) { + if ($assign_value_atomic_type->fallback_value_type === null) { IssueBuffer::maybeAdd( new InvalidArrayOffset( 'Cannot access value with offset ' . $offset, @@ -1451,7 +1451,7 @@ private static function analyzeDestructuringAssignment( ); } - $can_be_empty = !$assign_value_atomic_type->sealed; + $can_be_empty = $assign_value_atomic_type->fallback_value_type !== null; } elseif ($assign_value_atomic_type->hasArrayAccessInterface($codebase)) { ForeachAnalyzer::getKeyValueParamsForTraversableObject( $assign_value_atomic_type, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php index 522c4b0ed5d..195cd19843c 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php @@ -576,16 +576,49 @@ private static function analyzeOperands( } } - if (!$left_type_part->sealed) { + if ($left_type_part->fallback_value_type !== null) { foreach ($definitely_existing_mixed_right_properties as $key => $type) { $properties[$key] = Type::combineUnionTypes(Type::getMixed(), $type); } } + if ($left_type_part->fallback_key_type === null + && $right_type_part->fallback_key_type === null + ) { + $fallback_key_type = null; + } elseif ($left_type_part->fallback_key_type !== null + && $right_type_part->fallback_key_type !== null + ) { + $fallback_key_type = Type::combineUnionTypes( + $left_type_part->fallback_key_type, + $right_type_part->fallback_key_type + ); + } else { + $fallback_key_type = $left_type_part->fallback_key_type + ?: $right_type_part->fallback_key_type; + } + + if ($left_type_part->fallback_value_type === null + && $right_type_part->fallback_value_type === null + ) { + $fallback_value_type = null; + } elseif ($left_type_part->fallback_value_type !== null + && $right_type_part->fallback_value_type !== null + ) { + $fallback_value_type = Type::combineUnionTypes( + $left_type_part->fallback_value_type, + $right_type_part->fallback_value_type + ); + } else { + $fallback_value_type = $left_type_part->fallback_value_type + ?: $right_type_part->fallback_value_type; + } + $new_keyed_array = new TKeyedArray( $properties, null, - $left_type_part->sealed && $right_type_part->sealed + $fallback_key_type, + $fallback_value_type ); $result_type_member = new Union([$new_keyed_array]); } else { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php index 22a63b7831e..68ac08d5d70 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php @@ -1713,7 +1713,7 @@ private static function checkArgCount( ) { $packed_var_definite_args_tmp[] = 2; } elseif ($atomic_arg_type instanceof TKeyedArray) { - if (!$atomic_arg_type->sealed) { + if ($atomic_arg_type->fallback_value_type !== null) { return; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php index 0498efc4ec8..88daec2217a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php @@ -329,7 +329,7 @@ private static function getReturnTypeFromCallMapWithArgs( $keyed_array = new TKeyedArray([ Type::getInt(), Type::getInt() - ], null, true, null, null, true); + ], null, null, null, true); return new Union([$keyed_array]); case 'get_called_class': @@ -401,7 +401,7 @@ private static function getReturnTypeFromCallMapWithArgs( } } - if ($atomic_types['array']->sealed) { + if ($atomic_types['array']->fallback_value_type === null) { //the KeyedArray is sealed, we can use the min and max if ($min === $max) { return new Union([new TLiteralInt($max)]); @@ -438,7 +438,7 @@ private static function getReturnTypeFromCallMapWithArgs( $keyed_array = new TKeyedArray([ Type::getInt(), Type::getInt() - ], null, true, null, null, true); + ], null, null, null, true); if ((string) $first_arg_type === 'false') { return new Union([$keyed_array]); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php index 42f8f912984..8cf6a5eaf8c 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -246,7 +246,7 @@ public static function analyze( foreach ($stmt_expr_type->getAtomicTypes() as $type) { if ($type instanceof Scalar) { - $keyed_array = new TKeyedArray([new Union([$type])], null, true, null, null, true); + $keyed_array = new TKeyedArray([new Union([$type])], null, null, null, true); $permissible_atomic_types[] = $keyed_array; } elseif ($type instanceof TNull) { $permissible_atomic_types[] = new TArray([Type::getNever(), Type::getNever()]); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index 453f45e23a8..97a40bf02f3 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -1126,7 +1126,7 @@ private static function handleArrayAccessOnArray( $single_atomic = $key_values[0]; $from_mixed_array = $type->type_params[1]->isMixed(); - [$previous_key_type, $previous_value_type] = $type->type_params; + [$fallback_key_type, $fallback_value_type] = $type->type_params; // ok, type becomes an TKeyedArray $type = new TKeyedArray( @@ -1136,17 +1136,16 @@ private static function handleArrayAccessOnArray( $single_atomic instanceof TLiteralClassString ? [ $single_atomic->value => true ] : null, - $from_empty_array, - $from_empty_array ? null : $previous_key_type, - $from_empty_array ? null : $previous_value_type, + $from_empty_array ? null : $fallback_key_type, + $from_empty_array ? null : $fallback_value_type, ); } elseif (!$stmt->dim && $from_empty_array && $replacement_type) { $type = new TNonEmptyList($replacement_type); return; } } elseif ($type instanceof TKeyedArray - && $type->previous_value_type - && $type->previous_value_type->isMixed() + && $type->fallback_value_type + && $type->fallback_value_type->isMixed() && count($key_values) === 1 ) { $properties = $type->properties; @@ -1525,7 +1524,7 @@ private static function handleArrayAccessOnKeyedArray( ): void { $generic_key_type = $type->getGenericKeyType(); - if (!$stmt->dim && $type->sealed && $type->is_list) { + if (!$stmt->dim && $type->fallback_value_type === null && $type->is_list) { $key_values[] = new TLiteralInt(count($type->properties)); } @@ -1553,7 +1552,7 @@ private static function handleArrayAccessOnKeyedArray( $array_access_type, $properties[$key_value->value] ); - } elseif ($type->previous_value_type) { + } elseif ($type->fallback_value_type) { if ($codebase->config->ensure_array_string_offsets_exist) { self::checkLiteralStringArrayOffset( $offset_type, @@ -1576,38 +1575,36 @@ private static function handleArrayAccessOnKeyedArray( ); } - $properties[$key_value->value] = $type->previous_value_type; + $properties[$key_value->value] = $type->fallback_value_type; - $array_access_type = $type->previous_value_type; + $array_access_type = $type->fallback_value_type; } elseif ($hasMixed) { $has_valid_offset = true; $array_access_type = Type::getMixed(); - } else { - if ($type->sealed || !$context->inside_isset) { - $object_like_keys = array_keys($properties); + } else { + $object_like_keys = array_keys($properties); - $last_key = array_pop($object_like_keys); + $last_key = array_pop($object_like_keys); - $key_string = ''; + $key_string = ''; - if ($object_like_keys) { - $formatted_keys = implode( - ', ', - array_map( - /** @param int|string $key */ - static fn($key): string => is_int($key) ? "$key" : '\'' . $key . '\'', - $object_like_keys - ) - ); + if ($object_like_keys) { + $formatted_keys = implode( + ', ', + array_map( + /** @param int|string $key */ + static fn($key): string => is_int($key) ? "$key" : '\'' . $key . '\'', + $object_like_keys + ) + ); - $key_string = $formatted_keys . ' or '; - } + $key_string = $formatted_keys . ' or '; + } - $key_string .= is_int($last_key) ? $last_key : '\'' . $last_key . '\''; + $key_string .= is_int($last_key) ? $last_key : '\'' . $last_key . '\''; - $expected_offset_types[] = $key_string; - } + $expected_offset_types[] = $key_string; $array_access_type = Type::getMixed(); } @@ -1657,7 +1654,9 @@ private static function handleArrayAccessOnKeyedArray( $offset_type->isMixed() ? Type::getArrayKey() : $offset_type->freeze() ); - $property_count = $type->sealed ? count($type->properties) : null; + $property_count = $type->fallback_value_type === null + ? count($type->properties) + : null; if (!$stmt->dim && $property_count) { ++$property_count; @@ -1690,7 +1689,7 @@ private static function handleArrayAccessOnKeyedArray( $has_valid_offset = true; } else { if (!$context->inside_isset - || ($type->sealed && !$union_comparison_results->type_coerced) + || ($type->fallback_value_type === null && !$union_comparison_results->type_coerced) ) { $expected_offset_types[] = $generic_key_type->getId(); } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index bcfab646aff..3336b0729c7 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -775,7 +775,6 @@ private static function getGlobalTypeInner(string $var_id, bool $files_full_path $detailed_type = new TKeyedArray( $arr, null, - false, Type::getNonEmptyString(), Type::getString() ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php index 929aa796253..15bfd58c87f 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php @@ -577,7 +577,6 @@ private static function inferArrayType( $objectlike = new TKeyedArray( $array_creation_info->property_types, $array_creation_info->class_strings, - true, null, null, $array_creation_info->all_list diff --git a/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php index c40a37ad3f0..8a09ae6ea1d 100644 --- a/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php @@ -83,13 +83,13 @@ public static function analyze( /** @psalm-suppress DocblockTypeContradiction https://github.com/vimeo/psalm/issues/8518 */ if (!$properties) { - if ($atomic_root_type->previous_value_type) { + if ($atomic_root_type->fallback_value_type) { $root_types [] = new TArray([ - $atomic_root_type->previous_key_type - ? $atomic_root_type->previous_key_type + $atomic_root_type->fallback_key_type + ? $atomic_root_type->fallback_key_type : new Union([new TArrayKey]), - $atomic_root_type->previous_value_type, + $atomic_root_type->fallback_value_type, ]) ; } else { @@ -104,9 +104,8 @@ public static function analyze( $root_types []= new TKeyedArray( $properties, null, - $atomic_root_type->sealed, - $atomic_root_type->previous_key_type, - $atomic_root_type->previous_value_type, + $atomic_root_type->fallback_key_type, + $atomic_root_type->fallback_value_type, $is_list ); } @@ -118,9 +117,8 @@ public static function analyze( $root_types []= new TKeyedArray( $properties, null, - false, - $atomic_root_type->previous_key_type, - $atomic_root_type->previous_value_type, + $atomic_root_type->fallback_key_type, + $atomic_root_type->fallback_value_type, false, ); } diff --git a/src/Psalm/Internal/Codebase/ConstantTypeResolver.php b/src/Psalm/Internal/Codebase/ConstantTypeResolver.php index e6e0fcae640..79d6e3de8d6 100644 --- a/src/Psalm/Internal/Codebase/ConstantTypeResolver.php +++ b/src/Psalm/Internal/Codebase/ConstantTypeResolver.php @@ -141,8 +141,7 @@ public static function resolve( if ($left instanceof TKeyedArray && $right instanceof TKeyedArray) { $type = new TKeyedArray( $left->properties + $right->properties, - null, - true + null ); return $type; } @@ -266,7 +265,7 @@ public static function resolve( new Union([new TNever()]), ]); } else { - $resolved_type = new TKeyedArray($properties, null, true, null, null, $is_list); + $resolved_type = new TKeyedArray($properties, null, null, null, $is_list); } return $resolved_type; @@ -340,7 +339,7 @@ public static function resolve( * * @param array|string|int|float|bool|null $value */ - public static function getLiteralTypeFromScalarValue($value, bool $sealed_array = true): Atomic + public static function getLiteralTypeFromScalarValue($value): Atomic { if (is_array($value)) { if (empty($value)) { @@ -350,9 +349,9 @@ public static function getLiteralTypeFromScalarValue($value, bool $sealed_array $types = []; /** @var array|scalar|null $val */ foreach ($value as $key => $val) { - $types[$key] = new Union([self::getLiteralTypeFromScalarValue($val, $sealed_array)]); + $types[$key] = new Union([self::getLiteralTypeFromScalarValue($val)]); } - return new TKeyedArray($types, null, $sealed_array); + return new TKeyedArray($types, null); } if (is_string($value)) { diff --git a/src/Psalm/Internal/Codebase/Methods.php b/src/Psalm/Internal/Codebase/Methods.php index a7ba48dd184..c40cb8aa41a 100644 --- a/src/Psalm/Internal/Codebase/Methods.php +++ b/src/Psalm/Internal/Codebase/Methods.php @@ -625,7 +625,7 @@ public function getMethodReturnType( $types[] = new Union([new TEnumCase($original_fq_class_name, $case_name)]); } - $list = new TKeyedArray($types, null, true, null, null, true); + $list = new TKeyedArray($types, null, null, null, true); return new Union([$list]); } } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php index 2b6725d9723..6ab3776147b 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php @@ -85,7 +85,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $inner_type = $first_arg_array->getGenericValueType(); $key_type = $first_arg_array->getGenericKeyType(); - if (!isset($call_args[1]) && !$first_arg_array->previous_value_type) { + if (!isset($call_args[1]) && !$first_arg_array->fallback_value_type) { $had_one = count($first_arg_array->properties) === 1; $new_properties = array_filter( @@ -118,9 +118,8 @@ static function ($keyed_type) use ($statements_source, $context) { return new Union([new TKeyedArray( $new_properties, null, - $first_arg_array->sealed, - null, - null, + $first_arg_array->fallback_key_type, + $first_arg_array->fallback_value_type, $first_arg_array->is_list && $had_one )]); } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php index b3796d38a7a..88d2043f033 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php @@ -86,7 +86,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev if ($call_arg_type && $call_arg_type->isSingle() && ($call_arg_atomic = $call_arg_type->getSingleAtomic()) instanceof TKeyedArray - && $call_arg_atomic->sealed + && $call_arg_atomic->fallback_value_type === null ) { $array_arg_types []= array_values($call_arg_atomic->properties); } else { @@ -103,13 +103,13 @@ function (array $sub) use ($null) { fn (?Union $t) => $t ?? $null, $sub ); - return new Union([new TKeyedArray($sub, null, true, null, null, true)]); + return new Union([new TKeyedArray($sub, null, null, null, true)]); }, $array_arg_types ); assert(count($array_arg_types)); - return new Union([new TKeyedArray($array_arg_types, null, true, null, null, true)]); + return new Union([new TKeyedArray($array_arg_types, null, null, null, true)]); } $array_arg = $call_args[1] ?? null; @@ -215,9 +215,10 @@ function (array $sub) use ($null) { $array_arg_atomic_type->properties ), null, - $array_arg_atomic_type->sealed, - $array_arg_atomic_type->previous_key_type, - $array_arg_atomic_type->sealed ? null : $mapping_return_type, + $array_arg_atomic_type->fallback_key_type, + $array_arg_atomic_type->fallback_value_type === null + ? null + : $mapping_return_type, $array_arg_atomic_type->is_list ); diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php index 962325f873c..bf50b0cf106 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php @@ -58,7 +58,6 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $generic_properties = []; $class_strings = []; $all_keyed_arrays = true; - $all_keyed_arrays_are_sealed = true; $all_int_offsets = true; $all_nonempty_lists = true; $any_nonempty = false; @@ -146,11 +145,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $all_nonempty_lists = false; } - if (!$unpacked_type_part->sealed) { - $all_keyed_arrays_are_sealed = false; - } - - if ($unpacked_type_part->sealed) { + if ($unpacked_type_part->fallback_value_type === null) { $any_nonempty = true; } @@ -247,7 +242,6 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $objectlike = new TKeyedArray( $generic_properties, $class_strings ?: null, - $all_keyed_arrays && $all_keyed_arrays_are_sealed, $all_keyed_arrays ? null : $inner_key_type, $all_keyed_arrays ? null : $inner_value_type, $all_nonempty_lists || $all_int_offsets diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php index a524439414a..fb7120420c4 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php @@ -78,7 +78,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev } else { $value_type = $first_arg_array->getGenericValueType(); - if (!$first_arg_array->sealed && !$first_arg_array->previous_value_type) { + if ($first_arg_array->fallback_value_type === null) { $nullable = true; } } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php index 20191869c9f..64d732c308b 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php @@ -125,7 +125,8 @@ public static function getGetObjectVarsReturnType( new TKeyedArray( $properties, null, - $class_storage->final + $class_storage->final ? null : Type::getString(), + $class_storage->final ? null : Type::getMixed(), ) ]); } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ParseUrlReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ParseUrlReturnTypeProvider.php index 3cf991af2f8..96373ad09ba 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ParseUrlReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ParseUrlReturnTypeProvider.php @@ -148,8 +148,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev self::$return_type = new Union([ new TKeyedArray( $component_types, - null, - true + null ), new TFalse(), ], [ diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index 38c29d9c9a8..a9fceca7f27 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -648,7 +648,6 @@ private static function filterAtomicWithAnother( return new TKeyedArray( $type_2_atomic->properties, null, - false, Type::getInt(), $type_2_value, true @@ -676,7 +675,6 @@ private static function filterAtomicWithAnother( return new TKeyedArray( $type_1_atomic->properties, null, - false, Type::getInt(), $type_1_value, true diff --git a/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php b/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php index 9e30626e1b9..44575f73631 100644 --- a/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php +++ b/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php @@ -28,11 +28,11 @@ public static function isContainedBy( ?TypeComparisonResult $atomic_comparison_result ): bool { $container_sealed = $container_type_part instanceof TKeyedArray - && $container_type_part->sealed; + && $container_type_part->fallback_value_type === null; if ($container_sealed && $input_type_part instanceof TKeyedArray - && !$input_type_part->sealed + && $input_type_part->fallback_value_type !== null ) { return false; } diff --git a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php index d441b08f730..d100f644885 100644 --- a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php @@ -622,7 +622,7 @@ private static function reconcileNonEmptyCountable( } if ($assertion instanceof HasAtLeastCount) { - if ($array_atomic_type->sealed) { + if ($array_atomic_type->fallback_value_type === null) { // count($a) > 3 // count($a) >= 4 @@ -668,7 +668,7 @@ private static function reconcileNonEmptyCountable( $properties = $array_atomic_type->properties; for ($i = $prop_max_count; $i < $assertion->count; $i++) { $properties[$i] - = ($array_atomic_type->previous_value_type ?: Type::getMixed()); + = $array_atomic_type->fallback_value_type; } $array_atomic_type = $array_atomic_type->setProperties($properties); $existing_var_type->removeType('array'); @@ -734,7 +734,7 @@ private static function reconcileExactlyCountable( $non_empty_list ); } elseif ($array_atomic_type instanceof TKeyedArray) { - if ($array_atomic_type->sealed) { + if ($array_atomic_type->fallback_value_type === null) { if (count($array_atomic_type->properties) === $count) { $existing_var_type->removeType('array'); $existing_var_type->addType($array_atomic_type->setProperties( @@ -1675,9 +1675,8 @@ private static function reconcileHasArrayKey( $atomic_type->class_strings ?? [], [$assertion => true] ) : $atomic_type->class_strings, - $atomic_type->sealed, - $atomic_type->previous_key_type, - $atomic_type->previous_value_type, + $atomic_type->fallback_key_type, + $atomic_type->fallback_value_type, $atomic_type->is_list ); } @@ -2104,7 +2103,9 @@ private static function reconcileList( } else { $array_types[] = $type; } - } elseif ($type instanceof TArray || ($type instanceof TKeyedArray && !$type->sealed)) { + } elseif ($type instanceof TArray + || ($type instanceof TKeyedArray && $type->fallback_value_type !== null) + ) { if ($type instanceof TKeyedArray) { $type = $type->getGenericArrayType(); } diff --git a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php index ab1b5e927bc..d34eee6fe61 100644 --- a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php @@ -533,7 +533,7 @@ private static function reconcileNotNonEmptyCountable( $existing_var_type->removeType('array'); } elseif ($array_atomic_type instanceof TKeyedArray) { - if ($array_atomic_type->sealed && $count !== null) { + if ($array_atomic_type->fallback_value_type === null && $count !== null) { $prop_max_count = count($array_atomic_type->properties); $prop_min_count = 0; foreach ($array_atomic_type->properties as $property_type) { diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index ba7f741ef8b..5455e4054e4 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -678,20 +678,21 @@ private static function scrapeTypeProperties( $existing_objectlike_entries = (bool) $combination->objectlike_entries; $missing_entries = $combination->objectlike_entries; - $combination->objectlike_sealed = $combination->objectlike_sealed && $type->sealed; + $combination->objectlike_sealed = $combination->objectlike_sealed + && $type->fallback_value_type === null; - if ($type->previous_value_type) { + if ($type->fallback_value_type) { $combination->objectlike_value_type = Type::combineUnionTypes( - $type->previous_value_type, + $type->fallback_value_type, $combination->objectlike_value_type, $codebase, $overwrite_empty_array ); } - if ($type->previous_key_type) { + if ($type->fallback_key_type) { $combination->objectlike_key_type = Type::combineUnionTypes( - $type->previous_key_type, + $type->fallback_key_type, $combination->objectlike_key_type, $codebase, $overwrite_empty_array @@ -1364,50 +1365,45 @@ private static function handleKeyedArrayEntries( } if ($combination->objectlike_entries) { - $previous_key_type = null; + $fallback_key_type = null; if ($combination->objectlike_key_type) { - $previous_key_type = $combination->objectlike_key_type; + $fallback_key_type = $combination->objectlike_key_type; } elseif ($combination->array_type_params && $combination->array_type_params[0]->isArrayKey() ) { - $previous_key_type = $combination->array_type_params[0]; + $fallback_key_type = $combination->array_type_params[0]; } - $previous_value_type = null; + $fallback_value_type = null; if ($combination->objectlike_value_type) { - $previous_value_type = $combination->objectlike_value_type; + $fallback_value_type = $combination->objectlike_value_type; } elseif ($combination->array_type_params && $combination->array_type_params[1]->isMixed() ) { - $previous_value_type = $combination->array_type_params[1]; + $fallback_value_type = $combination->array_type_params[1]; } + $sealed = $combination->objectlike_sealed && ( + !$combination->array_type_params + || (isset($combination->array_type_params[1]) + && $combination->array_type_params[1]->isNever() + ) + ); + if ($combination->all_arrays_callable) { $objectlike = new TCallableKeyedArray( $combination->objectlike_entries, null, - $combination->objectlike_sealed && ( - !$combination->array_type_params - || (isset($combination->array_type_params[1]) - && $combination->array_type_params[1]->isNever() - ) - ), - $previous_key_type, - $previous_value_type, + $sealed ? null : $fallback_key_type, + $sealed ? null : $fallback_value_type, (bool)$combination->all_arrays_lists ); } else { $objectlike = new TKeyedArray( $combination->objectlike_entries, null, - $combination->objectlike_sealed && ( - !$combination->array_type_params - || (isset($combination->array_type_params[1]) - && $combination->array_type_params[1]->isNever() - ) - ), - $previous_key_type, - $previous_value_type, + $sealed ? null : $fallback_key_type, + $sealed ? null : $fallback_value_type, (bool)$combination->all_arrays_lists ); } @@ -1522,7 +1518,6 @@ private static function getArrayTypeFromGenericParams( $array_type = new TKeyedArray( [$generic_type_params[1]], null, - false, Type::getInt(), $combination->array_type_params[1], true diff --git a/src/Psalm/Internal/Type/TypeExpander.php b/src/Psalm/Internal/Type/TypeExpander.php index a9700734f5a..cb1ae86669e 100644 --- a/src/Psalm/Internal/Type/TypeExpander.php +++ b/src/Psalm/Internal/Type/TypeExpander.php @@ -37,6 +37,7 @@ use Psalm\Type\Atomic\TValueOf; use Psalm\Type\Atomic\TVoid; use Psalm\Type\Union; +use Psalm\Type; use ReflectionProperty; use function array_filter; @@ -1029,7 +1030,8 @@ private static function expandPropertiesOf( return [new TKeyedArray( $properties, null, - $all_sealed + $all_sealed ? null : Type::getString(), + $all_sealed ? null : Type::getMixed(), )]; } diff --git a/src/Psalm/Internal/Type/TypeParser.php b/src/Psalm/Internal/Type/TypeParser.php index fe0b35f99ce..2e850c3ce2d 100644 --- a/src/Psalm/Internal/Type/TypeParser.php +++ b/src/Psalm/Internal/Type/TypeParser.php @@ -1129,47 +1129,53 @@ private static function getTypeFromIntersectionTree( /** @var TKeyedArray $intersection_type */ foreach ($intersection_types as $intersection_type) { - if (!$intersection_type->sealed) { - $all_sealed = false; - } foreach ($intersection_type->properties as $property => $property_type) { + if ($intersection_type->fallback_value_type !== null) { + $all_sealed = false; + } + if (!array_key_exists($property, $properties)) { $properties[$property] = $property_type; continue; } - $intersection_type = Type::intersectUnionTypes( + $new_type = Type::intersectUnionTypes( $properties[$property], $property_type, $codebase ); - if ($intersection_type === null) { + + if ($new_type === null) { throw new TypeParseTreeException( 'Incompatible intersection types for "' . $property . '", ' . $properties[$property] . ' and ' . $property_type . ' provided' ); } - $properties[$property] = $intersection_type; + $properties[$property] = $new_type; } } - $previous_key_type = null; - $previous_value_type = null; - if ($first_type instanceof TArray) { - $previous_key_type = $first_type->type_params[0]; - $previous_value_type = $first_type->type_params[1]; - } elseif ($last_type instanceof TArray) { - $previous_key_type = $last_type->type_params[0]; - $previous_value_type = $last_type->type_params[1]; + $first_or_last_type = $first_type instanceof TArray + ? $first_type + : ($last_type instanceof TArray ? $last_type : null); + + $fallback_key_type = null; + $fallback_value_type = null; + + if ($first_or_last_type !== null) { + $fallback_key_type = $first_or_last_type->type_params[0]; + $fallback_value_type = $first_or_last_type->type_params[1]; + } elseif (!$all_sealed) { + $fallback_key_type = Type::getArrayKey(); + $fallback_value_type = Type::getMixed(); } return new TKeyedArray( $properties, null, - $all_sealed, - $previous_key_type ?? null, - $previous_value_type ?? null, + $fallback_key_type, + $fallback_value_type, false, $from_docblock ); @@ -1483,9 +1489,8 @@ private static function getTypeFromKeyedArrayTree( return new $class( $properties, $class_strings, - $sealed, - null, - null, + $sealed ? null : ($is_list ? Type::getInt() : Type::getArrayKey()), + $sealed ? null : Type::getMixed(), $is_list, $from_docblock ); diff --git a/src/Psalm/Type/Atomic/TKeyedArray.php b/src/Psalm/Type/Atomic/TKeyedArray.php index b0a6c382630..a747346e12c 100644 --- a/src/Psalm/Type/Atomic/TKeyedArray.php +++ b/src/Psalm/Type/Atomic/TKeyedArray.php @@ -45,27 +45,19 @@ class TKeyedArray extends Atomic */ public $class_strings; - /** - * True if the objectlike has been created from an explicit array, - * or if we're sure that this objectlike has only these keys. - * - * @var bool - */ - public $sealed = false; - /** * Whether or not the previous array had an unknown key type * * @var ?Union */ - public $previous_key_type; + public $fallback_key_type; /** * Whether or not to allow new properties to be asserted on the given array * * @var ?Union */ - public $previous_value_type; + public $fallback_value_type; /** * @var bool - if this is a list of sequential elements @@ -86,20 +78,15 @@ class TKeyedArray extends Atomic public function __construct( array $properties, ?array $class_strings = null, - bool $sealed = false, - ?Union $previous_key_type = null, - ?Union $previous_value_type = null, + ?Union $fallback_key_type = null, + ?Union $fallback_value_type = null, bool $is_list = false, bool $from_docblock = false ) { - if ($previous_key_type || $previous_value_type) { - $sealed = false; - } $this->properties = $properties; $this->class_strings = $class_strings; - $this->sealed = $sealed; - $this->previous_key_type = $previous_key_type; - $this->previous_value_type = $previous_value_type; + $this->fallback_key_type = $fallback_key_type; + $this->fallback_value_type = $fallback_value_type; $this->is_list = $is_list; $this->from_docblock = $from_docblock; } @@ -124,14 +111,16 @@ public function setProperties(array $properties): self */ public function setSealed(bool $sealed): self { - if ($sealed === $this->sealed) { + if ($sealed === ($this->fallback_value_type === null)) { return $this; } $cloned = clone $this; - $cloned->sealed = $sealed; if ($sealed) { - $cloned->previous_key_type = null; - $cloned->previous_value_type = null; + $cloned->fallback_key_type = null; + $cloned->fallback_value_type = null; + } else { + $cloned->fallback_key_type = $this->is_list ? Type::getInt() : Type::getArrayKey(); + $cloned->fallback_value_type = Type::getMixed(); } return $cloned; } @@ -175,18 +164,16 @@ public function getId(bool $exact = true, bool $nested = false): string $key = static::NAME_ARRAY; sort($property_strings); } - if ($this->sealed) { - $key = "strict-$key"; - } - return $key . '{' . + return ($this->fallback_value_type === null && !($this instanceof TCallableKeyedArray) ? 'strict-' : '') + . $key . '{' . implode(', ', $property_strings) . '}' - . ($this->previous_value_type - && (!$this->previous_value_type->isMixed() - || ($this->previous_key_type && !$this->previous_key_type->isArrayKey())) - ? '<' . ($this->previous_key_type ? $this->previous_key_type->getId($exact) . ', ' : '') - . $this->previous_value_type->getId($exact) . '>' + . ($this->fallback_value_type + && (!$this->fallback_value_type->isMixed() + || ($this->fallback_key_type && !$this->fallback_key_type->isArrayKey())) + ? '<' . ($this->fallback_key_type ? $this->fallback_key_type->getId($exact) . ', ' : '') + . $this->fallback_value_type->getId($exact) . '>' : ''); } @@ -250,9 +237,9 @@ public function toNamespacedString( ); } - return ($this->sealed ? 'strict-' : '') . - ($this->is_list ? static::NAME_LIST : static::NAME_ARRAY) . '{' . - implode(', ', $suffixed_properties) . '}'; + return ($this->fallback_value_type === null && !($this instanceof TCallableKeyedArray) ? 'strict-' : '') + . ($this->is_list ? static::NAME_LIST : static::NAME_ARRAY) + . '{' . implode(', ', $suffixed_properties) . '}'; } /** @@ -291,7 +278,7 @@ public function getGenericKeyType(bool $possibly_undefined = false): Union /** @psalm-suppress InaccessibleProperty We just created this type */ $key_type->possibly_undefined = $possibly_undefined; - return Type::combineUnionTypes($this->previous_key_type, $key_type); + return Type::combineUnionTypes($this->fallback_key_type, $key_type); } public function getGenericValueType(bool $possibly_undefined = false): Union @@ -303,7 +290,7 @@ public function getGenericValueType(bool $possibly_undefined = false): Union } $value_type = Type::combineUnionTypes( - $this->previous_value_type, + $this->fallback_value_type, $value_type, null, false, @@ -343,12 +330,12 @@ public function getGenericArrayType(bool $allow_non_empty = true): TArray $key_type = TypeCombiner::combine($key_types); - $value_type = Type::combineUnionTypes($this->previous_value_type, $value_type); - $key_type = Type::combineUnionTypes($this->previous_key_type, $key_type); + $value_type = Type::combineUnionTypes($this->fallback_value_type, $value_type); + $key_type = Type::combineUnionTypes($this->fallback_key_type, $key_type); $value_type = $value_type->setPossiblyUndefined(false); - if ($allow_non_empty && ($this->previous_value_type || $has_defined_keys)) { + if ($allow_non_empty && ($this->fallback_value_type || $has_defined_keys)) { $array_type = new TNonEmptyArray([$key_type, $value_type]); } else { $array_type = new TArray([$key_type, $value_type]); @@ -461,10 +448,22 @@ public function equals(Atomic $other_type, bool $ensure_source_equality): bool return false; } - if ($this->sealed !== $other_type->sealed) { + if (($this->fallback_value_type === null) !== ($other_type->fallback_value_type === null)) { return false; } + if ($this->fallback_value_type !== null && $other_type->fallback_value_type !== null) { + if (!$this->fallback_value_type->equals($other_type->fallback_value_type)) { + return false; + } + } + + if ($this->fallback_key_type !== null && $other_type->fallback_key_type !== null) { + if (!$this->fallback_key_type->equals($other_type->fallback_key_type)) { + return false; + } + } + foreach ($this->properties as $property_name => $property_type) { if (!isset($other_type->properties[$property_name])) { return false; diff --git a/src/Psalm/Type/Reconciler.php b/src/Psalm/Type/Reconciler.php index 73d38b8b39f..43ec93014dc 100644 --- a/src/Psalm/Type/Reconciler.php +++ b/src/Psalm/Type/Reconciler.php @@ -782,9 +782,9 @@ private static function getValueForKey( $key_parts_key = str_replace('\'', '', $array_key); if (!isset($array_properties[$key_parts_key])) { - if ($existing_key_type_part->previous_value_type) { + if ($existing_key_type_part->fallback_value_type) { $new_base_type_candidate = $existing_key_type_part - ->previous_value_type->setDifferent(true); + ->fallback_value_type->setDifferent(true); } else { return null; } @@ -1134,30 +1134,28 @@ private static function adjustTKeyedArrayType( $new_base_type = $existing_types[$base_key]; if ($base_atomic_type instanceof TArray) { - $previous_key_type = $base_atomic_type->type_params[0]; - $previous_value_type = $base_atomic_type->type_params[1]; + $fallback_key_type = $base_atomic_type->type_params[0]; + $fallback_value_type = $base_atomic_type->type_params[1]; $base_atomic_type = new TKeyedArray( [ $array_key_offset => $result_type, ], null, - false, - $previous_key_type->isNever() ? null : $previous_key_type, - $previous_value_type + $fallback_key_type->isNever() ? null : $fallback_key_type, + $fallback_value_type->isNever() ? null : $fallback_value_type ); } elseif ($base_atomic_type instanceof TList) { - $previous_key_type = Type::getInt(); - $previous_value_type = $base_atomic_type->type_param; + $fallback_key_type = Type::getInt(); + $fallback_value_type = $base_atomic_type->type_param; $base_atomic_type = new TKeyedArray( [ $array_key_offset => $result_type, ], null, - false, - $previous_key_type, - $previous_value_type, + $fallback_key_type, + $fallback_value_type, true ); } elseif ($base_atomic_type instanceof TClassStringMap) { diff --git a/tests/PropertiesOfTest.php b/tests/PropertiesOfTest.php index 588ab15d1a1..bbfcbe4b230 100644 --- a/tests/PropertiesOfTest.php +++ b/tests/PropertiesOfTest.php @@ -49,9 +49,9 @@ function test3() {} $result3 = test3(); ', 'assertions' => [ - '$result1===' => 'array{a: int}', - '$result2===' => 'array{a: int}', - '$result3===' => 'array{a: int}', + '$result1===' => 'array{a: int}', + '$result2===' => 'array{a: int}', + '$result3===' => 'array{a: int}', ] ], 'publicPropertiesOf' => [ diff --git a/tests/ReturnTypeProvider/GetObjectVarsTest.php b/tests/ReturnTypeProvider/GetObjectVarsTest.php index 1fe26f434da..548a2919afa 100644 --- a/tests/ReturnTypeProvider/GetObjectVarsTest.php +++ b/tests/ReturnTypeProvider/GetObjectVarsTest.php @@ -13,13 +13,13 @@ public function providerValidCodeParse(): iterable { yield 'returnsPublicProperties' => [ 'code' => ' ['$ret' => 'array{prop: string}'], + 'assertions' => ['$ret' => 'strict-array{prop: string}'], ]; yield 'returnsSealedArrayForFinalClass' => [ @@ -103,12 +103,12 @@ function f(object $p): array { yield 'propertiesOfCastScalar' => [ 'code' => ' ['$ret' => 'array{scalar: true}'], + 'assertions' => ['$ret' => 'strict-array{scalar: true}'], ]; yield 'propertiesOfPOPO' => [ 'code' => ' 1]);', - 'assertions' => ['$ret' => 'array{a: int}'], + 'assertions' => ['$ret' => 'strict-array{a: int}'], ]; yield 'templatedProperties' => [ diff --git a/tests/Template/PropertiesOfTemplateTest.php b/tests/Template/PropertiesOfTemplateTest.php index 414ae7a1aae..b6298eb06a3 100644 --- a/tests/Template/PropertiesOfTemplateTest.php +++ b/tests/Template/PropertiesOfTemplateTest.php @@ -79,7 +79,7 @@ public function __construct(public $a) {} $objAsArray = asArray($obj); ', 'assertions' => [ - '$objAsArray===' => 'array{a: 42, b: bool, c: string}' + '$objAsArray===' => 'array{a: 42, b: bool, c: string}' ] ], 'privatePropertiesPicksPrivate' => [ @@ -213,7 +213,7 @@ function asArray($obj) { return $properties; } - class A { + final class A { /** @var int */ public $a = 42; /** @var bool */ @@ -241,7 +241,7 @@ function asArray($obj) { return $properties; } - class A { + final class A { /** @var int */ public $a = 42; /** @var bool */ @@ -269,7 +269,7 @@ function asArray($obj) { return $properties; } - class A { + final class A { /** @var int */ public $a = 42; /** @var bool */ @@ -297,7 +297,7 @@ function asArray($obj) { return $properties; } - class A { + final class A { /** @var int */ public $a = 42; /** @var bool */ @@ -325,7 +325,7 @@ function asArray($obj) { return $properties; } - class A { + final class A { /** @var int */ public $a = 42; /** @var bool */ @@ -353,7 +353,7 @@ function asArray($obj) { return $properties; } - class A { + final class A { /** @var int */ public $a = 42; /** @var bool */ @@ -381,7 +381,7 @@ function asArray($obj) { return $properties; } - class A { + final class A { /** @var int */ public $a = 42; /** @var bool */ @@ -410,7 +410,7 @@ function asArray($array) { return $properties; } - class A { + final class A { /** @var int */ public $a = 42; /** @var bool */ diff --git a/tests/TypeReconciliation/ReconcilerTest.php b/tests/TypeReconciliation/ReconcilerTest.php index 0f910f8d680..6d93c5f935d 100644 --- a/tests/TypeReconciliation/ReconcilerTest.php +++ b/tests/TypeReconciliation/ReconcilerTest.php @@ -162,8 +162,8 @@ public function providerTestReconcilation(): array 'iterableToArray' => ['array', new IsType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'iterable'], 'iterableToTraversable' => ['Traversable', new IsType(new TNamedObject('Traversable')), 'iterable'], 'callableToCallableArray' => ['callable-array{0: class-string|object, 1: string}', new IsType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'callable'], - 'SmallKeyedArrayAndCallable' => ['array{test: string}', new IsType(new TKeyedArray(['test' => Type::getString()])), 'callable'], - 'BigKeyedArrayAndCallable' => ['array{foo: string, test: string, thing: string}', new IsType(new TKeyedArray(['foo' => Type::getString(), 'test' => Type::getString(), 'thing' => Type::getString()])), 'callable'], + 'SmallKeyedArrayAndCallable' => ['strict-array{test: string}', new IsType(new TKeyedArray(['test' => Type::getString()])), 'callable'], + 'BigKeyedArrayAndCallable' => ['strict-array{foo: string, test: string, thing: string}', new IsType(new TKeyedArray(['foo' => Type::getString(), 'test' => Type::getString(), 'thing' => Type::getString()])), 'callable'], 'callableOrArrayToCallableArray' => ['array', new IsType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'callable|array'], 'traversableToIntersection' => ['Countable&Traversable', new IsType(new TNamedObject('Traversable')), 'Countable'], 'iterableWithoutParamsToTraversableWithoutParams' => ['Traversable', new IsNotType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'iterable'],