From 43d6df286f1447e78e6ba85823ca7ceeb43622b9 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 22 Sep 2024 20:38:37 +0200 Subject: [PATCH 1/2] More precise mixed-type subtraction --- src/Type/MixedType.php | 28 +++++++++++++++ .../Analyser/nsrt/non-empty-string.php | 36 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 1539b6d3f4..7579a9ebab 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -27,7 +27,9 @@ use PHPStan\Type\Accessory\OversizedArrayType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -526,6 +528,32 @@ public function toFloat(): Type public function toString(): Type { + if ($this->subtractedType !== null) { + $castsToEmptyString = new UnionType([ + new NullType(), + new ConstantBooleanType(false), + new ConstantStringType(''), + ]); + if ($this->subtractedType->isSuperTypeOf($castsToEmptyString)->yes()) { + $accessories = [ + new StringType(), + new AccessoryNonEmptyStringType(), + ]; + + $castsToZeroString = new UnionType([ + new ConstantFloatType(0.0), + new ConstantStringType('0'), + new ConstantIntegerType(0), + ]); + if ($this->subtractedType->isSuperTypeOf($castsToZeroString)->yes()) { + $accessories[] = new AccessoryNonFalsyStringType(); + } + return new IntersectionType( + $accessories, + ); + } + } + return new StringType(); } diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string.php b/tests/PHPStan/Analyser/nsrt/non-empty-string.php index 976071cb84..78e536a735 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string.php @@ -408,4 +408,40 @@ function multiplesPrintfFormats(string $s) { assertType('non-empty-string', sprintf($nonEmpty, $s)); assertType('non-falsy-string', sprintf($nonFalsy, $s)); } + + function subtract($m) { + if ($m) { + assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $m); + assertType('non-falsy-string', (string) $m); + } + if ($m != '') { + assertType("mixed", $m); + assertType('string', (string) $m); + } + if ($m !== '') { + assertType("mixed~''", $m); + assertType('string', (string) $m); + } + if (!is_string($m)) { + assertType("mixed~string", $m); + assertType('string', (string) $m); + } + + if ($m !== true) { + assertType("mixed~true", $m); + assertType('string', (string) $m); + } + if ($m !== false) { + assertType("mixed~false", $m); + assertType('string', (string) $m); + } + if ($m !== false && $m !== '' && $m !== null) { + assertType("mixed~(''|false|null)", $m); + assertType('non-empty-string', (string) $m); + } + if (!is_bool($m) && $m !== '' && $m !== null) { + assertType("mixed~(''|bool|null)", $m); + assertType('non-empty-string', (string) $m); + } + } } From 30b5653b8a3f180afb62f2de7af89f9c34e58ae2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 22 Sep 2024 20:57:37 +0200 Subject: [PATCH 2/2] Update MixedType.php --- src/Type/MixedType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 7579a9ebab..86c5ee6626 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -506,7 +506,7 @@ public function toInteger(): Type new ConstantIntegerType(0), new ConstantArrayType([], []), new StringType(), - new FloatType(), + new FloatType(), // every 0.x float casts to int(0) ]); if ( $this->subtractedType !== null