From 7cc7b731fb2f821263b89e90ed8cd4f524727b1c Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 22 Dec 2024 13:15:04 +0100 Subject: [PATCH 1/2] Fix union with empty string --- src/Type/TypeCombinator.php | 38 +++++++++++++++++---- tests/PHPStan/Analyser/nsrt/bug-12312.php | 41 +++++++++++++++++++++++ 2 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12312.php diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 03ddffd988..cd776efa5f 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -3,8 +3,10 @@ namespace PHPStan\Type; use PHPStan\Type\Accessory\AccessoryArrayListType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\Accessory\HasOffsetValueType; use PHPStan\Type\Accessory\HasPropertyType; @@ -452,7 +454,10 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array && ($b->describe(VerbosityLevel::value()) === 'non-empty-string' || $b->describe(VerbosityLevel::value()) === 'non-falsy-string') ) { - return [null, new StringType()]; + return [null, self::intersect( + new StringType(), + ...self::getAccessoryCaseStringTypes($b), + )]; } if ( @@ -461,7 +466,10 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array && ($a->describe(VerbosityLevel::value()) === 'non-empty-string' || $a->describe(VerbosityLevel::value()) === 'non-falsy-string') ) { - return [new StringType(), null]; + return [self::intersect( + new StringType(), + ...self::getAccessoryCaseStringTypes($a), + ), null]; } if ( @@ -469,10 +477,11 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array && $a->getValue() === '0' && $b->describe(VerbosityLevel::value()) === 'non-falsy-string' ) { - return [null, new IntersectionType([ + return [null, self::intersect( new StringType(), new AccessoryNonEmptyStringType(), - ])]; + ...self::getAccessoryCaseStringTypes($b), + )]; } if ( @@ -480,15 +489,32 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array && $b->getValue() === '0' && $a->describe(VerbosityLevel::value()) === 'non-falsy-string' ) { - return [new IntersectionType([ + return [self::intersect( new StringType(), new AccessoryNonEmptyStringType(), - ]), null]; + ...self::getAccessoryCaseStringTypes($a), + ), null]; } return null; } + /** + * @return array + */ + private static function getAccessoryCaseStringTypes(Type $type): array + { + $accessory = []; + if ($type->isLowercaseString()->yes()) { + $accessory[] = new AccessoryLowercaseStringType(); + } + if ($type->isUppercaseString()->yes()) { + $accessory[] = new AccessoryUppercaseStringType(); + } + + return $accessory; + } + private static function unionWithSubtractedType( Type $type, ?Type $subtractedType, diff --git a/tests/PHPStan/Analyser/nsrt/bug-12312.php b/tests/PHPStan/Analyser/nsrt/bug-12312.php new file mode 100644 index 0000000000..d5b0bc15d3 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12312.php @@ -0,0 +1,41 @@ + Date: Mon, 23 Dec 2024 14:54:27 +0100 Subject: [PATCH 2/2] Add tests --- tests/PHPStan/Analyser/nsrt/bug-12312.php | 99 +++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug-12312.php b/tests/PHPStan/Analyser/nsrt/bug-12312.php index d5b0bc15d3..f67d1aa3cb 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12312.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12312.php @@ -17,6 +17,39 @@ public function sayLowercase(string $s): void assertType('lowercase-string', $s); } + /** + * @param lowercase-string $s + */ + public function sayLowercase2(string $s): void + { + if ('' != $s) { + assertType('lowercase-string&non-empty-string', $s); + } + assertType('lowercase-string', $s); + } + + /** + * @param lowercase-string&non-empty-string $s + */ + public function sayLowercase3(string $s): void + { + if ($s != '0') { + assertType('lowercase-string&non-falsy-string', $s); + } + assertType('lowercase-string&non-empty-string', $s); + } + + /** + * @param lowercase-string&non-empty-string $s + */ + public function sayLowercase4(string $s): void + { + if ('0' != $s) { + assertType('lowercase-string&non-falsy-string', $s); + } + assertType('lowercase-string&non-empty-string', $s); + } + /** * @param uppercase-string $s */ @@ -28,6 +61,39 @@ public function sayUppercase(string $s): void assertType('uppercase-string', $s); } + /** + * @param uppercase-string $s + */ + public function sayUppercase2(string $s): void + { + if ('' != $s) { + assertType('non-empty-string&uppercase-string', $s); + } + assertType('uppercase-string', $s); + } + + /** + * @param uppercase-string&non-empty-string $s + */ + public function sayUppercase3(string $s): void + { + if ($s != '0') { + assertType('non-falsy-string&uppercase-string', $s); + } + assertType('non-empty-string&uppercase-string', $s); + } + + /** + * @param uppercase-string&non-empty-string $s + */ + public function sayUppercase4(string $s): void + { + if ('0' != $s) { + assertType('non-falsy-string&uppercase-string', $s); + } + assertType('non-empty-string&uppercase-string', $s); + } + /** * @param lowercase-string&uppercase-string $s */ @@ -38,4 +104,37 @@ public function sayBoth(string $s): void } assertType('lowercase-string&uppercase-string', $s); } + + /** + * @param lowercase-string&uppercase-string $s + */ + public function sayBoth2(string $s): void + { + if ('' != $s) { + assertType('lowercase-string&non-empty-string&uppercase-string', $s); + } + assertType('lowercase-string&uppercase-string', $s); + } + + /** + * @param lowercase-string&uppercase-string&non-empty-string $s + */ + public function sayBoth3(string $s): void + { + if ($s != '0') { + assertType('lowercase-string&non-falsy-string&uppercase-string', $s); + } + assertType('lowercase-string&non-empty-string&uppercase-string', $s); + } + + /** + * @param lowercase-string&uppercase-string&non-empty-string $s + */ + public function sayBoth4(string $s): void + { + if ('0' != $s) { + assertType('lowercase-string&non-falsy-string&uppercase-string', $s); + } + assertType('lowercase-string&non-empty-string&uppercase-string', $s); + } }