From 193756d03e6a0e1478ee49538dd17af367aab7fe Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 3 Jun 2024 15:39:01 +0200 Subject: [PATCH] Consider numeric-string types after string concat --- .../InitializerExprTypeResolver.php | 29 +++++++ .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-11129.php | 83 +++++++++++++++++++ .../CallToFunctionParametersRuleTest.php | 2 +- 4 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/data/bug-11129.php diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index b3fcce1c6a..875df63b9b 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -2,6 +2,7 @@ namespace PHPStan\Reflection; +use Nette\Utils\Strings; use PhpParser\Node\Arg; use PhpParser\Node\Expr; use PhpParser\Node\Expr\BinaryOp; @@ -28,6 +29,7 @@ use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; +use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\BenevolentUnionType; @@ -78,6 +80,7 @@ use function is_finite; use function is_float; use function is_int; +use function is_numeric; use function max; use function min; use function sprintf; @@ -475,6 +478,32 @@ public function resolveConcatType(Type $left, Type $right): Type $accessoryTypes[] = new AccessoryLiteralStringType(); } + $leftNumericStringNonEmpty = TypeCombinator::remove($leftStringType, new ConstantStringType('')); + if ($leftNumericStringNonEmpty->isNumericString()->yes()) { + $allRightConstantsZeroOrMore = false; + foreach ($rightConstantStrings as $rightConstantString) { + if ($rightConstantString->getValue() === '') { + continue; + } + + if ( + !is_numeric($rightConstantString->getValue()) + || Strings::match($rightConstantString->getValue(), '#^[0-9]+$#') === null + ) { + $allRightConstantsZeroOrMore = false; + break; + } + + $allRightConstantsZeroOrMore = true; + } + + $zeroOrMoreInteger = IntegerRangeType::fromInterval(0, null); + $nonNegativeRight = $allRightConstantsZeroOrMore || $zeroOrMoreInteger->isSuperTypeOf($right)->yes(); + if ($nonNegativeRight) { + $accessoryTypes[] = new AccessoryNumericStringType(); + } + } + if (count($accessoryTypes) > 0) { $accessoryTypes[] = new StringType(); return new IntersectionType($accessoryTypes); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index a134571f9e..df07863448 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1482,6 +1482,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10122.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10189.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10317.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-11129.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10302-interface-extends.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10302-trait-extends.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10302-trait-implements.php'); diff --git a/tests/PHPStan/Analyser/data/bug-11129.php b/tests/PHPStan/Analyser/data/bug-11129.php new file mode 100644 index 0000000000..c599dae2a6 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-11129.php @@ -0,0 +1,83 @@ +