|
41 | 41 | use PHPStan\Type\Constant\ConstantIntegerType; |
42 | 42 | use PHPStan\Type\Constant\ConstantStringType; |
43 | 43 | use PHPStan\Type\Enum\EnumCaseObjectType; |
| 44 | +use PHPStan\Type\Generic\TemplateArrayType; |
44 | 45 | use PHPStan\Type\Generic\TemplateType; |
45 | 46 | use PHPStan\Type\Generic\TemplateTypeMap; |
46 | 47 | use PHPStan\Type\Generic\TemplateTypeVariance; |
@@ -398,6 +399,21 @@ private function describeItself(VerbosityLevel $level, bool $skipAccessoryTypes) |
398 | 399 | $isList = $this->isList()->yes(); |
399 | 400 | $isArray = $this->isArray()->yes(); |
400 | 401 | $isNonEmptyArray = $this->isIterableAtLeastOnce()->yes(); |
| 402 | + // When a TemplateArrayType carries the array refinement, we describe |
| 403 | + // it via its own describe() (e.g. "T of array") rather than collapsing |
| 404 | + // it into a generic `array<...>` prefix. In that case the |
| 405 | + // `NonEmptyArrayType` and `AccessoryArrayListType` markers must |
| 406 | + // describe themselves explicitly — they cannot be absorbed into a |
| 407 | + // non-existent `non-empty-array` prefix. |
| 408 | + $hasTemplateArray = false; |
| 409 | + if ($isArray || $isList) { |
| 410 | + foreach ($this->types as $type) { |
| 411 | + if ($type instanceof TemplateArrayType) { |
| 412 | + $hasTemplateArray = true; |
| 413 | + break; |
| 414 | + } |
| 415 | + } |
| 416 | + } |
401 | 417 | $describedTypes = []; |
402 | 418 | foreach ($this->getSortedTypes() as $i => $type) { |
403 | 419 | if ($type instanceof AccessoryNonEmptyStringType |
@@ -436,6 +452,14 @@ private function describeItself(VerbosityLevel $level, bool $skipAccessoryTypes) |
436 | 452 | continue; |
437 | 453 | } |
438 | 454 | if ($isList || $isArray) { |
| 455 | + if ($type instanceof TemplateArrayType) { |
| 456 | + // Preserve the template's own describe (e.g. "T of array") |
| 457 | + // instead of collapsing it to a generic array shape — the |
| 458 | + // other intersection members already carry the array |
| 459 | + // refinement. |
| 460 | + $describedTypes[$i] = $type->describe($level); |
| 461 | + continue; |
| 462 | + } |
439 | 463 | if ($type instanceof ArrayType) { |
440 | 464 | $keyType = $type->getKeyType(); |
441 | 465 | $valueType = $type->getItemType(); |
@@ -473,6 +497,9 @@ private function describeItself(VerbosityLevel $level, bool $skipAccessoryTypes) |
473 | 497 | continue; |
474 | 498 | } |
475 | 499 | if ($type instanceof NonEmptyArrayType || $type instanceof AccessoryArrayListType) { |
| 500 | + if ($hasTemplateArray) { |
| 501 | + $describedTypes[$i] = $type->describe($level); |
| 502 | + } |
476 | 503 | continue; |
477 | 504 | } |
478 | 505 | } |
|
0 commit comments