From 1d69bd4105b13fbcdb153e35732a0419e585944a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 09:09:25 +0200 Subject: [PATCH 01/26] Cover non-empty-string in `substr` --- tests/PHPStan/Analyser/data/non-empty-string.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index a235ea6ef8..ee0918c2cc 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -134,6 +134,10 @@ public function doEmpty2(string $s): void } } + /** @param non-empty-string $nonEmpty */ + public function doNegativeSubstr($nonEmpty) { + assertType[substr($nonEmpty, -5)] + } } class ImplodingStrings From f8748362de8652160b906185126999f281564e4a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 09:18:58 +0200 Subject: [PATCH 02/26] Create SubstrDynamicReturnTypeExtension.php --- .../Php/SubstrDynamicReturnTypeExtension.php | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/Type/Php/SubstrDynamicReturnTypeExtension.php diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..d299e65b36 --- /dev/null +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -0,0 +1,50 @@ +getName() === 'substr'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): \PHPStan\Type\Type + { + $args = $functionCall->args; + if (count($args) === 0) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + if (count($args) === 2) { + $string = $scope->getType($args[0]->value); + $offset = $scope->getType($args[1]->value); + + if ($argType->isNonEmptyString()->yes() && $offset instanceof ConstantIntegerType && $offset->getValue() < 0) { + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + } + } + + + return new StringType(); + } + +} From 0db389d85942f674c31282d4e349be4ed1a73b3c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 09:22:26 +0200 Subject: [PATCH 03/26] Update config.neon --- conf/config.neon | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/conf/config.neon b/conf/config.neon index 40a8c57fab..f76dfc17d5 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1274,6 +1274,11 @@ services: class: PHPStan\Type\Php\StrRepeatFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + + - + class: PHPStan\Type\Php\SubstrDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension - class: PHPStan\Type\Php\ParseUrlFunctionDynamicReturnTypeExtension From 1cb919d97fe953ecf68de5883c1ac9e170c5e866 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 09:24:33 +0200 Subject: [PATCH 04/26] Update non-empty-string.php --- tests/PHPStan/Analyser/data/non-empty-string.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index ee0918c2cc..7796910c81 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -134,9 +134,13 @@ public function doEmpty2(string $s): void } } - /** @param non-empty-string $nonEmpty */ - public function doNegativeSubstr($nonEmpty) { - assertType[substr($nonEmpty, -5)] + /** + * @param non-empty-string $nonEmpty + * @param positive-int $positiveInt + */ + public function doNegativeSubstr($nonEmpty, $positiveInt) { + assertType(substr($nonEmpty, -5)); + assertType(substr($nonEmpty, 0, $positiveInt)); } } From 3fe65c58bc81a0317595433f4044b7250cc4a2a2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 09:40:03 +0200 Subject: [PATCH 05/26] Update SubstrDynamicReturnTypeExtension.php --- src/Type/Php/SubstrDynamicReturnTypeExtension.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index d299e65b36..056c869dff 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -31,17 +31,26 @@ public function getTypeFromFunctionCall( return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - if (count($args) === 2) { + if (count($args) > 2) { $string = $scope->getType($args[0]->value); $offset = $scope->getType($args[1]->value); + + $negativeOffset = $offset instanceof ConstantIntegerType && $offset->getValue() < 0; + $zeroOffset = $offset instanceof ConstantIntegerType && $offset->getValue() === 0; + $positiveLength = null; + + if (count($args) === 3) { + $length = $scope->getType($args[2]->value); + $positiveLength = $length instanceof ConstantIntegerType && $length->getValue() > 0; + } - if ($argType->isNonEmptyString()->yes() && $offset instanceof ConstantIntegerType && $offset->getValue() < 0) { + if ($argType->isNonEmptyString()->yes() && ($negativeOffset || $zeroOffset && $positiveLength)) { return new IntersectionType([ new StringType(), new AccessoryNonEmptyStringType(), ]); } - } + } return new StringType(); From 28049b44f6cfcd45957b61307d0e757d6dde8c65 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 09:40:30 +0200 Subject: [PATCH 06/26] Update SubstrDynamicReturnTypeExtension.php --- src/Type/Php/SubstrDynamicReturnTypeExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index 056c869dff..5aebe6c567 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -31,7 +31,7 @@ public function getTypeFromFunctionCall( return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - if (count($args) > 2) { + if (count($args) >= 2) { $string = $scope->getType($args[0]->value); $offset = $scope->getType($args[1]->value); From f350a2b64ef0c7a112a9512191f5c851779bf1e6 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 09:43:25 +0200 Subject: [PATCH 07/26] Update non-empty-string.php --- tests/PHPStan/Analyser/data/non-empty-string.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 7796910c81..8cb606eaa6 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -138,7 +138,7 @@ public function doEmpty2(string $s): void * @param non-empty-string $nonEmpty * @param positive-int $positiveInt */ - public function doNegativeSubstr($nonEmpty, $positiveInt) { + public function doSubstr($nonEmpty, $positiveInt) { assertType(substr($nonEmpty, -5)); assertType(substr($nonEmpty, 0, $positiveInt)); } From 59874d82087cb4c7f33fb54cad226be44e514fb4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 09:44:41 +0200 Subject: [PATCH 08/26] Update config.neon --- conf/config.neon | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index f76dfc17d5..c87e525cce 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1274,11 +1274,11 @@ services: class: PHPStan\Type\Php\StrRepeatFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - + - - class: PHPStan\Type\Php\SubstrDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension + class: PHPStan\Type\Php\SubstrDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension - class: PHPStan\Type\Php\ParseUrlFunctionDynamicReturnTypeExtension From 07beeec4c8cdc2cd5e3bdcb6702a7bc595fb9ff2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 09:52:21 +0200 Subject: [PATCH 09/26] Update SubstrDynamicReturnTypeExtension.php --- src/Type/Php/SubstrDynamicReturnTypeExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index 5aebe6c567..d0495b681c 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -10,7 +10,7 @@ use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; -use PHPStan\Type\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantIntegerType; class SubstrDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { From c197dcb9e0257b8f27694e81848416634b381b76 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 09:54:00 +0200 Subject: [PATCH 10/26] Update non-empty-string.php --- tests/PHPStan/Analyser/data/non-empty-string.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 8cb606eaa6..536408f850 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -139,8 +139,8 @@ public function doEmpty2(string $s): void * @param positive-int $positiveInt */ public function doSubstr($nonEmpty, $positiveInt) { - assertType(substr($nonEmpty, -5)); - assertType(substr($nonEmpty, 0, $positiveInt)); + assertType('non-empty-string', substr($nonEmpty, -5)); + assertType('non-empty-string', substr($nonEmpty, 0, $positiveInt)); } } From 309127a0d6158af5f05e8fb704c3087c660d196f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 10:07:31 +0200 Subject: [PATCH 11/26] Update non-empty-string.php --- tests/PHPStan/Analyser/data/non-empty-string.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 536408f850..150d7316aa 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -138,7 +138,7 @@ public function doEmpty2(string $s): void * @param non-empty-string $nonEmpty * @param positive-int $positiveInt */ - public function doSubstr($nonEmpty, $positiveInt) { + public function doSubstr($nonEmpty, $positiveInt): void { assertType('non-empty-string', substr($nonEmpty, -5)); assertType('non-empty-string', substr($nonEmpty, 0, $positiveInt)); } From 6f0bddc116f3b19c02b811100f7ae0a2bfed7e70 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 10:08:03 +0200 Subject: [PATCH 12/26] Update SubstrDynamicReturnTypeExtension.php --- src/Type/Php/SubstrDynamicReturnTypeExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index d0495b681c..7f661c515c 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -44,7 +44,7 @@ public function getTypeFromFunctionCall( $positiveLength = $length instanceof ConstantIntegerType && $length->getValue() > 0; } - if ($argType->isNonEmptyString()->yes() && ($negativeOffset || $zeroOffset && $positiveLength)) { + if ($string->isNonEmptyString()->yes() && ($negativeOffset || $zeroOffset && $positiveLength)) { return new IntersectionType([ new StringType(), new AccessoryNonEmptyStringType(), From a68607cc4d03b085bf8ad751cab18e7995ab63d3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 10:11:36 +0200 Subject: [PATCH 13/26] Update non-empty-string.php --- tests/PHPStan/Analyser/data/non-empty-string.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 150d7316aa..5ba1922262 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -140,6 +140,7 @@ public function doEmpty2(string $s): void */ public function doSubstr($nonEmpty, $positiveInt): void { assertType('non-empty-string', substr($nonEmpty, -5)); + assertType('non-empty-string', substr($nonEmpty, 0, 5)); assertType('non-empty-string', substr($nonEmpty, 0, $positiveInt)); } } From 19e83a4a7e4610b6d9b12528ab60da442f69cd77 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 10:13:10 +0200 Subject: [PATCH 14/26] Update non-empty-string.php --- tests/PHPStan/Analyser/data/non-empty-string.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 5ba1922262..650455f8b9 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -139,9 +139,9 @@ public function doEmpty2(string $s): void * @param positive-int $positiveInt */ public function doSubstr($nonEmpty, $positiveInt): void { - assertType('non-empty-string', substr($nonEmpty, -5)); - assertType('non-empty-string', substr($nonEmpty, 0, 5)); - assertType('non-empty-string', substr($nonEmpty, 0, $positiveInt)); + assertType('non-empty-string', substr($nonEmpty, -5)); + assertType('non-empty-string', substr($nonEmpty, 0, 5)); + assertType('non-empty-string', substr($nonEmpty, 0, $positiveInt)); } } From 922f0c3b00b1416f2e009b733932d3c46f68165e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 16:58:42 +0200 Subject: [PATCH 15/26] Update SubstrDynamicReturnTypeExtension.php --- .../Php/SubstrDynamicReturnTypeExtension.php | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index 7f661c515c..9662e8afa6 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -35,13 +35,13 @@ public function getTypeFromFunctionCall( $string = $scope->getType($args[0]->value); $offset = $scope->getType($args[1]->value); - $negativeOffset = $offset instanceof ConstantIntegerType && $offset->getValue() < 0; - $zeroOffset = $offset instanceof ConstantIntegerType && $offset->getValue() === 0; - $positiveLength = null; + $negativeOffset = $this->getIntValue($offset) < 0; + $zeroOffset = $this->getIntValue($offset) === 0; + $positiveLength = false; if (count($args) === 3) { $length = $scope->getType($args[2]->value); - $positiveLength = $length instanceof ConstantIntegerType && $length->getValue() > 0; + $positiveLength = $this->getIntValue($length) > 0; } if ($string->isNonEmptyString()->yes() && ($negativeOffset || $zeroOffset && $positiveLength)) { @@ -56,4 +56,16 @@ public function getTypeFromFunctionCall( return new StringType(); } +private function getIntValue(Type $type): ?int { + if ($type instanceof IntegerRangeType) { + return $type->getMin(); + } + if ($type instanceof ConstantIntegerType) { + return $type->getValue(); + } + return null; + + +} + } From b788db61bfaed821e151cbdd3086ee3dd86461be Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 17:02:31 +0200 Subject: [PATCH 16/26] Update non-empty-string.php --- tests/PHPStan/Analyser/data/non-empty-string.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 650455f8b9..1c53d09def 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -138,9 +138,17 @@ public function doEmpty2(string $s): void * @param non-empty-string $nonEmpty * @param positive-int $positiveInt */ - public function doSubstr($nonEmpty, $positiveInt): void { - assertType('non-empty-string', substr($nonEmpty, -5)); + public function doSubstr(string $s, $nonEmpty, $positiveInt): void { + +assertType('string', substr($s, -5)); + assertType('non-empty-string', substr($nonEmpty, -5)) + +assertType('string', substr($s, 0, 5)); assertType('non-empty-string', substr($nonEmpty, 0, 5)); + +assertType('string', substr($nonEmpty, 0, -5)); + +assertType('string', substr($s, 0, $positiveInt)); assertType('non-empty-string', substr($nonEmpty, 0, $positiveInt)); } } From 33ebef4464b8d2ba77b9799a0b31703f25b30cc6 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 17:08:34 +0200 Subject: [PATCH 17/26] Update non-empty-string.php --- tests/PHPStan/Analyser/data/non-empty-string.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 1c53d09def..dab00bc65b 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -140,6 +140,8 @@ public function doEmpty2(string $s): void */ public function doSubstr(string $s, $nonEmpty, $positiveInt): void { +assertType('string', substr($s, 5)); + assertType('string', substr($s, -5)); assertType('non-empty-string', substr($nonEmpty, -5)) From 868f7dcc5b9d3a60f81f246560b418e967ab98e0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 17:10:11 +0200 Subject: [PATCH 18/26] Update SubstrDynamicReturnTypeExtension.php --- src/Type/Php/SubstrDynamicReturnTypeExtension.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index 9662e8afa6..134b34dd06 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -10,6 +10,8 @@ use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; +use PHPStan\Type\Type; +use PHPStan\Type\IntegerRangeType; use PHPStan\Type\Constant\ConstantIntegerType; class SubstrDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension From a578274d0614525df47ed09988eab586e92a6f58 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Jul 2021 20:16:47 +0200 Subject: [PATCH 19/26] Typo --- tests/PHPStan/Analyser/data/non-empty-string.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index dab00bc65b..7f7b8eeff5 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -143,7 +143,7 @@ public function doSubstr(string $s, $nonEmpty, $positiveInt): void { assertType('string', substr($s, 5)); assertType('string', substr($s, -5)); - assertType('non-empty-string', substr($nonEmpty, -5)) + assertType('non-empty-string', substr($nonEmpty, -5)); assertType('string', substr($s, 0, 5)); assertType('non-empty-string', substr($nonEmpty, 0, 5)); From 6553b7c8b817be9205dedd2080c14feac00701eb Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 19 Jul 2021 10:08:45 +0200 Subject: [PATCH 20/26] fix CS --- .../Php/SubstrDynamicReturnTypeExtension.php | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index 134b34dd06..12bdbc1154 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -7,12 +7,12 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; +use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; use PHPStan\Type\Type; -use PHPStan\Type\IntegerRangeType; -use PHPStan\Type\Constant\ConstantIntegerType; class SubstrDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { @@ -33,41 +33,39 @@ public function getTypeFromFunctionCall( return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - if (count($args) >= 2) { - $string = $scope->getType($args[0]->value); - $offset = $scope->getType($args[1]->value); - - $negativeOffset = $this->getIntValue($offset) < 0; - $zeroOffset = $this->getIntValue($offset) === 0; - $positiveLength = false; - - if (count($args) === 3) { - $length = $scope->getType($args[2]->value); - $positiveLength = $this->getIntValue($length) > 0; + if (count($args) >= 2) { + $string = $scope->getType($args[0]->value); + $offset = $scope->getType($args[1]->value); + + $negativeOffset = $this->getIntValue($offset) < 0; + $zeroOffset = $this->getIntValue($offset) === 0; + $positiveLength = false; + + if (count($args) === 3) { + $length = $scope->getType($args[2]->value); + $positiveLength = $this->getIntValue($length) > 0; + } + + if ($string->isNonEmptyString()->yes() && ($negativeOffset || $zeroOffset && $positiveLength)) { + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + } } - - if ($string->isNonEmptyString()->yes() && ($negativeOffset || $zeroOffset && $positiveLength)) { - return new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ]); - } - } - return new StringType(); } -private function getIntValue(Type $type): ?int { - if ($type instanceof IntegerRangeType) { - return $type->getMin(); - } - if ($type instanceof ConstantIntegerType) { - return $type->getValue(); - } + private function getIntValue(Type $type): ?int + { + if ($type instanceof IntegerRangeType) { + return $type->getMin(); + } + if ($type instanceof ConstantIntegerType) { + return $type->getValue(); + } return null; - - -} + } } From 6fef8bd6fd54ddef293eec903c68e6031bb08521 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 19 Jul 2021 10:37:57 +0200 Subject: [PATCH 21/26] fix CS in test --- .../Analyser/data/non-empty-string.php | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 7f7b8eeff5..150cded3b6 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -138,20 +138,20 @@ public function doEmpty2(string $s): void * @param non-empty-string $nonEmpty * @param positive-int $positiveInt */ - public function doSubstr(string $s, $nonEmpty, $positiveInt): void { - -assertType('string', substr($s, 5)); + public function doSubstr(string $s, $nonEmpty, $positiveInt): void + { + assertType('string', substr($s, 5)); -assertType('string', substr($s, -5)); - assertType('non-empty-string', substr($nonEmpty, -5)); + assertType('string', substr($s, -5)); + assertType('non-empty-string', substr($nonEmpty, -5)); -assertType('string', substr($s, 0, 5)); - assertType('non-empty-string', substr($nonEmpty, 0, 5)); + assertType('string', substr($s, 0, 5)); + assertType('non-empty-string', substr($nonEmpty, 0, 5)); -assertType('string', substr($nonEmpty, 0, -5)); + assertType('string', substr($nonEmpty, 0, -5)); -assertType('string', substr($s, 0, $positiveInt)); - assertType('non-empty-string', substr($nonEmpty, 0, $positiveInt)); + assertType('string', substr($s, 0, $positiveInt)); + assertType('non-empty-string', substr($nonEmpty, 0, $positiveInt)); } } @@ -205,7 +205,7 @@ public function doFoo4(string $s, array $nonEmptyArrayWithNonEmptyStrings): void public function sayHello(): void { // coming from issue #5291 - $s = array(1,2); + $s = array(1, 2); assertType('non-empty-string', implode("a", $s)); } @@ -213,7 +213,8 @@ public function sayHello(): void /** * @param non-empty-string $glue */ - public function nonE($glue, array $a) { + public function nonE($glue, array $a) + { // coming from issue #5291 if (empty($a)) { return "xyz"; @@ -225,7 +226,7 @@ public function nonE($glue, array $a) { public function sayHello2(): void { // coming from issue #5291 - $s = array(1,2); + $s = array(1, 2); assertType('non-empty-string', join("a", $s)); } @@ -233,7 +234,8 @@ public function sayHello2(): void /** * @param non-empty-string $glue */ - public function nonE2($glue, array $a) { + public function nonE2($glue, array $a) + { // coming from issue #5291 if (empty($a)) { return "xyz"; @@ -247,7 +249,8 @@ public function nonE2($glue, array $a) { class LiteralString { - function x(string $tableName, string $original): void { + function x(string $tableName, string $original): void + { assertType('non-empty-string', "from `$tableName`"); } @@ -316,7 +319,7 @@ public function doFoo(string $s, string $nonEmpty, int $i) assertType('non-empty-string', htmlspecialchars($nonEmpty)); assertType('string', htmlentities($s)); assertType('non-empty-string', htmlentities($nonEmpty)); - + assertType('string', urlencode($s)); assertType('non-empty-string', urlencode($nonEmpty)); assertType('string', urldecode($s)); From 0adb9ed4f720b4754dac3e8801573f9a50b2fb67 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 19 Jul 2021 14:57:45 +0200 Subject: [PATCH 22/26] cleanup after review --- src/Type/Php/SubstrDynamicReturnTypeExtension.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index 12bdbc1154..ccfad1d261 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -37,13 +37,13 @@ public function getTypeFromFunctionCall( $string = $scope->getType($args[0]->value); $offset = $scope->getType($args[1]->value); - $negativeOffset = $this->getIntValue($offset) < 0; + $negativeOffset = IntegerRangeType::fromInterval(null, -1)->isSuperTypeOf($offset)->yes(); $zeroOffset = $this->getIntValue($offset) === 0; $positiveLength = false; if (count($args) === 3) { $length = $scope->getType($args[2]->value); - $positiveLength = $this->getIntValue($length) > 0; + $positiveLength = IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($length)->yes(); } if ($string->isNonEmptyString()->yes() && ($negativeOffset || $zeroOffset && $positiveLength)) { @@ -65,7 +65,8 @@ private function getIntValue(Type $type): ?int if ($type instanceof ConstantIntegerType) { return $type->getValue(); } - return null; + + return null; } } From 7c1385fbf3f5362b28c4623d12eea67a9bc7eab4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 19 Jul 2021 15:05:16 +0200 Subject: [PATCH 23/26] added integer-range tests --- tests/PHPStan/Analyser/data/non-empty-string.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 150cded3b6..f66ffc818d 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -137,21 +137,26 @@ public function doEmpty2(string $s): void /** * @param non-empty-string $nonEmpty * @param positive-int $positiveInt + * @param 1|2|3 $postiveRange + * @param -1|-2|-3 $negativeRange */ - public function doSubstr(string $s, $nonEmpty, $positiveInt): void + public function doSubstr(string $s, $nonEmpty, $positiveInt, $postiveRange, $negativeRange): void { assertType('string', substr($s, 5)); assertType('string', substr($s, -5)); assertType('non-empty-string', substr($nonEmpty, -5)); + assertType('non-empty-string', substr($nonEmpty, $negativeRange)); assertType('string', substr($s, 0, 5)); assertType('non-empty-string', substr($nonEmpty, 0, 5)); + assertType('non-empty-string', substr($nonEmpty, 0, $postiveRange)); assertType('string', substr($nonEmpty, 0, -5)); assertType('string', substr($s, 0, $positiveInt)); assertType('non-empty-string', substr($nonEmpty, 0, $positiveInt)); + assertType('non-empty-string', substr($nonEmpty, 0, $postiveRange)); } } From c1d212800c81315b004bb884a4e818fff674fba4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 19 Jul 2021 15:09:02 +0200 Subject: [PATCH 24/26] simplify --- .../Php/SubstrDynamicReturnTypeExtension.php | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index ccfad1d261..849bb64596 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -12,7 +12,6 @@ use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; -use PHPStan\Type\Type; class SubstrDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { @@ -38,7 +37,7 @@ public function getTypeFromFunctionCall( $offset = $scope->getType($args[1]->value); $negativeOffset = IntegerRangeType::fromInterval(null, -1)->isSuperTypeOf($offset)->yes(); - $zeroOffset = $this->getIntValue($offset) === 0; + $zeroOffset = (new ConstantIntegerType(0))->isSuperTypeOf($offset)->yes(); $positiveLength = false; if (count($args) === 3) { @@ -56,17 +55,4 @@ public function getTypeFromFunctionCall( return new StringType(); } - - private function getIntValue(Type $type): ?int - { - if ($type instanceof IntegerRangeType) { - return $type->getMin(); - } - if ($type instanceof ConstantIntegerType) { - return $type->getValue(); - } - - return null; - } - } From d90b84cf9032d72e14f5e9e20c82701822ee6c9f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 19 Jul 2021 15:12:58 +0200 Subject: [PATCH 25/26] removed duplicated test --- tests/PHPStan/Analyser/data/non-empty-string.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index f66ffc818d..a233259c5a 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -156,7 +156,6 @@ public function doSubstr(string $s, $nonEmpty, $positiveInt, $postiveRange, $neg assertType('string', substr($s, 0, $positiveInt)); assertType('non-empty-string', substr($nonEmpty, 0, $positiveInt)); - assertType('non-empty-string', substr($nonEmpty, 0, $postiveRange)); } } From 1eef6d36880828a9b855f471eaf1f91b701c2cf8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 19 Jul 2021 15:55:04 +0200 Subject: [PATCH 26/26] fix CS --- src/Type/Php/SubstrDynamicReturnTypeExtension.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index 849bb64596..f86d919eab 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -55,4 +55,5 @@ public function getTypeFromFunctionCall( return new StringType(); } + }