Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1a20efd

Browse files
committedMar 10, 2025
Clean up a bit more aggressively
1 parent 5f1da36 commit 1a20efd

File tree

2 files changed

+81
-107
lines changed

2 files changed

+81
-107
lines changed
 

‎src/Analyser/TypeSpecifier.php

+80-106
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,6 @@ public function specifyTypesInCondition(
272272
) {
273273
$argType = $scope->getType($expr->right->getArgs()[0]->value);
274274

275-
$sizeType = null;
276275
if ($leftType instanceof ConstantIntegerType) {
277276
if ($orEqual) {
278277
$sizeType = IntegerRangeType::createAllGreaterThanOrEqualTo($leftType->getValue());
@@ -281,6 +280,8 @@ public function specifyTypesInCondition(
281280
}
282281
} elseif ($leftType instanceof IntegerRangeType) {
283282
$sizeType = $leftType->shift($offset);
283+
} else {
284+
$sizeType = $leftType;
284285
}
285286

286287
$specifiedTypes = $this->specifyTypesForCountFuncCall($expr->right, $argType, $sizeType, $context, $scope, $expr);
@@ -1044,103 +1045,91 @@ public function specifyTypesInCondition(
10441045
return (new SpecifiedTypes([], []))->setRootExpr($expr);
10451046
}
10461047

1047-
private function specifyTypesForCountFuncCall(FuncCall $countFuncCall, Type $type, ?Type $sizeType, TypeSpecifierContext $context, Scope $scope, ?Expr $rootExpr): ?SpecifiedTypes
1048+
private function specifyTypesForCountFuncCall(
1049+
FuncCall $countFuncCall,
1050+
Type $type,
1051+
Type $sizeType,
1052+
TypeSpecifierContext $context,
1053+
Scope $scope,
1054+
Expr $rootExpr,
1055+
): ?SpecifiedTypes
10481056
{
1049-
if ($sizeType === null) {
1050-
return null;
1057+
if (count($countFuncCall->getArgs()) === 1) {
1058+
$isNormalCount = TrinaryLogic::createYes();
1059+
} else {
1060+
$mode = $scope->getType($countFuncCall->getArgs()[1]->value);
1061+
$isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->result->or($type->getIterableValueType()->isArray()->negate());
10511062
}
10521063

1053-
if (
1054-
$this->isFuncCallWithNormalCount($countFuncCall, $scope)->yes()
1055-
&& $type->isConstantArray()->yes()
1056-
) {
1057-
$resultType = TypeTraverser::map($type, function (Type $type, callable $traverse) use ($sizeType, $context) {
1058-
if ($type instanceof UnionType) {
1059-
return $traverse($type);
1060-
}
1061-
1062-
$arraySize = $type->getArraySize();
1063-
$isSize = $sizeType->isSuperTypeOf($arraySize);
1064-
if ($context->truthy() && $isSize->no()) {
1065-
return new NeverType();
1066-
}
1067-
if ($context->falsey() && !$isSize->yes()) {
1068-
return new NeverType();
1069-
}
1070-
1071-
return $this->turnListIntoConstantArray($type, $sizeType) ?? $type;
1072-
});
1073-
1074-
return $this->create($countFuncCall->getArgs()[0]->value, $resultType, $context, $scope)->setRootExpr($rootExpr);
1064+
if (!$isNormalCount->yes() || (!$type->isConstantArray()->yes() && !$type->isList()->yes())) {
1065+
return null;
10751066
}
10761067

1077-
return null;
1078-
}
1068+
$resultType = TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($sizeType, $context) {
1069+
if ($type instanceof UnionType) {
1070+
return $traverse($type);
1071+
}
10791072

1080-
private function turnListIntoConstantArray(Type $type, Type $sizeType): ?Type
1081-
{
1082-
if (
1083-
$type->isList()->yes()
1084-
&& $sizeType instanceof ConstantIntegerType
1085-
&& $sizeType->getValue() < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT
1086-
) {
1087-
// turn optional offsets non-optional
1088-
$valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty();
1089-
for ($i = 0; $i < $sizeType->getValue(); $i++) {
1090-
$offsetType = new ConstantIntegerType($i);
1091-
$valueTypesBuilder->setOffsetValueType($offsetType, $type->getOffsetValueType($offsetType));
1073+
$isSizeSuperTypeOfArraySize = $sizeType->isSuperTypeOf($type->getArraySize());
1074+
if ($context->truthy() && $isSizeSuperTypeOfArraySize->no()) {
1075+
return new NeverType();
1076+
}
1077+
if ($context->falsey() && !$isSizeSuperTypeOfArraySize->yes()) {
1078+
return new NeverType();
10921079
}
1093-
return $valueTypesBuilder->getArray();
1094-
}
10951080

1096-
if (
1097-
$type->isList()->yes()
1098-
&& $sizeType instanceof IntegerRangeType
1099-
&& $sizeType->getMin() !== null
1100-
) {
1101-
// turn optional offsets non-optional
1102-
$valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty();
1103-
for ($i = 0; $i < $sizeType->getMin(); $i++) {
1104-
$offsetType = new ConstantIntegerType($i);
1105-
$valueTypesBuilder->setOffsetValueType($offsetType, $type->getOffsetValueType($offsetType));
1106-
}
1107-
if ($sizeType->getMax() !== null) {
1108-
for ($i = $sizeType->getMin(); $i < $sizeType->getMax(); $i++) {
1109-
$offsetType = new ConstantIntegerType($i);
1110-
$valueTypesBuilder->setOffsetValueType($offsetType, $type->getOffsetValueType($offsetType), true);
1111-
}
1112-
} elseif ($type->isConstantArray()->yes()) {
1113-
for ($i = $sizeType->getMin();; $i++) {
1114-
$offsetType = new ConstantIntegerType($i);
1115-
$hasOffset = $type->hasOffsetValueType($offsetType);
1116-
if ($hasOffset->no()) {
1117-
break;
1081+
if ($type->isList()->yes()) {
1082+
if (
1083+
$sizeType instanceof ConstantIntegerType
1084+
&& $sizeType->getValue() < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT
1085+
) {
1086+
// turn optional offsets non-optional
1087+
$valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty();
1088+
for ($i = 0; $i < $sizeType->getValue(); $i++) {
1089+
$offsetType = new ConstantIntegerType($i);
1090+
$valueTypesBuilder->setOffsetValueType($offsetType, $type->getOffsetValueType($offsetType));
11181091
}
1119-
$valueTypesBuilder->setOffsetValueType($offsetType, $type->getOffsetValueType($offsetType), !$hasOffset->yes());
1092+
return $valueTypesBuilder->getArray();
11201093
}
1121-
} else {
1122-
return null;
1123-
}
11241094

1125-
$arrayType = $valueTypesBuilder->getArray();
1126-
if ($arrayType->isIterableAtLeastOnce()->yes()) {
1127-
return $arrayType;
1128-
}
1129-
}
1095+
if (
1096+
$sizeType instanceof IntegerRangeType
1097+
&& $sizeType->getMin() !== null
1098+
) {
1099+
// turn optional offsets non-optional
1100+
$valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty();
1101+
for ($i = 0; $i < $sizeType->getMin(); $i++) {
1102+
$offsetType = new ConstantIntegerType($i);
1103+
$valueTypesBuilder->setOffsetValueType($offsetType, $type->getOffsetValueType($offsetType));
1104+
}
1105+
if ($sizeType->getMax() !== null) {
1106+
for ($i = $sizeType->getMin(); $i < $sizeType->getMax(); $i++) {
1107+
$offsetType = new ConstantIntegerType($i);
1108+
$valueTypesBuilder->setOffsetValueType($offsetType, $type->getOffsetValueType($offsetType), true);
1109+
}
1110+
} elseif ($type->isConstantArray()->yes()) {
1111+
for ($i = $sizeType->getMin();; $i++) {
1112+
$offsetType = new ConstantIntegerType($i);
1113+
$hasOffset = $type->hasOffsetValueType($offsetType);
1114+
if ($hasOffset->no()) {
1115+
break;
1116+
}
1117+
$valueTypesBuilder->setOffsetValueType($offsetType, $type->getOffsetValueType($offsetType), !$hasOffset->yes());
1118+
}
1119+
} else {
1120+
return TypeCombinator::intersect($type, new NonEmptyArrayType());
1121+
}
11301122

1131-
return null;
1132-
}
1123+
return $valueTypesBuilder->getArray();
1124+
}
11331125

1134-
private function isFuncCallWithNormalCount(FuncCall $countFuncCall, Scope $scope): TrinaryLogic
1135-
{
1136-
$argType = $scope->getType($countFuncCall->getArgs()[0]->value);
1126+
return $type;
1127+
}
11371128

1138-
if (count($countFuncCall->getArgs()) === 1) {
1139-
return TrinaryLogic::createYes();
1140-
}
1141-
$mode = $scope->getType($countFuncCall->getArgs()[1]->value);
1129+
return TypeCombinator::intersect($type, new NonEmptyArrayType());
1130+
});
11421131

1143-
return (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->result->or($argType->getIterableValueType()->isArray()->negate());
1132+
return $this->create($countFuncCall->getArgs()[0]->value, $resultType, $context, $scope)->setRootExpr($rootExpr);
11441133
}
11451134

11461135
private function specifyTypesForConstantBinaryExpression(
@@ -2177,30 +2166,15 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
21772166
return $specifiedTypes;
21782167
}
21792168

2180-
if ($context->truthy()) {
2181-
if ($argType->isArray()->yes()) {
2182-
if (
2183-
$argType->isConstantArray()->yes()
2184-
&& $rightType->isSuperTypeOf($argType->getArraySize())->no()
2185-
) {
2186-
return $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NeverType(), $context, $scope)->setRootExpr($expr);
2187-
}
2188-
2189-
$funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, $scope)->setRootExpr($expr);
2190-
$isNormalCount = $this->isFuncCallWithNormalCount($unwrappedLeftExpr, $scope);
2191-
$constArray = $isNormalCount->yes() ? $this->turnListIntoConstantArray($argType, $rightType) : null;
2192-
if ($constArray !== null) {
2193-
return $funcTypes->unionWith(
2194-
$this->create($unwrappedLeftExpr->getArgs()[0]->value, $constArray, $context, $scope)->setRootExpr($expr),
2195-
);
2196-
} elseif (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($rightType)->yes()) {
2197-
return $funcTypes->unionWith(
2198-
$this->create($unwrappedLeftExpr->getArgs()[0]->value, new NonEmptyArrayType(), $context, $scope)->setRootExpr($expr),
2199-
);
2200-
}
2201-
2202-
return $funcTypes;
2169+
if ($context->truthy() && $argType->isArray()->yes()) {
2170+
$funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, $scope)->setRootExpr($expr);
2171+
if (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($rightType)->yes()) {
2172+
return $funcTypes->unionWith(
2173+
$this->create($unwrappedLeftExpr->getArgs()[0]->value, new NonEmptyArrayType(), $context, $scope)->setRootExpr($expr),
2174+
);
22032175
}
2176+
2177+
return $funcTypes;
22042178
}
22052179
}
22062180

‎tests/PHPStan/Analyser/nsrt/bug11480.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public function intUnionCount(): void
8484
if (count($x) >= $count) {
8585
assertType("array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
8686
} else {
87-
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
87+
assertType("array{}", $x);
8888
}
8989
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
9090
}

0 commit comments

Comments
 (0)