diff --git a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php index acaa7a757f3..b1c29178ca5 100644 --- a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php +++ b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php @@ -133,6 +133,7 @@ public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult if ( ( $otherType instanceof AccessoryNumericStringType + || $otherType instanceof AccessoryNonEmptyStringType || $otherType instanceof AccessoryLowercaseStringType || $otherType instanceof AccessoryUppercaseStringType ) diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 2ae309ce906..184e5de1bbc 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -113,6 +113,10 @@ public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult return $otherType->isSuperTypeOf($this); } + if ($otherType instanceof AccessoryNonEmptyStringType) { + return IsSuperTypeOfResult::createYes(); + } + return IsSuperTypeOfResult::createFromTrinaryLogic( $otherType->isNumericString()->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()), ); diff --git a/tests/PHPStan/Analyser/nsrt/bcmath-number.php b/tests/PHPStan/Analyser/nsrt/bcmath-number.php index 139151c258f..3f06cff6c7e 100644 --- a/tests/PHPStan/Analyser/nsrt/bcmath-number.php +++ b/tests/PHPStan/Analyser/nsrt/bcmath-number.php @@ -186,7 +186,7 @@ public function bcVsNull(Number $a): void assertType('*ERROR*', $a * $b); assertType('*ERROR*', $a / $b); assertType('*ERROR*', $a % $b); - assertType('non-empty-string&numeric-string', $a . $b); + assertType('numeric-string', $a . $b); assertType('*ERROR*', $a ** $b); assertType('*ERROR*', $a << $b); assertType('*ERROR*', $a >> $b); diff --git a/tests/PHPStan/Analyser/nsrt/bug-12312.php b/tests/PHPStan/Analyser/nsrt/bug-12312.php index 8b88a298929..231bec32e34 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12312.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12312.php @@ -36,7 +36,7 @@ public function sayLowercase3(string $s): void if ($s != '0') { assertType('lowercase-string&non-falsy-string', $s); } - assertType('(lowercase-string&non-empty-string&numeric-string)|(lowercase-string&non-falsy-string)', $s); + assertType('(lowercase-string&non-falsy-string)|(lowercase-string&numeric-string)', $s); } /** @@ -47,7 +47,7 @@ public function sayLowercase4(string $s): void if ('0' != $s) { assertType('lowercase-string&non-falsy-string', $s); } - assertType('(lowercase-string&non-empty-string&numeric-string)|(lowercase-string&non-falsy-string)', $s); + assertType('(lowercase-string&non-falsy-string)|(lowercase-string&numeric-string)', $s); } /** @@ -80,7 +80,7 @@ public function sayUppercase3(string $s): void if ($s != '0') { assertType('non-falsy-string&uppercase-string', $s); } - assertType('(non-empty-string&numeric-string&uppercase-string)|(non-falsy-string&uppercase-string)', $s); + assertType('(non-falsy-string&uppercase-string)|(numeric-string&uppercase-string)', $s); } /** @@ -91,7 +91,7 @@ public function sayUppercase4(string $s): void if ('0' != $s) { assertType('non-falsy-string&uppercase-string', $s); } - assertType('(non-empty-string&numeric-string&uppercase-string)|(non-falsy-string&uppercase-string)', $s); + assertType('(non-falsy-string&uppercase-string)|(numeric-string&uppercase-string)', $s); } /** @@ -124,7 +124,7 @@ public function sayBoth3(string $s): void if ($s != '0') { assertType('lowercase-string&non-falsy-string&uppercase-string', $s); } - assertType('(lowercase-string&non-empty-string&numeric-string&uppercase-string)|(lowercase-string&non-falsy-string&uppercase-string)', $s); + assertType('(lowercase-string&non-falsy-string&uppercase-string)|(lowercase-string&numeric-string&uppercase-string)', $s); } /** @@ -135,6 +135,6 @@ public function sayBoth4(string $s): void if ('0' != $s) { assertType('lowercase-string&non-falsy-string&uppercase-string', $s); } - assertType('(lowercase-string&non-empty-string&numeric-string&uppercase-string)|(lowercase-string&non-falsy-string&uppercase-string)', $s); + assertType('(lowercase-string&non-falsy-string&uppercase-string)|(lowercase-string&numeric-string&uppercase-string)', $s); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12393b-php84.php b/tests/PHPStan/Analyser/nsrt/bug-12393b-php84.php index ae1946cdb2c..9aeb417adc3 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12393b-php84.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12393b-php84.php @@ -13,7 +13,7 @@ class StringableFoo { // https://3v4l.org/nelJF#v8.4.6 public function doFoo3(\BcMath\Number $foo): void { $this->foo = $foo; - assertType('non-empty-string&numeric-string', $this->foo); + assertType('numeric-string', $this->foo); } public function __toString(): string { diff --git a/tests/PHPStan/Analyser/nsrt/bug-12393b.php b/tests/PHPStan/Analyser/nsrt/bug-12393b.php index 36fafdc7b52..1c644bb77e4 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12393b.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12393b.php @@ -472,7 +472,7 @@ class FooNumericToString public function doFoo(float|int $b): void { $this->foo = $b; - assertType('decimal-int-string|(non-empty-string&uppercase-string)', $this->foo); + assertType('non-empty-string&uppercase-string', $this->foo); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-14047.php b/tests/PHPStan/Analyser/nsrt/bug-14047.php index e30dea53ad5..3714af54ddb 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-14047.php +++ b/tests/PHPStan/Analyser/nsrt/bug-14047.php @@ -46,20 +46,20 @@ function test_strings(string $a, string $b): void } if ($b !== '' && is_numeric($b)) { - assertType('non-empty-string&numeric-string', $b); + assertType('numeric-string', $b); } if (strtolower($b) === $b && $b !== '' && is_numeric($b)) { - assertType('lowercase-string&non-empty-string&numeric-string', $b); + assertType('lowercase-string&numeric-string', $b); } assertType('string', $b); if (is_numeric($b)) { assertType('numeric-string', $b); if (strtolower($b) === $b) { - assertType('lowercase-string&non-empty-string&numeric-string', $b); + assertType('lowercase-string&numeric-string', $b); if ($b !== '') { - assertType('lowercase-string&non-empty-string&numeric-string', $b); + assertType('lowercase-string&numeric-string', $b); } } if ($b !== '') { diff --git a/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php b/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php index dae31d84833..941693a46e6 100644 --- a/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php +++ b/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php @@ -17,7 +17,7 @@ function foo(int $a, float $b, $numeric, $numeric2, $number, $positive, $negativ assertType('non-empty-string&uppercase-string', (string)$b); assertType('(non-empty-string&uppercase-string)|numeric-string', (string)$numeric); assertType('(non-empty-string&uppercase-string)|numeric-string', (string)$numeric2); - assertType('decimal-int-string|(non-empty-string&uppercase-string)', (string)$number); + assertType('non-empty-string&uppercase-string', (string)$number); assertType('decimal-int-string&non-falsy-string', (string)$positive); assertType('decimal-int-string&non-falsy-string', (string)$negative); assertType("'1'", (string)$constantInt); @@ -36,7 +36,7 @@ function concatEmptyString(int $a, float $b, $numeric, $numeric2, $number, $posi assertType('non-empty-string&uppercase-string', '' . $b); assertType('(non-empty-string&uppercase-string)|numeric-string', '' . $numeric); assertType('(non-empty-string&uppercase-string)|numeric-string', '' . $numeric2); - assertType('decimal-int-string|(non-empty-string&uppercase-string)', '' . $number); + assertType('non-empty-string&uppercase-string', '' . $number); assertType('decimal-int-string&non-falsy-string', '' . $positive); assertType('decimal-int-string&non-falsy-string', '' . $negative); assertType("'1'", '' . $constantInt); @@ -45,7 +45,7 @@ function concatEmptyString(int $a, float $b, $numeric, $numeric2, $number, $posi assertType('non-empty-string&uppercase-string', $b . ''); assertType('(non-empty-string&uppercase-string)|numeric-string', $numeric . ''); assertType('(non-empty-string&uppercase-string)|numeric-string', $numeric2 . ''); - assertType('decimal-int-string|(non-empty-string&uppercase-string)', $number . ''); + assertType('non-empty-string&uppercase-string', $number . ''); assertType('decimal-int-string&non-falsy-string', $positive . ''); assertType('decimal-int-string&non-falsy-string', $negative . ''); assertType("'1'", $constantInt . ''); diff --git a/tests/PHPStan/Analyser/nsrt/float-to-string.php b/tests/PHPStan/Analyser/nsrt/float-to-string.php index 84adad9e996..e6a12eb4b14 100644 --- a/tests/PHPStan/Analyser/nsrt/float-to-string.php +++ b/tests/PHPStan/Analyser/nsrt/float-to-string.php @@ -13,7 +13,7 @@ function doFoo(float $float, $number): void assertType('non-empty-string&uppercase-string', (string) $float); assertType('non-empty-string&uppercase-string', '' . $float); assertType('non-falsy-string&uppercase-string', $float . '1'); - assertType('decimal-int-string|(non-empty-string&uppercase-string)', (string) $number); + assertType('non-empty-string&uppercase-string', (string) $number); // constant floats stay exact assertType("'1.5'", (string) 1.5); diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string-file-functions.php b/tests/PHPStan/Analyser/nsrt/non-empty-string-file-functions.php index 794a17316b7..fff0e4694f5 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string-file-functions.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string-file-functions.php @@ -25,7 +25,7 @@ public function strTypes($nonES, $nonFalsyString, $numericString) { assertType('non-falsy-string', $nonFalsyString); if (dir($numericString)) { - assertType('non-empty-string&numeric-string', $numericString); + assertType('numeric-string', $numericString); } assertType('numeric-string', $numericString); } diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string-str-containing-fns.php b/tests/PHPStan/Analyser/nsrt/non-empty-string-str-containing-fns.php index 19d3199f123..930a667381f 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string-str-containing-fns.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string-str-containing-fns.php @@ -54,14 +54,14 @@ public function strContains(string $s, string $s2, $nonES, $nonFalsy, $numS, $li assertType('non-empty-string', $s); } if (str_contains($numS, $nonEAndNumericS)) { - assertType('non-empty-string&numeric-string', $numS); + assertType('numeric-string', $numS); } if (str_contains($nonES, $s)) { assertType('non-empty-string', $nonES); } if (str_contains($nonEAndNumericS, $s)) { - assertType('non-empty-string&numeric-string', $nonEAndNumericS); + assertType('numeric-string', $nonEAndNumericS); } if (str_contains($i, $s2)) { diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string.php b/tests/PHPStan/Analyser/nsrt/non-empty-string.php index 4abb588f617..23527db2a31 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string.php @@ -140,7 +140,7 @@ public function doWithNumeric(string $s): void return; } - assertType('non-empty-string&numeric-string', $s); + assertType('numeric-string', $s); } public function doEmpty(string $s): void diff --git a/tests/PHPStan/Type/MixedTypeTest.php b/tests/PHPStan/Type/MixedTypeTest.php index c204153c3fe..acfe27716ce 100644 --- a/tests/PHPStan/Type/MixedTypeTest.php +++ b/tests/PHPStan/Type/MixedTypeTest.php @@ -374,7 +374,7 @@ public static function dataSubstractedIsNumericString(): array new StringType(), new AccessoryNonEmptyStringType(), ), - TrinaryLogic::createMaybe(), + TrinaryLogic::createNo(), ], [ new MixedType(), diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 2102af4f173..7f3fa7034ce 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -3389,6 +3389,38 @@ public static function dataUnion(): iterable FloatType::class, 'float', ]; + yield [ + [ + 'non-empty-string', + 'numeric-string', + ], + IntersectionType::class, + 'non-empty-string', + ]; + yield [ + [ + 'non-empty-string', + 'decimal-int-string', + ], + IntersectionType::class, + 'non-empty-string', + ]; + yield [ + [ + 'non-empty-uppercase-string', + 'numeric-string&uppercase-string', + ], + IntersectionType::class, + 'non-empty-string&uppercase-string', + ]; + yield [ + [ + 'non-empty-lowercase-string', + 'decimal-int-string', + ], + IntersectionType::class, + 'lowercase-string&non-empty-string', + ]; } /** @@ -5603,6 +5635,23 @@ public static function dataIntersect(): iterable 'lowercase-string&non-decimal-int-string', ]; + yield [ + [ + 'non-empty-string', + 'numeric-string', + ], + IntersectionType::class, + 'numeric-string', + ]; + yield [ + [ + 'non-empty-string', + 'decimal-int-string', + ], + IntersectionType::class, + 'decimal-int-string', + ]; + yield [ [ new ConstantArrayType( diff --git a/turbo-ext/src/AccessoryDecimalIntegerStringType.cpp b/turbo-ext/src/AccessoryDecimalIntegerStringType.cpp index 52b7dfae2aa..3cbe4f56cda 100644 --- a/turbo-ext/src/AccessoryDecimalIntegerStringType.cpp +++ b/turbo-ext/src/AccessoryDecimalIntegerStringType.cpp @@ -100,8 +100,8 @@ class AccessoryDecimalIntegerStringType return pt_type_is_super_type_of_result(pt_trinary_and(isString, inverse ? trinaryNegate(isDecimalIntegerString) : isDecimalIntegerString)); } - /* the union/intersection callback; yes for a numeric, lowercase or - * uppercase accessory when not inverted; else the other type's + /* the union/intersection callback; yes for a numeric, non-empty, + * lowercase or uppercase accessory when not inverted; else the other type's * string-and-(negated-)decimal-int verdict, and'ed with maybe unless it * equals $this; UNDEF = pending exception */ zv::Val isSubTypeOf(zval *otherType) const @@ -115,6 +115,7 @@ class AccessoryDecimalIntegerStringType } zend_class_entry *otherCe = Z_OBJCE_P(otherType); if (instanceof_function(otherCe, pt_ce_accessory_numeric_string_type) + || instanceof_function(otherCe, pt_ce_accessory_non_empty_string_type) || instanceof_function(otherCe, pt_ce_accessory_lowercase_string_type) || instanceof_function(otherCe, pt_ce_accessory_uppercase_string_type) ) { diff --git a/turbo-ext/src/AccessoryNumericStringType.cpp b/turbo-ext/src/AccessoryNumericStringType.cpp index a9b3ea04d01..081f78c432b 100644 --- a/turbo-ext/src/AccessoryNumericStringType.cpp +++ b/turbo-ext/src/AccessoryNumericStringType.cpp @@ -59,9 +59,9 @@ class AccessoryNumericStringType return pt_type_is_super_type_of_result(isNumericString); } - /* the union/intersection callback; else the other type's numeric-string - * verdict, and'ed with maybe unless it is an AccessoryNumericStringType; - * UNDEF = pending exception */ + /* the union/intersection callback; yes for a non-empty-string accessory; + * else the other type's numeric-string verdict, and'ed with maybe unless it + * is an AccessoryNumericStringType; UNDEF = pending exception */ zv::Val isSubTypeOf(zval *otherType) const { bool unionOrIntersection; @@ -71,6 +71,7 @@ class AccessoryNumericStringType ZVAL_OBJ(&selfZv, self); return pt_type_op(Z_OBJ_P(otherType), PT_OP_IS_SUPER_TYPE_OF, 1, &selfZv); } + if (instanceof_function(Z_OBJCE_P(otherType), pt_ce_accessory_non_empty_string_type)) return pt_type_is_super_type_of_result(PT_TRI_YES); zend_long value = pt_type_call_trinary(Z_OBJ_P(otherType), PT_LC("isnumericstring"), 0, NULL); if (UNEXPECTED(value < 0)) return zv::Val(); if (!instanceof_function(Z_OBJCE_P(otherType), pt_ce_accessory_numeric_string_type)) {