From 884648b56cbea6f0555ab4ad261d469bd046c932 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Mon, 12 Sep 2016 23:24:26 -0400 Subject: [PATCH] Improve type combination when evaluating array types --- src/Psalm/Checker/StatementsChecker.php | 6 ++++-- src/Psalm/Type.php | 25 +++++++++++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/Psalm/Checker/StatementsChecker.php b/src/Psalm/Checker/StatementsChecker.php index 3b07fd0a1..515100544 100644 --- a/src/Psalm/Checker/StatementsChecker.php +++ b/src/Psalm/Checker/StatementsChecker.php @@ -1679,7 +1679,7 @@ class StatementsChecker if (isset($item->value->inferredType)) { if ($item_value_type) { - $item_value_type = Type::combineUnionTypes($item->value->inferredType, $item_value_type); + $item_value_type = Type::combineUnionTypes($item->value->inferredType, $item_value_type, true); } else { $item_value_type = $item->value->inferredType; @@ -1692,7 +1692,9 @@ class StatementsChecker 'array', [ $item_key_type ?: new Type\Union([new Type\Atomic('int'), new Type\Atomic('string')]), - $item_value_type && count($item_value_type->types) === 1 ? $item_value_type : Type::getMixed() + $item_value_type && count($item_value_type->types) === ($item_value_type->isNullable() ? 2 : 1) + ? $item_value_type + : Type::getMixed() ] ) ]); diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index a77910f4c..214cda3bd 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -485,11 +485,12 @@ abstract class Type * Combines two union types into one * @param Union $type_1 * @param Union $type_2 + * @param bool $combine_to_mixed if true, combine differing types A and B to mixed, not A|B * @return Union */ - public static function combineUnionTypes(Union $type_1, Union $type_2) + public static function combineUnionTypes(Union $type_1, Union $type_2, $combine_to_mixed = false) { - return self::combineTypes(array_merge(array_values($type_1->types), array_values($type_2->types))); + return self::combineTypes(array_merge(array_values($type_1->types), array_values($type_2->types)), $combine_to_mixed); } /** @@ -502,9 +503,10 @@ abstract class Type * and array + array = array * * @param array $types + * @param bool $combine_to_mixed if true, combine differing types A and B to mixed, not A|B * @return Union */ - public static function combineTypes(array $types) + public static function combineTypes(array $types, $combine_to_mixed = false) { if (in_array(null, $types)) { return Type::getMixed(); @@ -595,7 +597,7 @@ abstract class Type // if we're continuing, also add the correspoinding key type param if it exists if ($expanded_key_types) { - array_unshift($generic_type_params, self::combineTypes($expanded_key_types)); + array_unshift($generic_type_params, self::combineTypes($expanded_key_types, $combine_to_mixed)); } $new_types[] = $value_type_param ? new Generic($generic_type, $generic_type_params) : new Atomic($generic_type); @@ -604,8 +606,11 @@ abstract class Type $expanded_value_types = []; + $has_null = false; + foreach ($value_type as $expandable_value_type) { if ($expandable_value_type) { + $has_null = $has_null || $expandable_value_type->isNullable(); $expanded_value_types = array_merge($expanded_value_types, array_values($expandable_value_type->types)); } else { @@ -613,10 +618,18 @@ abstract class Type } } - $generic_type_params = [self::combineTypes($expanded_value_types)]; + // if $combine_to_mixed is true, we want to combine multiple non-null types to a single mixed type. + if ($combine_to_mixed) { + + if (count($expanded_value_types) > ($has_null ? 2 : 1)) { + $expanded_value_types = [Type::getMixed()->types['mixed']]; + } + } + + $generic_type_params = [self::combineTypes($expanded_value_types, $combine_to_mixed)]; if ($expanded_key_types) { - array_unshift($generic_type_params, self::combineTypes($expanded_key_types)); + array_unshift($generic_type_params, self::combineTypes($expanded_key_types, $combine_to_mixed)); } // we have a generic type with