From 49428242d254cfb65cdd666fe9c4caba99741b55 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 22 May 2025 10:25:32 +0200 Subject: [PATCH 1/8] Getenv --- conf/config.neon | 5 ++ resources/functionMap_php80delta.php | 2 + src/Php/PhpVersion.php | 5 ++ .../Php/GetenvFunctionReturnTypeExtension.php | 49 +++++++++++++++++++ .../Analyser/NodeScopeResolverTest.php | 6 +++ tests/PHPStan/Analyser/data/getenv-php74.php | 17 +++++++ tests/PHPStan/Analyser/data/getenv-php80.php | 17 +++++++ .../CallToFunctionParametersRuleTest.php | 14 ++++++ .../Rules/Functions/data/bug-13065.php | 15 ++++++ 9 files changed, 130 insertions(+) create mode 100644 src/Type/Php/GetenvFunctionReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/data/getenv-php74.php create mode 100644 tests/PHPStan/Analyser/data/getenv-php80.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-13065.php diff --git a/conf/config.neon b/conf/config.neon index 099a7e214b..8eb0b0bf55 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1541,6 +1541,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\GetenvFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\GetParentClassDynamicFunctionReturnTypeExtension tags: diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index c6a9dfe91a..c55054122a 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -78,6 +78,8 @@ 'imagejpeg' => ['bool', 'im'=>'GdImage', 'filename='=>'string|resource|null', 'quality='=>'int'], 'imagerotate' => ['false|object', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], 'imagescale' => ['false|object', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], + 'getenv' => ['array|string|false', 'varname'=>'string|null', 'local_only='=>'bool'], + 'getenv\'1' => ['array'], 'ldap_set_rebind_proc' => ['bool', 'ldap'=>'resource', 'callback'=>'?callable'], 'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string'], 'mb_encoding_aliases' => ['list', 'encoding'=>'string'], diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index b275c464eb..9ef01c8475 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -353,4 +353,9 @@ public function substrReturnFalseInsteadOfEmptyString(): bool return $this->versionId < 80000; } + public function getenvAcceptsNull(): bool + { + return $this->versionId >= 80000; + } + } diff --git a/src/Type/Php/GetenvFunctionReturnTypeExtension.php b/src/Type/Php/GetenvFunctionReturnTypeExtension.php new file mode 100644 index 0000000000..40db69a697 --- /dev/null +++ b/src/Type/Php/GetenvFunctionReturnTypeExtension.php @@ -0,0 +1,49 @@ +getName() === 'getenv'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type + { + if (!$this->phpVersion->getenvAcceptsNull()) { + return null; + } + if (count($functionCall->getArgs()) < 1) { + return null; + } + + $argType = $scope->getType($functionCall->getArgs()[0]->value); + if ($argType->isNull()->yes()) { + return new ArrayType(new StringType(), new StringType()); + } + if ($argType->isNull()->no()) { + return new UnionType([new StringType(), new ConstantBooleanType(false)]); + } + + return null; + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 1e3afb4e70..2ce2ec4583 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -57,6 +57,12 @@ private static function findTestFiles(): iterable yield __DIR__ . '/data/explode-php80.php'; } + if (PHP_VERSION_ID < 80000) { + yield __DIR__ . '/data/getenv-php74.php'; + } else { + yield __DIR__ . '/data/getenv-php80.php'; + } + if (PHP_VERSION_ID >= 80000) { yield __DIR__ . '/../Reflection/data/unionTypes.php'; yield __DIR__ . '/../Reflection/data/mixedType.php'; diff --git a/tests/PHPStan/Analyser/data/getenv-php74.php b/tests/PHPStan/Analyser/data/getenv-php74.php new file mode 100644 index 0000000000..2f2540eeef --- /dev/null +++ b/tests/PHPStan/Analyser/data/getenv-php74.php @@ -0,0 +1,17 @@ +', getenv()); + assertType('string|false', getenv('foo')); + } + +} diff --git a/tests/PHPStan/Analyser/data/getenv-php80.php b/tests/PHPStan/Analyser/data/getenv-php80.php new file mode 100644 index 0000000000..17610b82b3 --- /dev/null +++ b/tests/PHPStan/Analyser/data/getenv-php80.php @@ -0,0 +1,17 @@ +', getenv(null)); + assertType('array', getenv()); + assertType('string|false', getenv('foo')); + } + +} diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 31538252d8..e6347cdd8d 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2102,4 +2102,18 @@ public function testBug12499(): void $this->analyse([__DIR__ . '/data/bug-12499.php'], []); } + public function testBug13065(): void + { + if (PHP_VERSION_ID < 80000) { + $this->analyse([__DIR__ . '/data/bug-13065.php'], [ + [ + 'Parameter #1 $varname of function getenv expects string, null given.', + 10, + ], + ]); + } else { + $this->analyse([__DIR__ . '/data/bug-13065.php'], []); + } + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-13065.php b/tests/PHPStan/Rules/Functions/data/bug-13065.php new file mode 100644 index 0000000000..f63acf9989 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-13065.php @@ -0,0 +1,15 @@ + Date: Thu, 22 May 2025 11:27:22 +0200 Subject: [PATCH 2/8] Review --- .../CallToFunctionParametersRuleTest.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index e6347cdd8d..9358802c16 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2104,16 +2104,15 @@ public function testBug12499(): void public function testBug13065(): void { + $errors = []; if (PHP_VERSION_ID < 80000) { - $this->analyse([__DIR__ . '/data/bug-13065.php'], [ - [ - 'Parameter #1 $varname of function getenv expects string, null given.', - 10, - ], - ]); - } else { - $this->analyse([__DIR__ . '/data/bug-13065.php'], []); + $errors[] = [ + 'Parameter #1 $varname of function getenv expects string, null given.', + 10, + ]; } + + $this->analyse([__DIR__ . '/data/bug-13065.php'], $errors); } } From 9c05317656b9dc3ba32d66fc0e39ee0f94f3fadc Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 22 May 2025 11:35:46 +0200 Subject: [PATCH 3/8] Try --- conf/config.neon | 5 ----- resources/functionMap_php80delta.php | 6 ++++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 8eb0b0bf55..099a7e214b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1541,11 +1541,6 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: PHPStan\Type\Php\GetenvFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\GetParentClassDynamicFunctionReturnTypeExtension tags: diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index c55054122a..afe46d90e2 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -78,8 +78,10 @@ 'imagejpeg' => ['bool', 'im'=>'GdImage', 'filename='=>'string|resource|null', 'quality='=>'int'], 'imagerotate' => ['false|object', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], 'imagescale' => ['false|object', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], - 'getenv' => ['array|string|false', 'varname'=>'string|null', 'local_only='=>'bool'], - 'getenv\'1' => ['array'], + 'getenv' => ['array', 'varname'=>'null', 'local_only='=>'bool'], + 'getenv\'1' => ['string|false', 'varname'=>'string', 'local_only='=>'bool'], + 'getenv\'2' => ['array|string|false', 'varname'=>'string|null', 'local_only='=>'bool'], + 'getenv\'3' => ['array'], 'ldap_set_rebind_proc' => ['bool', 'ldap'=>'resource', 'callback'=>'?callable'], 'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string'], 'mb_encoding_aliases' => ['list', 'encoding'=>'string'], From ebe11b802ae530cc31aeb83a7153824f95758e70 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 22 May 2025 11:43:59 +0200 Subject: [PATCH 4/8] Revert "Try" This reverts commit 26db34a8ea005476e067fd2fe79bd3596b4935e3. --- conf/config.neon | 5 +++++ resources/functionMap_php80delta.php | 6 ++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 099a7e214b..8eb0b0bf55 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1541,6 +1541,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\GetenvFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\GetParentClassDynamicFunctionReturnTypeExtension tags: diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index afe46d90e2..c55054122a 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -78,10 +78,8 @@ 'imagejpeg' => ['bool', 'im'=>'GdImage', 'filename='=>'string|resource|null', 'quality='=>'int'], 'imagerotate' => ['false|object', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], 'imagescale' => ['false|object', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], - 'getenv' => ['array', 'varname'=>'null', 'local_only='=>'bool'], - 'getenv\'1' => ['string|false', 'varname'=>'string', 'local_only='=>'bool'], - 'getenv\'2' => ['array|string|false', 'varname'=>'string|null', 'local_only='=>'bool'], - 'getenv\'3' => ['array'], + 'getenv' => ['array|string|false', 'varname'=>'string|null', 'local_only='=>'bool'], + 'getenv\'1' => ['array'], 'ldap_set_rebind_proc' => ['bool', 'ldap'=>'resource', 'callback'=>'?callable'], 'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string'], 'mb_encoding_aliases' => ['list', 'encoding'=>'string'], From 6bc4f2cca53ec68c0a9eb9d230cd98afb0990ac4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 22 May 2025 14:39:33 +0200 Subject: [PATCH 5/8] Try again --- conf/config.neon | 5 -- resources/functionMap_php80delta.php | 4 +- .../Php/GetenvFunctionReturnTypeExtension.php | 49 ------------------- 3 files changed, 2 insertions(+), 56 deletions(-) delete mode 100644 src/Type/Php/GetenvFunctionReturnTypeExtension.php diff --git a/conf/config.neon b/conf/config.neon index 8eb0b0bf55..099a7e214b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1541,11 +1541,6 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: PHPStan\Type\Php\GetenvFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\GetParentClassDynamicFunctionReturnTypeExtension tags: diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index c55054122a..ec070388a3 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -78,8 +78,8 @@ 'imagejpeg' => ['bool', 'im'=>'GdImage', 'filename='=>'string|resource|null', 'quality='=>'int'], 'imagerotate' => ['false|object', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], 'imagescale' => ['false|object', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], - 'getenv' => ['array|string|false', 'varname'=>'string|null', 'local_only='=>'bool'], - 'getenv\'1' => ['array'], + 'getenv' => ['string|false', 'varname'=>'string', 'local_only='=>'bool'], + 'getenv\'1' => ['array', 'varname='=>'null', 'local_only='=>'bool'], 'ldap_set_rebind_proc' => ['bool', 'ldap'=>'resource', 'callback'=>'?callable'], 'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string'], 'mb_encoding_aliases' => ['list', 'encoding'=>'string'], diff --git a/src/Type/Php/GetenvFunctionReturnTypeExtension.php b/src/Type/Php/GetenvFunctionReturnTypeExtension.php deleted file mode 100644 index 40db69a697..0000000000 --- a/src/Type/Php/GetenvFunctionReturnTypeExtension.php +++ /dev/null @@ -1,49 +0,0 @@ -getName() === 'getenv'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type - { - if (!$this->phpVersion->getenvAcceptsNull()) { - return null; - } - if (count($functionCall->getArgs()) < 1) { - return null; - } - - $argType = $scope->getType($functionCall->getArgs()[0]->value); - if ($argType->isNull()->yes()) { - return new ArrayType(new StringType(), new StringType()); - } - if ($argType->isNull()->no()) { - return new UnionType([new StringType(), new ConstantBooleanType(false)]); - } - - return null; - } - -} From 647fc095fbf82b88e3ea5e1188128a0f56fd9a20 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 22 May 2025 16:07:47 +0200 Subject: [PATCH 6/8] Add test --- tests/PHPStan/Analyser/data/getenv-php74.php | 5 ++++- tests/PHPStan/Analyser/data/getenv-php80.php | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Analyser/data/getenv-php74.php b/tests/PHPStan/Analyser/data/getenv-php74.php index 2f2540eeef..f8769b28cd 100644 --- a/tests/PHPStan/Analyser/data/getenv-php74.php +++ b/tests/PHPStan/Analyser/data/getenv-php74.php @@ -7,11 +7,14 @@ class Foo { - public function test() + public function test(string|null $stringOrNull, mixed $mixed) { assertType('string|false', getenv(null)); assertType('array', getenv()); assertType('string|false', getenv('foo')); + + assertType('string|false', getenv($stringOrNull)); + assertType('string|false', getenv($mixed)); } } diff --git a/tests/PHPStan/Analyser/data/getenv-php80.php b/tests/PHPStan/Analyser/data/getenv-php80.php index 17610b82b3..572dea1700 100644 --- a/tests/PHPStan/Analyser/data/getenv-php80.php +++ b/tests/PHPStan/Analyser/data/getenv-php80.php @@ -7,11 +7,14 @@ class Foo { - public function test() + public function test(string|null $stringOrNull, mixed $mixed) { assertType('array', getenv(null)); assertType('array', getenv()); assertType('string|false', getenv('foo')); + + assertType('array|string|false', getenv($stringOrNull)); + assertType('array|string|false', getenv($mixed)); } } From b4d341013801ca583c1b81af77cf82ba05acc403 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 22 May 2025 16:22:08 +0200 Subject: [PATCH 7/8] Move to nsrt --- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 6 ------ tests/PHPStan/Analyser/{data => nsrt}/getenv-php74.php | 2 +- tests/PHPStan/Analyser/{data => nsrt}/getenv-php80.php | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) rename tests/PHPStan/Analyser/{data => nsrt}/getenv-php74.php (94%) rename tests/PHPStan/Analyser/{data => nsrt}/getenv-php80.php (95%) diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 2ce2ec4583..1e3afb4e70 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -57,12 +57,6 @@ private static function findTestFiles(): iterable yield __DIR__ . '/data/explode-php80.php'; } - if (PHP_VERSION_ID < 80000) { - yield __DIR__ . '/data/getenv-php74.php'; - } else { - yield __DIR__ . '/data/getenv-php80.php'; - } - if (PHP_VERSION_ID >= 80000) { yield __DIR__ . '/../Reflection/data/unionTypes.php'; yield __DIR__ . '/../Reflection/data/mixedType.php'; diff --git a/tests/PHPStan/Analyser/data/getenv-php74.php b/tests/PHPStan/Analyser/nsrt/getenv-php74.php similarity index 94% rename from tests/PHPStan/Analyser/data/getenv-php74.php rename to tests/PHPStan/Analyser/nsrt/getenv-php74.php index f8769b28cd..1a0c3721d7 100644 --- a/tests/PHPStan/Analyser/data/getenv-php74.php +++ b/tests/PHPStan/Analyser/nsrt/getenv-php74.php @@ -1,4 +1,4 @@ -= 80 namespace GetenvPHP80; From 7b176f3183ce25f17bd6a5f3491bbc4bba06d1c7 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 22 May 2025 16:51:19 +0200 Subject: [PATCH 8/8] Fix --- src/Php/PhpVersion.php | 5 ----- tests/PHPStan/Analyser/nsrt/getenv-php74.php | 9 ++++++--- tests/PHPStan/Analyser/nsrt/getenv-php80.php | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 9ef01c8475..b275c464eb 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -353,9 +353,4 @@ public function substrReturnFalseInsteadOfEmptyString(): bool return $this->versionId < 80000; } - public function getenvAcceptsNull(): bool - { - return $this->versionId >= 80000; - } - } diff --git a/tests/PHPStan/Analyser/nsrt/getenv-php74.php b/tests/PHPStan/Analyser/nsrt/getenv-php74.php index 1a0c3721d7..a100e47686 100644 --- a/tests/PHPStan/Analyser/nsrt/getenv-php74.php +++ b/tests/PHPStan/Analyser/nsrt/getenv-php74.php @@ -1,4 +1,4 @@ -', getenv()); diff --git a/tests/PHPStan/Analyser/nsrt/getenv-php80.php b/tests/PHPStan/Analyser/nsrt/getenv-php80.php index 63b50d7db9..c5036fa153 100644 --- a/tests/PHPStan/Analyser/nsrt/getenv-php80.php +++ b/tests/PHPStan/Analyser/nsrt/getenv-php80.php @@ -1,4 +1,4 @@ -= 80 += 8.0 namespace GetenvPHP80;