diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index 24561b5598..d984b603b5 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -8,6 +8,7 @@ use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; +use PHPStan\Type\ConditionalTypeForParameter; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -16,6 +17,7 @@ use PHPStan\Type\NeverType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use PHPStan\Type\TypeTraverser; use PHPStan\Type\TypeUtils; use function array_slice; use function count; @@ -73,7 +75,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, foreach ($constantArray->getKeyTypes() as $i => $keyType) { $returnedArrayBuilder->setOffsetValueType( $keyType, - $valueType, + $this->mapValueType($constantArray->getOffsetValueType($keyType), $valueType), $constantArray->isOptionalKey($i), ); } @@ -88,24 +90,24 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } else { $mappedArrayType = TypeCombinator::intersect(new ArrayType( $arrayType->getIterableKeyType(), - $valueType, + $this->mapValueType($arrayType->getIterableValueType(), $valueType), ), ...TypeUtils::getAccessoryTypes($arrayType)); } } elseif ($arrayType->isArray()->yes()) { $mappedArrayType = TypeCombinator::intersect(new ArrayType( $arrayType->getIterableKeyType(), - $valueType, + $this->mapValueType($arrayType->getIterableValueType(), $valueType), ), ...TypeUtils::getAccessoryTypes($arrayType)); } else { $mappedArrayType = new ArrayType( new MixedType(), - $valueType, + $this->mapValueType($arrayType->getIterableValueType(), $valueType), ); } } else { $mappedArrayType = TypeCombinator::intersect(new ArrayType( new IntegerType(), - $valueType, + $this->mapValueType($arrayType->getIterableValueType(), $valueType), ), ...TypeUtils::getAccessoryTypes($arrayType)); } @@ -116,4 +118,15 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return $mappedArrayType; } + private function mapValueType(Type $initialValue, Type $returnType): Type + { + return TypeTraverser::map($returnType, static function (Type $type, callable $traverse) use ($initialValue): Type { + if ($type instanceof ConditionalTypeForParameter) { + $type = $type->toConditional($initialValue); + } + + return $traverse($type); + }); + } + } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 2af1147c65..a68bdf816b 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1044,6 +1044,11 @@ public function testBug3759(): void $this->analyse([__DIR__ . '/data/bug-3759.php'], []); } + public function testBug10715(): void + { + $this->analyse([__DIR__ . '/data/bug-10715.php'], []); + } + public function testBug11337(): void { $this->analyse([__DIR__ . '/data/bug-11337.php'], []); diff --git a/tests/PHPStan/Rules/Methods/data/bug-10715.php b/tests/PHPStan/Rules/Methods/data/bug-10715.php new file mode 100644 index 0000000000..82cd7f66ae --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-10715.php @@ -0,0 +1,30 @@ + $word + * + * @return ($word is array ? array : string) + */ + public static function wgtrim(string|array $word): string|array + { + if (\is_array($word)) { + return array_map(static::wgtrim(...), $word); + } + + return 'word'; + } + + /** + * @param array{foo: array, bar: string} $array + * + * @return array{foo: array, bar: string} + */ + public static function example(array $array): array + { + return array_map(static::wgtrim(...), $array); + } +}