diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 8b38c5a383..008e3785fc 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -91,6 +91,7 @@ use function max; use function min; use function sprintf; +use function str_starts_with; use function strtolower; use const INF; @@ -506,6 +507,10 @@ public function resolveConcatType(Type $left, Type $right): Type $leftNumericStringNonEmpty = TypeCombinator::remove($leftStringType, new ConstantStringType('')); if ($leftNumericStringNonEmpty->isNumericString()->yes()) { + $validationCallback = $left->isInteger()->yes() + ? static fn (string $value): bool => !str_starts_with($value, '-') + : static fn (string $value): bool => Strings::match($value, '#^\d+$#') !== null; + $allRightConstantsZeroOrMore = false; foreach ($rightConstantStrings as $rightConstantString) { if ($rightConstantString->getValue() === '') { @@ -514,7 +519,7 @@ public function resolveConcatType(Type $left, Type $right): Type if ( !is_numeric($rightConstantString->getValue()) - || Strings::match($rightConstantString->getValue(), '#^[0-9]+$#') === null + || !$validationCallback($rightConstantString->getValue()) ) { $allRightConstantsZeroOrMore = false; break; diff --git a/tests/PHPStan/Analyser/nsrt/bug-11129.php b/tests/PHPStan/Analyser/nsrt/bug-11129.php index a845bf1d2a..33007584be 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11129.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11129.php @@ -48,9 +48,20 @@ public function foo( assertType('lowercase-string&non-falsy-string', $i.$maybeNonNumericConstStrings); assertType('lowercase-string&non-falsy-string', $maybeNonNumericConstStrings.$i); - assertType('lowercase-string&non-falsy-string&uppercase-string', $i.$maybeFloatConstStrings); // could be 'lowercase-string&non-falsy-string&numeric-string' + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $i.$maybeFloatConstStrings); assertType('lowercase-string&non-falsy-string&uppercase-string', $maybeFloatConstStrings.$i); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $i.'1'); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $i.'1.0'); + assertType('lowercase-string&non-falsy-string&uppercase-string', $i.'1.1.1'); + assertType('lowercase-string&non-falsy-string&uppercase-string', $i.'-1'); + assertType('lowercase-string&non-falsy-string&uppercase-string', $i.'-1.0'); + assertType('lowercase-string&non-falsy-string&numeric-string', $i.'10e-3'); + assertType('lowercase-string&non-falsy-string', $i.'-10e-3'); + assertType('non-falsy-string&numeric-string&uppercase-string', $i.'10E3'); + assertType('non-falsy-string&uppercase-string', $i.'-10E3'); + assertType('non-falsy-string', $i.'10eE3'); + assertType('lowercase-string&non-empty-string&numeric-string&uppercase-string', $i.$bool); assertType('lowercase-string&non-empty-string&uppercase-string', $bool.$i); assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $positiveInt.$bool); @@ -68,14 +79,14 @@ public function foo( assertType('non-falsy-string&numeric-string&uppercase-string', $float.$positiveInt); assertType('non-falsy-string&uppercase-string', $float.$negativeInt); assertType('non-falsy-string&uppercase-string', $float.$i); - assertType('non-falsy-string&uppercase-string', $i.$float); // could be 'non-falsy-string&numeric-string&uppercase-string' + assertType('non-falsy-string&uppercase-string', $i.$float); assertType('non-falsy-string', $numericString.$float); assertType('non-falsy-string', $numericString.$maybeFloatConstStrings); // https://3v4l.org/Ia4r0 $scientificFloatAsString = '3e4'; assertType('non-falsy-string', $numericString.$scientificFloatAsString); - assertType('lowercase-string&non-falsy-string', $i.$scientificFloatAsString); + assertType('lowercase-string&non-falsy-string&numeric-string', $i.$scientificFloatAsString); assertType('non-falsy-string', $scientificFloatAsString.$numericString); assertType('lowercase-string&non-falsy-string', $scientificFloatAsString.$i); }