Skip to content

Commit

Permalink
Narrow string on *strlen() with positive-int
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm authored Sep 19, 2024
1 parent 5582803 commit e629cee
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 44 deletions.
80 changes: 40 additions & 40 deletions src/Analyser/TypeSpecifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -1124,39 +1124,6 @@ private function specifyTypesForConstantBinaryExpression(
));
}

if (
!$context->null()
&& $exprNode instanceof FuncCall
&& count($exprNode->getArgs()) === 1
&& $exprNode->name instanceof Name
&& in_array(strtolower((string) $exprNode->name), ['strlen', 'mb_strlen'], true)
&& $constantType instanceof ConstantIntegerType
) {
if ($constantType->getValue() < 0) {
return $this->create($exprNode->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr);
}

if ($context->truthy() || $constantType->getValue() === 0) {
$newContext = $context;
if ($constantType->getValue() === 0) {
$newContext = $newContext->negate();
}
$argType = $scope->getType($exprNode->getArgs()[0]->value);
if ($argType->isString()->yes()) {
$funcTypes = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr);

$accessory = new AccessoryNonEmptyStringType();
if ($constantType->getValue() >= 2) {
$accessory = new AccessoryNonFalsyStringType();
}
$valueTypes = $this->create($exprNode->getArgs()[0]->value, $accessory, $newContext, false, $scope, $rootExpr);

return $funcTypes->unionWith($valueTypes);
}
}

}

return null;
}

Expand Down Expand Up @@ -2140,6 +2107,42 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
}
}

if (
!$context->null()
&& $unwrappedLeftExpr instanceof FuncCall
&& count($unwrappedLeftExpr->getArgs()) === 1
&& $unwrappedLeftExpr->name instanceof Name
&& in_array(strtolower((string) $unwrappedLeftExpr->name), ['strlen', 'mb_strlen'], true)
&& $rightType->isInteger()->yes()
) {
if (IntegerRangeType::fromInterval(null, -1)->isSuperTypeOf($rightType)->yes()) {
return $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr);
}

$isZero = (new ConstantIntegerType(0))->isSuperTypeOf($rightType);
if ($isZero->yes()) {
$funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, false, $scope, $rootExpr);
return $funcTypes->unionWith(
$this->create($unwrappedLeftExpr->getArgs()[0]->value, new ConstantStringType(''), $context, false, $scope, $rootExpr),
);
}

if ($context->truthy() && IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($rightType)->yes()) {
$argType = $scope->getType($unwrappedLeftExpr->getArgs()[0]->value);
if ($argType->isString()->yes()) {
$funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, false, $scope, $rootExpr);

$accessory = new AccessoryNonEmptyStringType();
if (IntegerRangeType::fromInterval(2, null)->isSuperTypeOf($rightType)->yes()) {
$accessory = new AccessoryNonFalsyStringType();
}
$valueTypes = $this->create($unwrappedLeftExpr->getArgs()[0]->value, $accessory, $context, false, $scope, $rootExpr);

return $funcTypes->unionWith($valueTypes);
}
}
}

if (
$context->true()
&& $unwrappedLeftExpr instanceof FuncCall
Expand Down Expand Up @@ -2209,14 +2212,11 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
}
}

if ($rightType->isInteger()->yes() || $rightType->isString()->yes()) {
if ($rightType->isString()->yes()) {
$types = null;
foreach ($rightType->getFiniteTypes() as $finiteType) {
if ($finiteType->isString()->yes()) {
$specifiedType = $this->specifyTypesForConstantStringBinaryExpression($unwrappedLeftExpr, $finiteType, $context, $scope, $rootExpr);
} else {
$specifiedType = $this->specifyTypesForConstantBinaryExpression($unwrappedLeftExpr, $finiteType, $context, $scope, $rootExpr);
}
foreach ($rightType->getConstantStrings() as $constantString) {
$specifiedType = $this->specifyTypesForConstantStringBinaryExpression($unwrappedLeftExpr, $constantString, $context, $scope, $rootExpr);

if ($specifiedType === null) {
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/TypeSpecifierTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1283,7 +1283,7 @@ public function dataCondition(): iterable
),
),
[
'$foo' => 'non-empty-string',
'$foo' => "string & ~''",
'strlen($foo)' => '~0',
],
[
Expand Down
14 changes: 11 additions & 3 deletions tests/PHPStan/Analyser/nsrt/strlen-int-range.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ function doFoo(string $s, $zeroToThree, $twoOrThree, $twoOrMore, int $maxThree,
}

if (strlen($s) == $oneOrMore) {
assertType('string', $s); // could be non-empty-string
assertType('non-empty-string', $s);
}
if (strlen($s) === $oneOrMore) {
assertType('string', $s); // could be non-empty-string
assertType('non-empty-string', $s);
}

if (strlen($s) == $tenOrEleven) {
Expand Down Expand Up @@ -118,12 +118,20 @@ function doFoo(string $s, $zeroToThree, $twoOrThree, $twoOrMore, int $maxThree,
* @param int<1, max> $oneOrMore
* @param int<2, max> $twoOrMore
*/
function doFooBar(array $arr, int $oneOrMore, int $twoOrMore): void
function doFooBar(string $s, array $arr, int $oneOrMore, int $twoOrMore): void
{
if (count($arr) == $oneOrMore) {
assertType('non-empty-array', $arr);
}
if (count($arr) === $twoOrMore) {
assertType('non-empty-array', $arr);
}

if (strlen($s) == $twoOrMore) {
assertType('non-falsy-string', $s);
}
if (strlen($s) === $twoOrMore) {
assertType('non-falsy-string', $s);
}

}

0 comments on commit e629cee

Please sign in to comment.