Skip to content

Commit 386da24

Browse files
authored
Comparison with strtolower() etc. leads lower/upper-case-string (#4857)
1 parent a492601 commit 386da24

File tree

2 files changed

+93
-4
lines changed

2 files changed

+93
-4
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@
3535
use PHPStan\ShouldNotHappenException;
3636
use PHPStan\TrinaryLogic;
3737
use PHPStan\Type\Accessory\AccessoryArrayListType;
38+
use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
3839
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
3940
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
41+
use PHPStan\Type\Accessory\AccessoryUppercaseStringType;
4042
use PHPStan\Type\Accessory\HasOffsetType;
4143
use PHPStan\Type\Accessory\HasPropertyType;
4244
use PHPStan\Type\Accessory\NonEmptyArrayType;
@@ -2437,21 +2439,39 @@ private function resolveNormalizedIdentical(Expr\BinaryOp\Identical $expr, Scope
24372439
$argType = $scope->getType($unwrappedLeftExpr->getArgs()[0]->value);
24382440

24392441
if ($argType->isString()->yes()) {
2442+
$specifiedTypes = new SpecifiedTypes();
2443+
if (in_array(strtolower($unwrappedLeftExpr->name->toString()), ['strtolower', 'mb_strtolower'], true)) {
2444+
$specifiedTypes = $this->create(
2445+
$unwrappedRightExpr,
2446+
TypeCombinator::intersect($rightType, new AccessoryLowercaseStringType()),
2447+
$context,
2448+
$scope,
2449+
)->setRootExpr($expr);
2450+
}
2451+
if (in_array(strtolower($unwrappedLeftExpr->name->toString()), ['strtoupper', 'mb_strtoupper'], true)) {
2452+
$specifiedTypes = $this->create(
2453+
$unwrappedRightExpr,
2454+
TypeCombinator::intersect($rightType, new AccessoryUppercaseStringType()),
2455+
$context,
2456+
$scope,
2457+
)->setRootExpr($expr);
2458+
}
2459+
24402460
if ($rightType->isNonFalsyString()->yes()) {
2441-
return $this->create(
2461+
return $specifiedTypes->unionWith($this->create(
24422462
$unwrappedLeftExpr->getArgs()[0]->value,
24432463
TypeCombinator::intersect($argType, new AccessoryNonFalsyStringType()),
24442464
$context,
24452465
$scope,
2446-
)->setRootExpr($expr);
2466+
)->setRootExpr($expr));
24472467
}
24482468

2449-
return $this->create(
2469+
return $specifiedTypes->unionWith($this->create(
24502470
$unwrappedLeftExpr->getArgs()[0]->value,
24512471
TypeCombinator::intersect($argType, new AccessoryNonEmptyStringType()),
24522472
$context,
24532473
$scope,
2454-
)->setRootExpr($expr);
2474+
)->setRootExpr($expr));
24552475
}
24562476
}
24572477

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
namespace Bug14047;
4+
5+
use function is_numeric;
6+
use function PHPStan\Testing\assertType;
7+
8+
function test_strings(string $a, string $b): void
9+
{
10+
if (strtolower($a) !== $b) {
11+
assertType('string', $a);
12+
assertType('string', $b);
13+
}
14+
15+
if ($a !== '' && strtolower($a) == $b) {
16+
assertType('non-empty-string', $a);
17+
assertType('lowercase-string&non-empty-string', $b);
18+
}
19+
20+
if ($a !== '' && strtolower($a) === $b) {
21+
assertType('non-empty-string', $a);
22+
assertType('lowercase-string&non-empty-string', $b);
23+
}
24+
25+
if ($a !== '' && strtolower($a) == $b && $b !== '0') {
26+
assertType('non-empty-string', $a);
27+
assertType('lowercase-string&non-falsy-string', $b);
28+
}
29+
30+
if ($a !== '' && strtolower($a) === $a) {
31+
assertType('lowercase-string&non-empty-string', $a);
32+
} elseif ($a !== '' && strtoupper($a) === $a) {
33+
assertType('non-empty-string&uppercase-string', $a);
34+
}
35+
36+
if ($a !== '') {
37+
if (strtolower($a) === $a) {
38+
assertType('lowercase-string&non-empty-string', $a);
39+
}
40+
}
41+
42+
if (strtolower($b) === $b && $b !== '') {
43+
assertType('lowercase-string&non-empty-string', $b);
44+
} elseif (strtoupper($b) === $b && $b !== '') {
45+
assertType('non-empty-string&uppercase-string', $b);
46+
}
47+
48+
if ($b !== '' && is_numeric($b)) {
49+
assertType('non-empty-string&numeric-string', $b);
50+
}
51+
52+
if (strtolower($b) === $b && $b !== '' && is_numeric($b)) {
53+
assertType('lowercase-string&non-empty-string&numeric-string', $b);
54+
}
55+
56+
assertType('string', $b);
57+
if (is_numeric($b)) {
58+
assertType('numeric-string', $b);
59+
if (strtolower($b) === $b) {
60+
assertType('lowercase-string&non-empty-string&numeric-string', $b);
61+
if ($b !== '') {
62+
assertType('lowercase-string&non-empty-string&numeric-string', $b);
63+
}
64+
}
65+
if ($b !== '') {
66+
assertType('numeric-string', $b);
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)