Skip to content

Commit e629cee

Browse files
authored
Narrow string on *strlen() with positive-int
1 parent 5582803 commit e629cee

File tree

3 files changed

+52
-44
lines changed

3 files changed

+52
-44
lines changed

Diff for: src/Analyser/TypeSpecifier.php

+40-40
Original file line numberDiff line numberDiff line change
@@ -1124,39 +1124,6 @@ private function specifyTypesForConstantBinaryExpression(
11241124
));
11251125
}
11261126

1127-
if (
1128-
!$context->null()
1129-
&& $exprNode instanceof FuncCall
1130-
&& count($exprNode->getArgs()) === 1
1131-
&& $exprNode->name instanceof Name
1132-
&& in_array(strtolower((string) $exprNode->name), ['strlen', 'mb_strlen'], true)
1133-
&& $constantType instanceof ConstantIntegerType
1134-
) {
1135-
if ($constantType->getValue() < 0) {
1136-
return $this->create($exprNode->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr);
1137-
}
1138-
1139-
if ($context->truthy() || $constantType->getValue() === 0) {
1140-
$newContext = $context;
1141-
if ($constantType->getValue() === 0) {
1142-
$newContext = $newContext->negate();
1143-
}
1144-
$argType = $scope->getType($exprNode->getArgs()[0]->value);
1145-
if ($argType->isString()->yes()) {
1146-
$funcTypes = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr);
1147-
1148-
$accessory = new AccessoryNonEmptyStringType();
1149-
if ($constantType->getValue() >= 2) {
1150-
$accessory = new AccessoryNonFalsyStringType();
1151-
}
1152-
$valueTypes = $this->create($exprNode->getArgs()[0]->value, $accessory, $newContext, false, $scope, $rootExpr);
1153-
1154-
return $funcTypes->unionWith($valueTypes);
1155-
}
1156-
}
1157-
1158-
}
1159-
11601127
return null;
11611128
}
11621129

@@ -2140,6 +2107,42 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
21402107
}
21412108
}
21422109

2110+
if (
2111+
!$context->null()
2112+
&& $unwrappedLeftExpr instanceof FuncCall
2113+
&& count($unwrappedLeftExpr->getArgs()) === 1
2114+
&& $unwrappedLeftExpr->name instanceof Name
2115+
&& in_array(strtolower((string) $unwrappedLeftExpr->name), ['strlen', 'mb_strlen'], true)
2116+
&& $rightType->isInteger()->yes()
2117+
) {
2118+
if (IntegerRangeType::fromInterval(null, -1)->isSuperTypeOf($rightType)->yes()) {
2119+
return $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr);
2120+
}
2121+
2122+
$isZero = (new ConstantIntegerType(0))->isSuperTypeOf($rightType);
2123+
if ($isZero->yes()) {
2124+
$funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, false, $scope, $rootExpr);
2125+
return $funcTypes->unionWith(
2126+
$this->create($unwrappedLeftExpr->getArgs()[0]->value, new ConstantStringType(''), $context, false, $scope, $rootExpr),
2127+
);
2128+
}
2129+
2130+
if ($context->truthy() && IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($rightType)->yes()) {
2131+
$argType = $scope->getType($unwrappedLeftExpr->getArgs()[0]->value);
2132+
if ($argType->isString()->yes()) {
2133+
$funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, false, $scope, $rootExpr);
2134+
2135+
$accessory = new AccessoryNonEmptyStringType();
2136+
if (IntegerRangeType::fromInterval(2, null)->isSuperTypeOf($rightType)->yes()) {
2137+
$accessory = new AccessoryNonFalsyStringType();
2138+
}
2139+
$valueTypes = $this->create($unwrappedLeftExpr->getArgs()[0]->value, $accessory, $context, false, $scope, $rootExpr);
2140+
2141+
return $funcTypes->unionWith($valueTypes);
2142+
}
2143+
}
2144+
}
2145+
21432146
if (
21442147
$context->true()
21452148
&& $unwrappedLeftExpr instanceof FuncCall
@@ -2209,14 +2212,11 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
22092212
}
22102213
}
22112214

2212-
if ($rightType->isInteger()->yes() || $rightType->isString()->yes()) {
2215+
if ($rightType->isString()->yes()) {
22132216
$types = null;
2214-
foreach ($rightType->getFiniteTypes() as $finiteType) {
2215-
if ($finiteType->isString()->yes()) {
2216-
$specifiedType = $this->specifyTypesForConstantStringBinaryExpression($unwrappedLeftExpr, $finiteType, $context, $scope, $rootExpr);
2217-
} else {
2218-
$specifiedType = $this->specifyTypesForConstantBinaryExpression($unwrappedLeftExpr, $finiteType, $context, $scope, $rootExpr);
2219-
}
2217+
foreach ($rightType->getConstantStrings() as $constantString) {
2218+
$specifiedType = $this->specifyTypesForConstantStringBinaryExpression($unwrappedLeftExpr, $constantString, $context, $scope, $rootExpr);
2219+
22202220
if ($specifiedType === null) {
22212221
continue;
22222222
}

Diff for: tests/PHPStan/Analyser/TypeSpecifierTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -1283,7 +1283,7 @@ public function dataCondition(): iterable
12831283
),
12841284
),
12851285
[
1286-
'$foo' => 'non-empty-string',
1286+
'$foo' => "string & ~''",
12871287
'strlen($foo)' => '~0',
12881288
],
12891289
[

Diff for: tests/PHPStan/Analyser/nsrt/strlen-int-range.php

+11-3
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ function doFoo(string $s, $zeroToThree, $twoOrThree, $twoOrMore, int $maxThree,
6969
}
7070

7171
if (strlen($s) == $oneOrMore) {
72-
assertType('string', $s); // could be non-empty-string
72+
assertType('non-empty-string', $s);
7373
}
7474
if (strlen($s) === $oneOrMore) {
75-
assertType('string', $s); // could be non-empty-string
75+
assertType('non-empty-string', $s);
7676
}
7777

7878
if (strlen($s) == $tenOrEleven) {
@@ -118,12 +118,20 @@ function doFoo(string $s, $zeroToThree, $twoOrThree, $twoOrMore, int $maxThree,
118118
* @param int<1, max> $oneOrMore
119119
* @param int<2, max> $twoOrMore
120120
*/
121-
function doFooBar(array $arr, int $oneOrMore, int $twoOrMore): void
121+
function doFooBar(string $s, array $arr, int $oneOrMore, int $twoOrMore): void
122122
{
123123
if (count($arr) == $oneOrMore) {
124124
assertType('non-empty-array', $arr);
125125
}
126126
if (count($arr) === $twoOrMore) {
127127
assertType('non-empty-array', $arr);
128128
}
129+
130+
if (strlen($s) == $twoOrMore) {
131+
assertType('non-falsy-string', $s);
132+
}
133+
if (strlen($s) === $twoOrMore) {
134+
assertType('non-falsy-string', $s);
135+
}
136+
129137
}

0 commit comments

Comments
 (0)