diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 8af74bf926..8226370d44 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -85,6 +85,7 @@ use PHPStan\Type\IntersectionType; use PHPStan\Type\IterableType; use PHPStan\Type\KeyOfType; +use PHPStan\Type\LateResolvableType; use PHPStan\Type\MixedType; use PHPStan\Type\NewObjectType; use PHPStan\Type\NonAcceptingNeverType; @@ -678,20 +679,24 @@ static function (string $variance): TemplateTypeVariance { if (count($genericTypes) === 1) { // array $arrayType = new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), $genericTypes[0]); } elseif (count($genericTypes) === 2) { // array - $keyType = TypeCombinator::intersect($genericTypes[0]->toArrayKey(), new UnionType([ - new IntegerType(), - new StringType(), - ]))->toArrayKey(); - $finiteTypes = $keyType->getFiniteTypes(); - if ( - count($finiteTypes) === 1 - && ($finiteTypes[0] instanceof ConstantStringType || $finiteTypes[0] instanceof ConstantIntegerType) - ) { - $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - $arrayBuilder->setOffsetValueType($finiteTypes[0], $genericTypes[1], true); - $arrayType = $arrayBuilder->getArray(); + if ($genericTypes[0] instanceof LateResolvableType) { + $arrayType = new ArrayType($genericTypes[0], $genericTypes[1]); } else { - $arrayType = new ArrayType($keyType, $genericTypes[1]); + $keyType = TypeCombinator::intersect($genericTypes[0]->toArrayKey(), new UnionType([ + new IntegerType(), + new StringType(), + ]))->toArrayKey(); + $finiteTypes = $keyType->getFiniteTypes(); + if ( + count($finiteTypes) === 1 + && ($finiteTypes[0] instanceof ConstantStringType || $finiteTypes[0] instanceof ConstantIntegerType) + ) { + $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + $arrayBuilder->setOffsetValueType($finiteTypes[0], $genericTypes[1], true); + $arrayType = $arrayBuilder->getArray(); + } else { + $arrayType = new ArrayType($keyType, $genericTypes[1]); + } } } else { return new ErrorType(); diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 076fedcacc..7cbb69609b 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -123,7 +123,7 @@ public static function decideType( ($type->isCallable()->yes() && $phpDocType->isCallable()->yes()) || ( (!$phpDocType instanceof NeverType || ($type instanceof MixedType && !$type->isExplicitMixed())) - && $type->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocType))->yes() + && $type->isSuperTypeOf(TypeUtils::resolveLateResolvableTypes(TemplateTypeHelper::resolveToBounds($phpDocType)))->yes() ) ) { $resultType = $phpDocType; diff --git a/src/Type/ValueOfType.php b/src/Type/ValueOfType.php index e2d3f0516c..78657a2438 100644 --- a/src/Type/ValueOfType.php +++ b/src/Type/ValueOfType.php @@ -81,7 +81,15 @@ protected function getResult(): Type return new UnionType($valueTypes); } - return $this->type->getIterableValueType(); + $iterableValueType = $this->type->getIterableValueType(); + if ($iterableValueType instanceof ErrorType && $this->type instanceof LateResolvableType) { + $resolvedType = $this->type->resolve(); + if (!$resolvedType instanceof ErrorType) { + return $resolvedType; + } + } + + return $iterableValueType; } /** diff --git a/tests/PHPStan/Analyser/nsrt/bug-10231.php b/tests/PHPStan/Analyser/nsrt/bug-10231.php new file mode 100644 index 0000000000..243f2432f3 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10231.php @@ -0,0 +1,48 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug10231; + +use function PHPStan\Testing\assertType; + +class HelloWorld +{ + /** + * @template TGroupColumnName of array-key + * @template TValueColumnName of array-key + * @template TArray of array + * @param array $input + * @param TGroupColumnName $groupByColumn + * @param TValueColumnName $valueColumnName + * + * @return array< + * value-of, + * list> + * > + */ + public static function groupByColumn(array $input, string|int $groupByColumn, string|int $valueColumnName): array + { + $output = []; + foreach ($input as $result) { + $output[$result[$groupByColumn]][] = $result[$valueColumnName]; + } + + return $output; + } +} + +/** @var array $input */ +$input = [ + ['event_id' => '111', 'id' => 1], + ['event_id' => '111', 'id' => 2], + ['event_id' => '222', 'id' => 99], +]; + +$result = HelloWorld::groupByColumn( + $input, + 'event_id', + 'id', +); + +assertType('array>', $result);