Skip to content

Commit 45a0160

Browse files
staabmondrejmirtes
authored andcommitted
Improve loose comparison on string types
1 parent e36bb83 commit 45a0160

7 files changed

+156
-2
lines changed

src/Type/Accessory/AccessoryNonEmptyStringType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,14 @@ public function isScalar(): TrinaryLogic
323323

324324
public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
325325
{
326+
if ($type->isNull()->yes()) {
327+
return new ConstantBooleanType(false);
328+
}
329+
326330
if ($type->isString()->yes() && $type->isNonEmptyString()->no()) {
327331
return new ConstantBooleanType(false);
328332
}
333+
329334
return new BooleanType();
330335
}
331336

src/Type/Accessory/AccessoryNonFalsyStringType.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use PHPStan\Type\BooleanType;
1212
use PHPStan\Type\CompoundType;
1313
use PHPStan\Type\Constant\ConstantArrayType;
14+
use PHPStan\Type\Constant\ConstantBooleanType;
1415
use PHPStan\Type\Constant\ConstantIntegerType;
1516
use PHPStan\Type\ErrorType;
1617
use PHPStan\Type\FloatType;
@@ -19,6 +20,7 @@
1920
use PHPStan\Type\IntersectionType;
2021
use PHPStan\Type\IsSuperTypeOfResult;
2122
use PHPStan\Type\ObjectWithoutClassType;
23+
use PHPStan\Type\StaticTypeFactory;
2224
use PHPStan\Type\StringType;
2325
use PHPStan\Type\Traits\MaybeCallableTypeTrait;
2426
use PHPStan\Type\Traits\NonArrayTypeTrait;
@@ -322,6 +324,11 @@ public function isScalar(): TrinaryLogic
322324

323325
public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
324326
{
327+
$falseyTypes = StaticTypeFactory::falsey();
328+
if ($falseyTypes->isSuperTypeOf($type)->yes()) {
329+
return new ConstantBooleanType(false);
330+
}
331+
325332
return new BooleanType();
326333
}
327334

src/Type/Accessory/AccessoryNumericStringType.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use PHPStan\Type\BooleanType;
1212
use PHPStan\Type\CompoundType;
1313
use PHPStan\Type\Constant\ConstantArrayType;
14+
use PHPStan\Type\Constant\ConstantBooleanType;
1415
use PHPStan\Type\Constant\ConstantIntegerType;
1516
use PHPStan\Type\Constant\ConstantStringType;
1617
use PHPStan\Type\ErrorType;
@@ -324,6 +325,14 @@ public function isScalar(): TrinaryLogic
324325

325326
public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
326327
{
328+
if ($type->isNull()->yes()) {
329+
return new ConstantBooleanType(false);
330+
}
331+
332+
if ($type->isString()->yes() && $type->isNumericString()->no()) {
333+
return new ConstantBooleanType(false);
334+
}
335+
327336
return new BooleanType();
328337
}
329338

src/Type/StringType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\TrinaryLogic;
1111
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
1212
use PHPStan\Type\Constant\ConstantArrayType;
13+
use PHPStan\Type\Constant\ConstantBooleanType;
1314
use PHPStan\Type\Constant\ConstantIntegerType;
1415
use PHPStan\Type\Constant\ConstantStringType;
1516
use PHPStan\Type\Traits\MaybeCallableTypeTrait;
@@ -267,6 +268,10 @@ public function isScalar(): TrinaryLogic
267268

268269
public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
269270
{
271+
if ($type->isArray()->yes()) {
272+
return new ConstantBooleanType(false);
273+
}
274+
270275
return new BooleanType();
271276
}
272277

tests/PHPStan/Analyser/nsrt/loose-comparisons-php7.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,15 @@ public function sayInt(
6161
assertType('bool', $int == $phpStr);
6262
assertType('bool', $int == 'a');
6363
}
64+
65+
/**
66+
* @param "abc"|"def" $constNonFalsy
67+
*/
68+
public function sayConstUnion(
69+
$constNonFalsy,
70+
): void
71+
{
72+
assertType('true', $constNonFalsy == 0);
73+
assertType('true', "" == 0);
74+
}
6475
}

tests/PHPStan/Analyser/nsrt/loose-comparisons-php8.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,15 @@ public function sayInt(
6868
assertType('false', $intRange == 'a');
6969
}
7070

71+
/**
72+
* @param "abc"|"def" $constNonFalsy
73+
*/
74+
public function sayConstUnion(
75+
$constNonFalsy,
76+
): void
77+
{
78+
assertType('false', $constNonFalsy == 0);
79+
assertType('false', "" == 0);
80+
}
81+
7182
}

tests/PHPStan/Analyser/nsrt/loose-comparisons.php

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ public function sayEmptyArray(
526526
* @param array{} $emptyArr
527527
* @param 'php' $phpStr
528528
* @param '' $emptyStr
529+
* @param non-falsy-string $nonFalsyString
529530
*/
530531
public function sayNonFalsyStr(
531532
$true,
@@ -540,7 +541,8 @@ public function sayNonFalsyStr(
540541
$null,
541542
$emptyArr,
542543
$phpStr,
543-
$emptyStr
544+
$emptyStr,
545+
$nonFalsyString
544546
): void
545547
{
546548
assertType('true', $phpStr == $true);
@@ -555,6 +557,100 @@ public function sayNonFalsyStr(
555557
assertType('false', $phpStr == $emptyArr);
556558
assertType('true', $phpStr == $phpStr);
557559
assertType('false', $phpStr == $emptyStr);
560+
561+
assertType('bool', $nonFalsyString == $true);
562+
assertType('false', $nonFalsyString == $false);
563+
assertType('bool', $nonFalsyString == $one);
564+
assertType('false', $nonFalsyString == $zero);
565+
assertType('bool', $nonFalsyString == $minusOne);
566+
assertType('bool', $nonFalsyString == $oneStr);
567+
assertType('false', $nonFalsyString == $zeroStr);
568+
assertType('bool', $nonFalsyString == $minusOneStr);
569+
assertType('bool', $nonFalsyString == $plusOneStr);
570+
assertType('false', $nonFalsyString == $null);
571+
assertType('false', $nonFalsyString == $emptyArr);
572+
assertType('bool', $nonFalsyString == $phpStr);
573+
assertType('false', $nonFalsyString == $emptyStr);
574+
}
575+
576+
/**
577+
* @param true $true
578+
* @param false $false
579+
* @param 1 $one
580+
* @param 0 $zero
581+
* @param -1 $minusOne
582+
* @param '1' $oneStr
583+
* @param '0' $zeroStr
584+
* @param '-1' $minusOneStr
585+
* @param '+1' $plusOneStr
586+
* @param null $null
587+
* @param array{} $emptyArr
588+
* @param 'php' $phpStr
589+
* @param '' $emptyStr
590+
* @param numeric-string $numericStr
591+
*/
592+
public function sayStr(
593+
$true,
594+
$false,
595+
$one,
596+
$zero,
597+
$minusOne,
598+
$oneStr,
599+
$zeroStr,
600+
$minusOneStr,
601+
$plusOneStr,
602+
$null,
603+
$emptyArr,
604+
string $string,
605+
$phpStr,
606+
$emptyStr,
607+
$numericStr,
608+
?string $stringOrNull,
609+
): void
610+
{
611+
assertType('bool', $string == $true);
612+
assertType('bool', $string == $false);
613+
assertType('bool', $string == $one);
614+
assertType('bool', $string == $zero);
615+
assertType('bool', $string == $minusOne);
616+
assertType('bool', $string == $oneStr);
617+
assertType('bool', $string == $zeroStr);
618+
assertType('bool', $string == $minusOneStr);
619+
assertType('bool', $string == $plusOneStr);
620+
assertType('bool', $string == $null);
621+
assertType('bool', $string == $stringOrNull);
622+
assertType('false', $string == $emptyArr);
623+
assertType('bool', $string == $phpStr);
624+
assertType('bool', $string == $emptyStr);
625+
assertType('bool', $string == $numericStr);
626+
627+
assertType('bool', $numericStr == $true);
628+
assertType('bool', $numericStr == $false);
629+
assertType('bool', $numericStr == $one);
630+
assertType('bool', $numericStr == $zero);
631+
assertType('bool', $numericStr == $minusOne);
632+
assertType('bool', $numericStr == $oneStr);
633+
assertType('bool', $numericStr == $zeroStr);
634+
assertType('bool', $numericStr == $minusOneStr);
635+
assertType('bool', $numericStr == $plusOneStr);
636+
assertType('false', $numericStr == $null);
637+
assertType('bool', $numericStr == $stringOrNull);
638+
assertType('false', $numericStr == $emptyArr);
639+
assertType('bool', $numericStr == $string);
640+
assertType('false', $numericStr == $phpStr);
641+
assertType('false', $numericStr == $emptyStr);
642+
if (is_numeric($string)) {
643+
assertType('bool', $numericStr == $string);
644+
}
645+
646+
assertType('false', "" == 1);
647+
assertType('true', "" == null);
648+
assertType('false', "" == true);
649+
assertType('true', "" == false);
650+
assertType('false', "" == "1");
651+
assertType('false', "" == "0");
652+
assertType('false', "" == "-1");
653+
assertType('false', "" == []);
558654
}
559655

560656
/**
@@ -657,11 +753,13 @@ public function sayInt(
657753
* @param true|1|"1" $looseOne
658754
* @param false|0|"0" $looseZero
659755
* @param false|1 $constMix
756+
* @param "abc"|"def" $constNonFalsy
660757
*/
661758
public function sayConstUnion(
662759
$looseOne,
663760
$looseZero,
664-
$constMix
761+
$constMix,
762+
$constNonFalsy,
665763
): void
666764
{
667765
assertType('true', $looseOne == 1);
@@ -696,6 +794,14 @@ public function sayConstUnion(
696794
assertType('bool', $constMix == $looseOne);
697795
assertType('bool', $looseZero == $constMix);
698796
assertType('bool', $constMix == $looseZero);
797+
798+
assertType('false', $constNonFalsy == 1);
799+
assertType('false', $constNonFalsy == null);
800+
assertType('true', $constNonFalsy == true);
801+
assertType('false', $constNonFalsy == false);
802+
assertType('false', $constNonFalsy == "1");
803+
assertType('false', $constNonFalsy == "0");
804+
assertType('false', $constNonFalsy == []);
699805
}
700806

701807
/**

0 commit comments

Comments
 (0)