diff --git a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php index d965eb0ebc..98c6b2d7bb 100644 --- a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php +++ b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php @@ -411,18 +411,23 @@ public function isScalar(): TrinaryLogic public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { + if ($this->inverse) { + // A non-decimal-int-string may still be numeric ("02", "2.0", "1e1") + // or empty (""), so it can be loosely equal to a decimal-int-string, + // another numeric value or null. Nothing can be decided here. + return new BooleanType(); + } + + // A decimal-int-string is a numeric string, so it compares like one: + // numerically against other numeric strings (and numbers), and never + // loosely equal to null (it is always non-empty) or to a non-numeric + // string (that would be a byte-wise comparison that cannot match). if ($type->isNull()->yes()) { return new ConstantBooleanType(false); } - if ($type->isString()->yes()) { - if ($this->inverse) { - if ($type->isDecimalIntegerString()->yes()) { - return new ConstantBooleanType(false); - } - } elseif ($type->isDecimalIntegerString()->no()) { - return new ConstantBooleanType(false); - } + if ($type->isString()->yes() && $type->isNumericString()->no()) { + return new ConstantBooleanType(false); } return new BooleanType(); diff --git a/tests/PHPStan/Analyser/nsrt/bug-14793.php b/tests/PHPStan/Analyser/nsrt/bug-14793.php new file mode 100644 index 0000000000..9c7c7c4154 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-14793.php @@ -0,0 +1,40 @@ +