|
49 | 49 | use PHPStan\Type\BooleanType; |
50 | 50 | use PHPStan\Type\ConditionalTypeForParameter; |
51 | 51 | use PHPStan\Type\Constant\ConstantArrayType; |
52 | | -use PHPStan\Type\Constant\ConstantArrayTypeBuilder; |
53 | 52 | use PHPStan\Type\Constant\ConstantBooleanType; |
54 | 53 | use PHPStan\Type\Constant\ConstantFloatType; |
55 | 54 | use PHPStan\Type\Constant\ConstantIntegerType; |
|
100 | 99 | final class TypeSpecifier |
101 | 100 | { |
102 | 101 |
|
103 | | - private const MAX_ACCESSORIES_LIMIT = 8; |
104 | | - |
105 | 102 | private const BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4; |
106 | 103 |
|
107 | 104 | /** @var MethodTypeSpecifyingExtension[][]|null */ |
@@ -1432,100 +1429,15 @@ private function specifyTypesForCountFuncCall( |
1432 | 1429 | continue; |
1433 | 1430 | } |
1434 | 1431 |
|
1435 | | - if ( |
1436 | | - $sizeType instanceof ConstantIntegerType |
1437 | | - && $sizeType->getValue() < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT |
1438 | | - && $isList->yes() |
1439 | | - && $arrayType->getKeyType()->isSuperTypeOf(IntegerRangeType::fromInterval(0, $sizeType->getValue() - 1))->yes() |
1440 | | - ) { |
1441 | | - // turn optional offsets non-optional |
1442 | | - $valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty(); |
1443 | | - for ($i = 0; $i < $sizeType->getValue(); $i++) { |
1444 | | - $offsetType = new ConstantIntegerType($i); |
1445 | | - $valueTypesBuilder->setOffsetValueType($offsetType, $arrayType->getOffsetValueType($offsetType)); |
1446 | | - } |
1447 | | - $resultTypes[] = $valueTypesBuilder->getArray(); |
1448 | | - continue; |
1449 | | - } |
1450 | | - |
1451 | | - if ( |
1452 | | - $sizeType instanceof IntegerRangeType |
1453 | | - && $sizeType->getMin() !== null |
1454 | | - && $sizeType->getMin() < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT |
1455 | | - && $isList->yes() |
1456 | | - && $arrayType->getKeyType()->isSuperTypeOf(IntegerRangeType::fromInterval(0, ($sizeType->getMax() ?? $sizeType->getMin()) - 1))->yes() |
1457 | | - ) { |
1458 | | - $builderData = []; |
1459 | | - // turn optional offsets non-optional |
1460 | | - for ($i = 0; $i < $sizeType->getMin(); $i++) { |
1461 | | - $offsetType = new ConstantIntegerType($i); |
1462 | | - $builderData[] = [$offsetType, $arrayType->getOffsetValueType($offsetType), false]; |
1463 | | - } |
1464 | | - if ($sizeType->getMax() !== null) { |
1465 | | - if ($sizeType->getMax() - $sizeType->getMin() > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) { |
1466 | | - $resultTypes[] = $arrayType; |
1467 | | - continue; |
1468 | | - } |
1469 | | - for ($i = $sizeType->getMin(); $i < $sizeType->getMax(); $i++) { |
1470 | | - $offsetType = new ConstantIntegerType($i); |
1471 | | - $builderData[] = [$offsetType, $arrayType->getOffsetValueType($offsetType), true]; |
1472 | | - } |
1473 | | - } elseif ($arrayType->isConstantArray()->yes()) { |
1474 | | - for ($i = $sizeType->getMin();; $i++) { |
1475 | | - $offsetType = new ConstantIntegerType($i); |
1476 | | - $hasOffset = $arrayType->hasOffsetValueType($offsetType); |
1477 | | - if ($hasOffset->no()) { |
1478 | | - break; |
1479 | | - } |
1480 | | - $builderData[] = [$offsetType, $arrayType->getOffsetValueType($offsetType), !$hasOffset->yes()]; |
1481 | | - } |
1482 | | - } else { |
1483 | | - $intersection = []; |
1484 | | - $intersection[] = $arrayType; |
1485 | | - $intersection[] = new NonEmptyArrayType(); |
1486 | | - |
1487 | | - $zero = new ConstantIntegerType(0); |
1488 | | - $i = 0; |
1489 | | - foreach ($builderData as [$offsetType, $valueType]) { |
1490 | | - // non-empty-list already implies the offset 0 |
1491 | | - if ($zero->isSuperTypeOf($offsetType)->yes()) { |
1492 | | - continue; |
1493 | | - } |
1494 | | - |
1495 | | - if ($i > self::MAX_ACCESSORIES_LIMIT) { |
1496 | | - break; |
1497 | | - } |
1498 | | - |
1499 | | - $intersection[] = new HasOffsetValueType($offsetType, $valueType); |
1500 | | - $i++; |
1501 | | - } |
1502 | | - |
1503 | | - $resultTypes[] = TypeCombinator::intersect(...$intersection); |
1504 | | - continue; |
1505 | | - } |
1506 | | - |
1507 | | - if (count($builderData) > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) { |
1508 | | - $resultTypes[] = $arrayType; |
1509 | | - continue; |
1510 | | - } |
1511 | | - |
1512 | | - $builder = ConstantArrayTypeBuilder::createEmpty(); |
1513 | | - foreach ($builderData as [$offsetType, $valueType, $optional]) { |
1514 | | - $builder->setOffsetValueType($offsetType, $valueType, $optional); |
1515 | | - } |
1516 | | - |
1517 | | - $builtArray = $builder->getArray(); |
1518 | | - if ($isList->yes() && !$builder->isList()) { |
1519 | | - $constantArrays = $builtArray->getConstantArrays(); |
1520 | | - if (count($constantArrays) === 1) { |
1521 | | - $builtArray = $constantArrays[0]->makeList(); |
1522 | | - } |
1523 | | - } |
1524 | | - $resultTypes[] = $builtArray; |
1525 | | - continue; |
1526 | | - } |
1527 | | - |
1528 | | - $resultTypes[] = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); |
| 1432 | + // `truncateListToSize` rebuilds the inner array as a list shape |
| 1433 | + // — that's only sound when the *outer* type is definitely a |
| 1434 | + // list. The inner array alone may have `isList()` answer `Maybe` |
| 1435 | + // (e.g. `ArrayType<int<0, max>, T>` inside a |
| 1436 | + // `non-empty-list<T>` intersection), so the gate has to live |
| 1437 | + // here, not on the per-array method. |
| 1438 | + $resultTypes[] = $isList->yes() |
| 1439 | + ? $arrayType->truncateListToSize($sizeType) |
| 1440 | + : TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); |
1529 | 1441 | } |
1530 | 1442 |
|
1531 | 1443 | if ($context->truthy() && $isConstantArray->yes() && $isList->yes()) { |
|
0 commit comments