diff --git a/src/Type/Generic/TemplateTypeHelper.php b/src/Type/Generic/TemplateTypeHelper.php index 6f09c29ccb..f5a8c6715a 100644 --- a/src/Type/Generic/TemplateTypeHelper.php +++ b/src/Type/Generic/TemplateTypeHelper.php @@ -3,8 +3,11 @@ namespace PHPStan\Type\Generic; use PHPStan\Reflection\ParametersAcceptor; +use PHPStan\Type\Accessory\HasOffsetType; +use PHPStan\Type\Accessory\HasOffsetValueType; use PHPStan\Type\ErrorType; use PHPStan\Type\GeneralizePrecision; +use PHPStan\Type\MixedType; use PHPStan\Type\NonAcceptingNeverType; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; @@ -147,6 +150,14 @@ public static function generalizeInferredTemplateType(TemplateType $templateType } elseif ($type->isConstantValue()->yes() && (!$templateType->getBound()->isScalar()->yes() || $isArrayKey)) { $type = $type->generalize(GeneralizePrecision::templateArgument()); } + + $type = TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { + if ($type instanceof HasOffsetValueType || $type instanceof HasOffsetType) { + return new MixedType(); + } + + return $traverse($type); + }); } return $type; diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index 6b63c91b88..9b9cfefb48 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -411,4 +411,11 @@ public function testBug12397(): void $this->analyse([__DIR__ . '/data/bug-12397.php'], []); } + public function testBug11507(): void + { + $this->checkNullables = true; + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-11507.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-11507.php b/tests/PHPStan/Rules/Functions/data/bug-11507.php new file mode 100644 index 0000000000..8b10076dfa --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-11507.php @@ -0,0 +1,50 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug11507; + +/** + * @template TValue + */ +class Collection +{ + /** + * @param array $items + */ + public function __construct( + public array $items, + ) {} + + /** + * Run a map over each of the items. + * + * @template TMapValue + * + * @param callable(TValue, int=): TMapValue $callback + * @return Collection + */ + public function map(callable $callback): Collection + { + $keys = array_keys($this->items); + + $items = array_map($callback, $this->items); + + $result = array_combine($keys, $items); + + return new self($result); + } +} + +/** + * @param Collection> $collection + * @return Collection> + */ +function foo(Collection $collection): Collection +{ + return $collection->map(function (array $item) { + $item['foo'] = '100'; + + return $item; + }); +}