From 61e66733dfb7b7805f10531ac062335108a4aa67 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 5 Jan 2025 14:04:28 +0100 Subject: [PATCH 1/5] Support arrays with union value-types in implode() --- phpstan-baseline.neon | 5 ---- .../ImplodeFunctionReturnTypeExtension.php | 12 ++++++--- tests/PHPStan/Analyser/nsrt/implode.php | 25 +++++++++++++++++++ 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9977a1992d..93649e9087 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1362,11 +1362,6 @@ parameters: count: 1 path: src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php - - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" - count: 1 - path: src/Type/Php/ImplodeFunctionReturnTypeExtension.php - - message: """ #^Call to deprecated method getConstantScalars\\(\\) of class PHPStan\\\\Type\\\\TypeUtils\\: diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php index 5c680b5b62..3b6caf6725 100644 --- a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\Internal\CombinationsHelper; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; @@ -12,7 +13,6 @@ use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantStringType; -use PHPStan\Type\ConstantScalarType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; @@ -115,13 +115,17 @@ private function inferConstantType(ConstantArrayType $arrayType, ConstantStringT $arrayValues = []; foreach ($valueTypes as $valueType) { - if (!$valueType instanceof ConstantScalarType) { + $constScalars = $valueType->getConstantScalarValues(); + if (count($constScalars) === 0) { return null; } - $arrayValues[] = $valueType->getValue(); + $arrayValues[] = $constScalars; } - $strings[] = new ConstantStringType(implode($separatorType->getValue(), $arrayValues)); + $combinations = CombinationsHelper::combinations($arrayValues); + foreach ($combinations as $combination) { + $strings[] = new ConstantStringType(implode($separatorType->getValue(), $combination)); + } } return TypeCombinator::union(...$strings); diff --git a/tests/PHPStan/Analyser/nsrt/implode.php b/tests/PHPStan/Analyser/nsrt/implode.php index b2e2b9aa7c..b2d8f3bf3c 100644 --- a/tests/PHPStan/Analyser/nsrt/implode.php +++ b/tests/PHPStan/Analyser/nsrt/implode.php @@ -21,4 +21,29 @@ public function constants() { assertType("'x,345'", join(',', [self::X, '345'])); assertType("'1,345'", join(',', [self::ONE, '345'])); } + + /** @param array{0: 1|2, 1: 'a'|'b'} $constArr */ + public function constArrays($constArr) { + assertType("'1a'|'1b'|'2a'|'2b'", implode('', $constArr)); + } + + /** @param array{0: 1|2|3, 1: 'a'|'b'|'c'} $constArr */ + public function constArrays2($constArr) { + assertType("'1a'|'1b'|'1c'|'2a'|'2b'|'2c'|'3a'|'3b'|'3c'", implode('', $constArr)); + } + + /** @param array{0: 1, 1: 'a'|'b', 2: 'x'|'y'} $constArr */ + public function constArrays3($constArr) { + assertType("'1ax'|'1ay'|'1bx'|'1by'", implode('', $constArr)); + } + + /** @param array{0: 1, 1: 'a'|'b', 2?: 'x'|'y'} $constArr */ + public function constArrays4($constArr) { + assertType("'1a'|'1ax'|'1ay'|'1b'|'1bx'|'1by'", implode('', $constArr)); + } + + /** @param array{10: 1|2|3, xy: 'a'|'b'|'c'} $constArr */ + public function constArrays5($constArr) { + assertType("'1a'|'1b'|'1c'|'2a'|'2b'|'2c'|'3a'|'3b'|'3c'", implode('', $constArr)); + } } From 9954344c0c74b0466fe6a1096187657b2052ad1e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 5 Jan 2025 14:42:04 +0100 Subject: [PATCH 2/5] add combination limit --- src/Type/Php/ImplodeFunctionReturnTypeExtension.php | 5 +++++ tests/PHPStan/Analyser/nsrt/implode.php | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php index 3b6caf6725..60d268fd4a 100644 --- a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Internal\CombinationsHelper; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; @@ -128,6 +129,10 @@ private function inferConstantType(ConstantArrayType $arrayType, ConstantStringT } } + if (count($strings) > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) { + return null; + } + return TypeCombinator::union(...$strings); } diff --git a/tests/PHPStan/Analyser/nsrt/implode.php b/tests/PHPStan/Analyser/nsrt/implode.php index b2d8f3bf3c..51e121a4c1 100644 --- a/tests/PHPStan/Analyser/nsrt/implode.php +++ b/tests/PHPStan/Analyser/nsrt/implode.php @@ -46,4 +46,9 @@ public function constArrays4($constArr) { public function constArrays5($constArr) { assertType("'1a'|'1b'|'1c'|'2a'|'2b'|'2c'|'3a'|'3b'|'3c'", implode('', $constArr)); } + + /** @param array{0: 1, 1: 'a'|'b', 3?: 'c'|'d', 4?: 'e'|'f', 5?: 'g'|'h', 6?: 'x'|'y'} $constArr */ + public function constArrays6($constArr) { + assertType("string", implode('', $constArr)); + } } From 7c5200deebe70f74436e7f70933560d160e70440 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 5 Jan 2025 14:53:12 +0100 Subject: [PATCH 3/5] fix --- src/Type/Php/ImplodeFunctionReturnTypeExtension.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php index 60d268fd4a..76afc963c2 100644 --- a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -22,6 +22,7 @@ use function count; use function implode; use function in_array; +use const COUNT_RECURSIVE; final class ImplodeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { @@ -123,16 +124,16 @@ private function inferConstantType(ConstantArrayType $arrayType, ConstantStringT $arrayValues[] = $constScalars; } + if (count($strings) + count($arrayValues, COUNT_RECURSIVE) > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) { + return null; + } + $combinations = CombinationsHelper::combinations($arrayValues); foreach ($combinations as $combination) { $strings[] = new ConstantStringType(implode($separatorType->getValue(), $combination)); } } - if (count($strings) > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) { - return null; - } - return TypeCombinator::union(...$strings); } From 06ca77648582421bedbaca38066a128b37a29091 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 5 Jan 2025 15:26:22 +0100 Subject: [PATCH 4/5] Added regression test --- tests/PHPStan/Analyser/nsrt/bug-11854.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11854.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-11854.php b/tests/PHPStan/Analyser/nsrt/bug-11854.php new file mode 100644 index 0000000000..48a49258cc --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11854.php @@ -0,0 +1,18 @@ + Date: Sun, 5 Jan 2025 17:46:02 +0100 Subject: [PATCH 5/5] fix combinations count --- src/Type/Php/ImplodeFunctionReturnTypeExtension.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php index 76afc963c2..a052a43416 100644 --- a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -22,7 +22,6 @@ use function count; use function implode; use function in_array; -use const COUNT_RECURSIVE; final class ImplodeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { @@ -116,15 +115,17 @@ private function inferConstantType(ConstantArrayType $arrayType, ConstantStringT $valueTypes = $array->getValueTypes(); $arrayValues = []; + $combinationsCount = 1; foreach ($valueTypes as $valueType) { $constScalars = $valueType->getConstantScalarValues(); if (count($constScalars) === 0) { return null; } $arrayValues[] = $constScalars; + $combinationsCount *= count($constScalars); } - if (count($strings) + count($arrayValues, COUNT_RECURSIVE) > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) { + if ($combinationsCount > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) { return null; } @@ -134,6 +135,10 @@ private function inferConstantType(ConstantArrayType $arrayType, ConstantStringT } } + if (count($strings) > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) { + return null; + } + return TypeCombinator::union(...$strings); }