Skip to content

Commit 08dc679

Browse files
staabmondrejmirtes
authored andcommitted
Improve narrowing after string functions
1 parent 5379e31 commit 08dc679

File tree

2 files changed

+52
-36
lines changed

2 files changed

+52
-36
lines changed

Diff for: src/Analyser/TypeSpecifier.php

+35-35
Original file line numberDiff line numberDiff line change
@@ -1157,41 +1157,6 @@ private function specifyTypesForConstantStringBinaryExpression(
11571157
}
11581158
$constantStringValue = $scalarValues[0];
11591159

1160-
if (
1161-
$context->truthy()
1162-
&& $exprNode instanceof FuncCall
1163-
&& $exprNode->name instanceof Name
1164-
&& in_array(strtolower($exprNode->name->toString()), [
1165-
'substr', 'strstr', 'stristr', 'strchr', 'strrchr', 'strtolower', 'strtoupper', 'ucfirst', 'lcfirst',
1166-
'mb_substr', 'mb_strstr', 'mb_stristr', 'mb_strchr', 'mb_strrchr', 'mb_strtolower', 'mb_strtoupper', 'mb_ucfirst', 'mb_lcfirst',
1167-
'ucwords', 'mb_convert_case', 'mb_convert_kana',
1168-
], true)
1169-
&& isset($exprNode->getArgs()[0])
1170-
&& $constantStringValue !== ''
1171-
) {
1172-
$argType = $scope->getType($exprNode->getArgs()[0]->value);
1173-
1174-
if ($argType->isString()->yes()) {
1175-
if ($constantStringValue !== '0') {
1176-
return $this->create(
1177-
$exprNode->getArgs()[0]->value,
1178-
TypeCombinator::intersect($argType, new AccessoryNonFalsyStringType()),
1179-
$context,
1180-
false,
1181-
$scope,
1182-
);
1183-
}
1184-
1185-
return $this->create(
1186-
$exprNode->getArgs()[0]->value,
1187-
TypeCombinator::intersect($argType, new AccessoryNonEmptyStringType()),
1188-
$context,
1189-
false,
1190-
$scope,
1191-
);
1192-
}
1193-
}
1194-
11951160
if (
11961161
$exprNode instanceof FuncCall
11971162
&& $exprNode->name instanceof Name
@@ -2192,6 +2157,41 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
21922157
}
21932158
}
21942159

2160+
if (
2161+
$context->truthy()
2162+
&& $unwrappedLeftExpr instanceof FuncCall
2163+
&& $unwrappedLeftExpr->name instanceof Name
2164+
&& in_array(strtolower($unwrappedLeftExpr->name->toString()), [
2165+
'substr', 'strstr', 'stristr', 'strchr', 'strrchr', 'strtolower', 'strtoupper', 'ucfirst', 'lcfirst',
2166+
'mb_substr', 'mb_strstr', 'mb_stristr', 'mb_strchr', 'mb_strrchr', 'mb_strtolower', 'mb_strtoupper', 'mb_ucfirst', 'mb_lcfirst',
2167+
'ucwords', 'mb_convert_case', 'mb_convert_kana',
2168+
], true)
2169+
&& isset($unwrappedLeftExpr->getArgs()[0])
2170+
&& $rightType->isNonEmptyString()->yes()
2171+
) {
2172+
$argType = $scope->getType($unwrappedLeftExpr->getArgs()[0]->value);
2173+
2174+
if ($argType->isString()->yes()) {
2175+
if ($rightType->isNonFalsyString()->yes()) {
2176+
return $this->create(
2177+
$unwrappedLeftExpr->getArgs()[0]->value,
2178+
TypeCombinator::intersect($argType, new AccessoryNonFalsyStringType()),
2179+
$context,
2180+
false,
2181+
$scope,
2182+
);
2183+
}
2184+
2185+
return $this->create(
2186+
$unwrappedLeftExpr->getArgs()[0]->value,
2187+
TypeCombinator::intersect($argType, new AccessoryNonEmptyStringType()),
2188+
$context,
2189+
false,
2190+
$scope,
2191+
);
2192+
}
2193+
}
2194+
21952195
if ($rightType->isInteger()->yes() || $rightType->isString()->yes()) {
21962196
$types = null;
21972197
foreach ($rightType->getFiniteTypes() as $finiteType) {

Diff for: tests/PHPStan/Analyser/nsrt/non-empty-string-substr-specifying.php

+17-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
use function PHPStan\Testing\assertType;
66

7-
class Foo {
7+
class Foo
8+
{
89
public function nonEmptySubstr(string $s, int $offset, int $length): void
910
{
1011
if (substr($s, 10) === 'hallo') {
@@ -81,4 +82,19 @@ public function nonEmptySubstr(string $s, int $offset, int $length): void
8182
assertType('\'hallo\'', $x);
8283
}
8384
}
85+
86+
/**
87+
* @param non-empty-string $nonES
88+
* @param non-falsy-string $falsyString
89+
*/
90+
public function stringTypes(string $s, $nonES, $falsyString): void
91+
{
92+
if (substr($s, 10) === $nonES) {
93+
assertType('non-empty-string', $s);
94+
}
95+
96+
if (substr($s, 10) === $falsyString) {
97+
assertType('non-falsy-string', $s);
98+
}
99+
}
84100
}

0 commit comments

Comments
 (0)