diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 1c375aae070..f70f0e3961c 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -44,6 +44,7 @@ use function get_class; use function implode; use function in_array; +use function max; use function spl_object_id; use function sprintf; use function usort; @@ -1221,7 +1222,7 @@ private static function processArrayTypes(array $arrayTypes): array $arrayType = new ArrayType( self::union(...$keyTypesForGeneralArray), - self::union(...self::optimizeConstantArrays($valueTypesForGeneralArray)), + self::union(...self::optimizeConstantArrays($valueTypesForGeneralArray, $valueTypesForGeneralArray)), ); if ($templateArrayType !== null) { @@ -1240,7 +1241,7 @@ private static function processArrayTypes(array $arrayTypes): array ]; } - $reducedArrayTypes = self::optimizeConstantArrays(self::reduceArrays($arrayTypes, true)); + $reducedArrayTypes = self::optimizeConstantArrays(self::reduceArrays($arrayTypes, true), $arrayTypes); foreach ($reducedArrayTypes as $idx => $reducedArray) { $applied = $accessoryTypes; if ($reducedArray->isIterableAtLeastOnce()->no()) { @@ -1259,10 +1260,29 @@ private static function processArrayTypes(array $arrayTypes): array } /** + * The count of constant array value types above which a union of the given + * types gets generalized. A single type already holding more than + * ARRAY_COUNT_LIMIT of them raises it - a union no bigger than one of its + * members does not grow the type, generalizing it would only lose precision. + * * @param Type[] $types + */ + private static function getConstantArrayValueTypesLimit(array $types): int + { + $limit = ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT; + foreach ($types as $type) { + $limit = max($limit, self::countConstantArrayValueTypes([$type])); + } + + return $limit; + } + + /** + * @param Type[] $types + * @param Type[] $unionedTypes the types whose union $types represent * @return Type[] */ - private static function optimizeConstantArrays(array $types): array + private static function optimizeConstantArrays(array $types, array $unionedTypes): array { $constantArrayValuesCount = self::countConstantArrayValueTypes($types); @@ -1270,6 +1290,11 @@ private static function optimizeConstantArrays(array $types): array return $types; } + $limit = self::getConstantArrayValueTypesLimit($unionedTypes); + if ($constantArrayValuesCount <= $limit) { + return $types; + } + // Stage 1: collapse same-key-set ConstantArrayType variants per-position // before the (lossy) generalization below kicks in. Variants with the // same key signature mergeWith losslessly into a single shape whose @@ -1311,7 +1336,7 @@ private static function optimizeConstantArrays(array $types): array if ($anyMerged) { $types = array_values($collapsed); $constantArrayValuesCount = self::countConstantArrayValueTypes($types); - if ($constantArrayValuesCount <= ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) { + if ($constantArrayValuesCount <= $limit) { return $types; } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-15459.php b/tests/PHPStan/Analyser/nsrt/bug-15459.php new file mode 100644 index 00000000000..407dea28caa --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-15459.php @@ -0,0 +1,545 @@ + limit) limit = count; + } + return limit; + } + + /* private static optimizeConstantArrays(Type[] $types, Type[] $unionedTypes): Type[] */ + static zv::Val optimizeConstantArrays(zval *typesArray, zval *unionedTypes) { zv::Val types = copy(typesArray); zend_long limit = arrayCountLimit(); @@ -2308,6 +2324,10 @@ class TypeCombinator if (constantArrayValuesCount <= limit) return types; + limit = getConstantArrayValueTypesLimit(unionedTypes); + PT_FAIL_IF_NEG(limit); + if (constantArrayValuesCount <= limit) return types; + /* Stage 1: collapse same-key-set ConstantArrayType variants * per-position before the (lossy) generalization below kicks in */ zv::Arr signatureGroups = zv::Arr::create(0);