From 41abe14809d8d560772611a2cd881dad77319435 Mon Sep 17 00:00:00 2001 From: Thomas Gnandt Date: Wed, 9 Feb 2022 21:09:59 +0100 Subject: [PATCH 1/6] fix parameter type of *val-functions --- resources/functionMap.php | 8 +- .../Analyser/AnalyserIntegrationTest.php | 36 ++++++ .../Analyser/data/typeval-functions.php | 107 ++++++++++++++++++ 3 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/typeval-functions.php diff --git a/resources/functionMap.php b/resources/functionMap.php index b458a56265..3829003324 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -2020,7 +2020,7 @@ 'DomXsltStylesheet::result_dump_mem' => ['string', 'xmldoc'=>'DOMDocument'], 'DOTNET::__construct' => ['void', 'assembly_name'=>'string', 'class_name'=>'string', 'codepage='=>'int'], 'dotnet_load' => ['int', 'assembly_name'=>'string', 'datatype_name='=>'string', 'codepage='=>'int'], -'doubleval' => ['float', 'var'=>'mixed'], +'doubleval' => ['float', 'var'=>'array|bool|float|int|resource|string|null'], 'Ds\Collection::clear' => ['void'], 'Ds\Collection::copy' => ['Ds\Collection'], 'Ds\Collection::isEmpty' => ['bool'], @@ -2988,7 +2988,7 @@ 'finfo_file' => ['string|false', 'finfo'=>'resource', 'file_name'=>'string', 'options='=>'int', 'context='=>'resource'], 'finfo_open' => ['resource|false', 'options='=>'int', 'arg='=>'string'], 'finfo_set_flags' => ['bool', 'finfo'=>'resource', 'options'=>'int'], -'floatval' => ['float', 'var'=>'mixed'], +'floatval' => ['float', 'var'=>'array|bool|float|int|resource|string|null'], 'flock' => ['bool', 'fp'=>'resource', 'operation'=>'int', '&w_wouldblock='=>'int'], 'floor' => ['float|false', 'number'=>'float'], 'flush' => ['void'], @@ -5607,7 +5607,7 @@ 'intltz_to_date_time_zone' => ['DateTimeZone|false', 'obj'=>''], 'intltz_use_daylight_time' => ['bool', 'obj'=>''], 'intlz_create_default' => ['IntlTimeZone'], -'intval' => ['int', 'var'=>'mixed', 'base='=>'int'], +'intval' => ['int', 'var'=>'array|bool|float|int|resource|string|null', 'base='=>'int'], 'InvalidArgumentException::__clone' => ['void'], 'InvalidArgumentException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'(?Throwable)|(?InvalidArgumentException)'], 'InvalidArgumentException::__toString' => ['string'], @@ -11797,7 +11797,7 @@ 'strtoupper' => ['string', 'str'=>'string'], 'strtr' => ['string', 'str'=>'string', 'from'=>'string', 'to'=>'string'], 'strtr\'1' => ['string', 'str'=>'string', 'replace_pairs'=>'array'], -'strval' => ['string', 'var'=>'mixed'], +'strval' => ['string', 'var'=>'bool|float|int|resource|string|null'], 'substr' => ['string', 'string'=>'string', 'start'=>'int', 'length='=>'int'], 'substr_compare' => ['int|false', 'main_str'=>'string', 'str'=>'string', 'offset'=>'int', 'length='=>'int', 'case_sensitivity='=>'bool'], 'substr_count' => ['0|positive-int', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'length='=>'int'], diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 3de557c17b..8e26fee89d 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -541,6 +541,42 @@ public function testBug6501(): void $this->assertNoErrors($errors); } + public function testTypevalFamilyFunctionParameters(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/typeval-functions.php'); + $this->assertCount(12, $errors); + + $this->assertSame('Cannot cast array{} to string.', $errors[0]->getMessage()); + $this->assertSame(19, $errors[0]->getLine()); + $this->assertSame('Parameter #1 $value of function strval expects bool|float|int|resource|string|null, array given.', $errors[1]->getMessage()); + $this->assertSame(20, $errors[1]->getLine()); + + $this->assertSame('Cannot cast resource to float.', $errors[2]->getMessage()); + $this->assertSame(65, $errors[2]->getLine()); + + $this->assertSame('Cannot cast stdClass to string.', $errors[3]->getMessage()); + $this->assertSame(79, $errors[3]->getLine()); + $this->assertSame('Parameter #1 $value of function strval expects bool|float|int|resource|string|null, stdClass given.', $errors[4]->getMessage()); + $this->assertSame(80, $errors[4]->getLine()); + + $this->assertSame('Cannot cast stdClass to int.', $errors[5]->getMessage()); + $this->assertSame(82, $errors[5]->getLine()); + $this->assertSame('Parameter #1 $value of function intval expects array|bool|float|int|resource|string|null, stdClass given.', $errors[6]->getMessage()); + $this->assertSame(83, $errors[6]->getLine()); + + $this->assertSame('Cannot cast stdClass to float.', $errors[7]->getMessage()); + $this->assertSame(85, $errors[7]->getLine()); + $this->assertSame('Parameter #1 $value of function floatval expects array|bool|float|int|resource|string|null, stdClass given.', $errors[8]->getMessage()); + $this->assertSame(86, $errors[8]->getLine()); + $this->assertSame('Parameter #1 $value of function doubleval expects array|bool|float|int|resource|string|null, stdClass given.', $errors[9]->getMessage()); + $this->assertSame(87, $errors[9]->getLine()); + + $this->assertSame('Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to int.', $errors[10]->getMessage()); + $this->assertSame(92, $errors[10]->getLine()); + $this->assertSame('Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to float.', $errors[11]->getMessage()); + $this->assertSame(95, $errors[11]->getLine()); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/typeval-functions.php b/tests/PHPStan/Analyser/data/typeval-functions.php new file mode 100644 index 0000000000..e94772abf9 --- /dev/null +++ b/tests/PHPStan/Analyser/data/typeval-functions.php @@ -0,0 +1,107 @@ + Date: Tue, 8 Feb 2022 19:22:09 +0100 Subject: [PATCH 2/6] disallow Stringable in intval, floatval, doubleval --- conf/config.level2.neon | 1 + .../Functions/TypevalFamilyParametersRule.php | 90 +++++++++++++++++++ .../Analyser/AnalyserIntegrationTest.php | 13 ++- .../TypevalFamilyParametersRuleTest.php | 39 ++++++++ .../PHPStan/Rules/Functions/data/typeval.php | 16 ++++ 5 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 src/Rules/Functions/TypevalFamilyParametersRule.php create mode 100644 tests/PHPStan/Rules/Functions/TypevalFamilyParametersRuleTest.php create mode 100644 tests/PHPStan/Rules/Functions/data/typeval.php diff --git a/conf/config.level2.neon b/conf/config.level2.neon index becc7b5b02..02807935d4 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -14,6 +14,7 @@ rules: - PHPStan\Rules\Classes\AccessPrivateConstantThroughStaticRule - PHPStan\Rules\Comparison\UsageOfVoidMatchExpressionRule - PHPStan\Rules\Functions\IncompatibleDefaultParameterTypeRule + - PHPStan\Rules\Functions\TypevalFamilyParametersRule - PHPStan\Rules\Generics\ClassAncestorsRule - PHPStan\Rules\Generics\ClassTemplateTypeRule - PHPStan\Rules\Generics\EnumAncestorsRule diff --git a/src/Rules/Functions/TypevalFamilyParametersRule.php b/src/Rules/Functions/TypevalFamilyParametersRule.php new file mode 100644 index 0000000000..eaa0a69384 --- /dev/null +++ b/src/Rules/Functions/TypevalFamilyParametersRule.php @@ -0,0 +1,90 @@ + + */ +class TypevalFamilyParametersRule implements Rule +{ + + private const FUNCTIONS = [ + 'intval', + 'floatval', + 'doubleval', + ]; + + public function __construct( + private RuleLevelHelper $ruleLevelHelper, + ) + { + } + + public function getNodeType(): string + { + return Node\Expr\FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!($node->name instanceof Node\Name)) { + return []; + } + $name = strtolower($node->name->toString()); + if (!in_array($name, self::FUNCTIONS, true)) { + return []; + } + if (count($node->getArgs()) === 0) { + return []; + } + + $paramTypeCallback = static fn (Type $type): Type => $type->toString(); + + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->getArgs()[0]->value, + '', + static function (Type $type) use ($paramTypeCallback): bool { + $paramType = $paramTypeCallback($type); + + return !$paramType instanceof ErrorType; + }, + ); + $type = $typeResult->getType(); + $base = new ObjectType(''); + + if ($type instanceof ErrorType || !$base->isSuperTypeOf($type)->maybe()) { + return []; + } + + $paramType = $paramTypeCallback($type); + // only check if a stringable object is given, everything else is handled by the parameter typehint + if (!$paramType instanceof ErrorType) { + + return [ + RuleErrorBuilder::message(sprintf( + 'Parameter #1 $value of function %s does not accept object, %s given.', + $name, + $scope->getType($node->getArgs()[0]->value)->describe(VerbosityLevel::value()), + ))->line($node->getLine())->build(), + ]; + } + + return []; + } + +} diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 8e26fee89d..557689b002 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -544,7 +544,7 @@ public function testBug6501(): void public function testTypevalFamilyFunctionParameters(): void { $errors = $this->runAnalyse(__DIR__ . '/data/typeval-functions.php'); - $this->assertCount(12, $errors); + $this->assertCount(15, $errors); $this->assertSame('Cannot cast array{} to string.', $errors[0]->getMessage()); $this->assertSame(19, $errors[0]->getLine()); @@ -573,8 +573,15 @@ public function testTypevalFamilyFunctionParameters(): void $this->assertSame('Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to int.', $errors[10]->getMessage()); $this->assertSame(92, $errors[10]->getLine()); - $this->assertSame('Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to float.', $errors[11]->getMessage()); - $this->assertSame(95, $errors[11]->getLine()); + $this->assertSame('Parameter #1 $value of function intval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $errors[11]->getMessage()); + $this->assertSame(93, $errors[11]->getLine()); + + $this->assertSame('Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to float.', $errors[12]->getMessage()); + $this->assertSame(95, $errors[12]->getLine()); + $this->assertSame('Parameter #1 $value of function floatval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $errors[13]->getMessage()); + $this->assertSame(96, $errors[13]->getLine()); + $this->assertSame('Parameter #1 $value of function doubleval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $errors[14]->getMessage()); + $this->assertSame(97, $errors[14]->getLine()); } /** diff --git a/tests/PHPStan/Rules/Functions/TypevalFamilyParametersRuleTest.php b/tests/PHPStan/Rules/Functions/TypevalFamilyParametersRuleTest.php new file mode 100644 index 0000000000..21acb08f9d --- /dev/null +++ b/tests/PHPStan/Rules/Functions/TypevalFamilyParametersRuleTest.php @@ -0,0 +1,39 @@ + + */ +class TypevalFamilyParametersRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $broker = $this->createReflectionProvider(); + return new TypevalFamilyParametersRule(new RuleLevelHelper($broker, true, false, true, false)); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/typeval.php'], [ + [ + 'Parameter #1 $value of function intval does not accept object, class@anonymous/tests/PHPStan/Rules/Functions/data/typeval.php:3 given.', + 10, + ], + [ + 'Parameter #1 $value of function floatval does not accept object, class@anonymous/tests/PHPStan/Rules/Functions/data/typeval.php:3 given.', + 13, + ], + [ + 'Parameter #1 $value of function doubleval does not accept object, class@anonymous/tests/PHPStan/Rules/Functions/data/typeval.php:3 given.', + 16, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Functions/data/typeval.php b/tests/PHPStan/Rules/Functions/data/typeval.php new file mode 100644 index 0000000000..ca80641f06 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/typeval.php @@ -0,0 +1,16 @@ + Date: 2022年2月10日 19:08:39 +0100 Subject: [PATCH 3/6] allow casting resource to float --- src/Type/ResourceType.php | 2 +- .../Analyser/AnalyserIntegrationTest.php | 61 +++++++++---------- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/Type/ResourceType.php b/src/Type/ResourceType.php index afd0005665..d622ab6497 100644 --- a/src/Type/ResourceType.php +++ b/src/Type/ResourceType.php @@ -54,7 +54,7 @@ public function toInteger(): Type public function toFloat(): Type { - return new ErrorType(); + return new FloatType(); } public function toArray(): Type diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 557689b002..1ab01cd758 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -544,44 +544,41 @@ public function testBug6501(): void public function testTypevalFamilyFunctionParameters(): void { $errors = $this->runAnalyse(__DIR__ . '/data/typeval-functions.php'); - $this->assertCount(15, $errors); + $this->assertCount(14, $errors); $this->assertSame('Cannot cast array{} to string.', $errors[0]->getMessage()); $this->assertSame(19, $errors[0]->getLine()); $this->assertSame('Parameter #1 $value of function strval expects bool|float|int|resource|string|null, array given.', $errors[1]->getMessage()); $this->assertSame(20, $errors[1]->getLine()); - $this->assertSame('Cannot cast resource to float.', $errors[2]->getMessage()); - $this->assertSame(65, $errors[2]->getLine()); - - $this->assertSame('Cannot cast stdClass to string.', $errors[3]->getMessage()); - $this->assertSame(79, $errors[3]->getLine()); - $this->assertSame('Parameter #1 $value of function strval expects bool|float|int|resource|string|null, stdClass given.', $errors[4]->getMessage()); - $this->assertSame(80, $errors[4]->getLine()); - - $this->assertSame('Cannot cast stdClass to int.', $errors[5]->getMessage()); - $this->assertSame(82, $errors[5]->getLine()); - $this->assertSame('Parameter #1 $value of function intval expects array|bool|float|int|resource|string|null, stdClass given.', $errors[6]->getMessage()); - $this->assertSame(83, $errors[6]->getLine()); - - $this->assertSame('Cannot cast stdClass to float.', $errors[7]->getMessage()); - $this->assertSame(85, $errors[7]->getLine()); - $this->assertSame('Parameter #1 $value of function floatval expects array|bool|float|int|resource|string|null, stdClass given.', $errors[8]->getMessage()); - $this->assertSame(86, $errors[8]->getLine()); - $this->assertSame('Parameter #1 $value of function doubleval expects array|bool|float|int|resource|string|null, stdClass given.', $errors[9]->getMessage()); - $this->assertSame(87, $errors[9]->getLine()); - - $this->assertSame('Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to int.', $errors[10]->getMessage()); - $this->assertSame(92, $errors[10]->getLine()); - $this->assertSame('Parameter #1 $value of function intval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $errors[11]->getMessage()); - $this->assertSame(93, $errors[11]->getLine()); - - $this->assertSame('Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to float.', $errors[12]->getMessage()); - $this->assertSame(95, $errors[12]->getLine()); - $this->assertSame('Parameter #1 $value of function floatval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $errors[13]->getMessage()); - $this->assertSame(96, $errors[13]->getLine()); - $this->assertSame('Parameter #1 $value of function doubleval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $errors[14]->getMessage()); - $this->assertSame(97, $errors[14]->getLine()); + $this->assertSame('Cannot cast stdClass to string.', $errors[2]->getMessage()); + $this->assertSame(79, $errors[2]->getLine()); + $this->assertSame('Parameter #1 $value of function strval expects bool|float|int|resource|string|null, stdClass given.', $errors[3]->getMessage()); + $this->assertSame(80, $errors[3]->getLine()); + + $this->assertSame('Cannot cast stdClass to int.', $errors[4]->getMessage()); + $this->assertSame(82, $errors[4]->getLine()); + $this->assertSame('Parameter #1 $value of function intval expects array|bool|float|int|resource|string|null, stdClass given.', $errors[5]->getMessage()); + $this->assertSame(83, $errors[5]->getLine()); + + $this->assertSame('Cannot cast stdClass to float.', $errors[6]->getMessage()); + $this->assertSame(85, $errors[6]->getLine()); + $this->assertSame('Parameter #1 $value of function floatval expects array|bool|float|int|resource|string|null, stdClass given.', $errors[7]->getMessage()); + $this->assertSame(86, $errors[7]->getLine()); + $this->assertSame('Parameter #1 $value of function doubleval expects array|bool|float|int|resource|string|null, stdClass given.', $errors[8]->getMessage()); + $this->assertSame(87, $errors[8]->getLine()); + + $this->assertSame('Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to int.', $errors[9]->getMessage()); + $this->assertSame(92, $errors[9]->getLine()); + $this->assertSame('Parameter #1 $value of function intval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $errors[10]->getMessage()); + $this->assertSame(93, $errors[10]->getLine()); + + $this->assertSame('Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to float.', $errors[11]->getMessage()); + $this->assertSame(95, $errors[11]->getLine()); + $this->assertSame('Parameter #1 $value of function floatval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $errors[12]->getMessage()); + $this->assertSame(96, $errors[12]->getLine()); + $this->assertSame('Parameter #1 $value of function doubleval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $errors[13]->getMessage()); + $this->assertSame(97, $errors[13]->getLine()); } /** From 0238f94f65817b04a28ca76f00c5f449ea0412f8 Mon Sep 17 00:00:00 2001 From: Thomas Gnandt Date: 2022年2月10日 19:33:47 +0100 Subject: [PATCH 4/6] fix expectedparametername in php < 8 --- .../Functions/TypevalFamilyParametersRule.php | 4 +++- .../Analyser/AnalyserIntegrationTest.php | 21 ++++++++++++------- .../TypevalFamilyParametersRuleTest.php | 12 ++++++++--- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/Rules/Functions/TypevalFamilyParametersRule.php b/src/Rules/Functions/TypevalFamilyParametersRule.php index eaa0a69384..811f70678e 100644 --- a/src/Rules/Functions/TypevalFamilyParametersRule.php +++ b/src/Rules/Functions/TypevalFamilyParametersRule.php @@ -15,6 +15,7 @@ use function in_array; use function sprintf; use function strtolower; +use const PHP_VERSION_ID; /** * @implements Rule @@ -77,7 +78,8 @@ static function (Type $type) use ($paramTypeCallback): bool { return [ RuleErrorBuilder::message(sprintf( - 'Parameter #1 $value of function %s does not accept object, %s given.', + 'Parameter #1 %s of function %s does not accept object, %s given.', + PHP_VERSION_ID < 80000 ? '$var' : '$value', $name, $scope->getType($node->getArgs()[0]->value)->describe(VerbosityLevel::value()), ))->line($node->getLine())->build(), diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 1ab01cd758..9b14e6d1a3 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -15,6 +15,7 @@ use function extension_loaded; use function method_exists; use function restore_error_handler; +use function sprintf; use const PHP_VERSION_ID; class AnalyserIntegrationTest extends PHPStanTestCase @@ -545,39 +546,43 @@ public function testTypevalFamilyFunctionParameters(): void { $errors = $this->runAnalyse(__DIR__ . '/data/typeval-functions.php'); $this->assertCount(14, $errors); + $paramName = '$value'; + if (PHP_VERSION_ID < 80000) { + $paramName = '$var'; + } $this->assertSame('Cannot cast array{} to string.', $errors[0]->getMessage()); $this->assertSame(19, $errors[0]->getLine()); - $this->assertSame('Parameter #1 $value of function strval expects bool|float|int|resource|string|null, array given.', $errors[1]->getMessage()); + $this->assertSame(sprintf('Parameter #1 %s of function strval expects bool|float|int|resource|string|null, array given.', $paramName), $errors[1]->getMessage()); $this->assertSame(20, $errors[1]->getLine()); $this->assertSame('Cannot cast stdClass to string.', $errors[2]->getMessage()); $this->assertSame(79, $errors[2]->getLine()); - $this->assertSame('Parameter #1 $value of function strval expects bool|float|int|resource|string|null, stdClass given.', $errors[3]->getMessage()); + $this->assertSame(sprintf('Parameter #1 %s of function strval expects bool|float|int|resource|string|null, stdClass given.', $paramName), $errors[3]->getMessage()); $this->assertSame(80, $errors[3]->getLine()); $this->assertSame('Cannot cast stdClass to int.', $errors[4]->getMessage()); $this->assertSame(82, $errors[4]->getLine()); - $this->assertSame('Parameter #1 $value of function intval expects array|bool|float|int|resource|string|null, stdClass given.', $errors[5]->getMessage()); + $this->assertSame(sprintf('Parameter #1 %s of function intval expects array|bool|float|int|resource|string|null, stdClass given.', $paramName), $errors[5]->getMessage()); $this->assertSame(83, $errors[5]->getLine()); $this->assertSame('Cannot cast stdClass to float.', $errors[6]->getMessage()); $this->assertSame(85, $errors[6]->getLine()); - $this->assertSame('Parameter #1 $value of function floatval expects array|bool|float|int|resource|string|null, stdClass given.', $errors[7]->getMessage()); + $this->assertSame(sprintf('Parameter #1 %s of function floatval expects array|bool|float|int|resource|string|null, stdClass given.', $paramName), $errors[7]->getMessage()); $this->assertSame(86, $errors[7]->getLine()); - $this->assertSame('Parameter #1 $value of function doubleval expects array|bool|float|int|resource|string|null, stdClass given.', $errors[8]->getMessage()); + $this->assertSame(sprintf('Parameter #1 %s of function doubleval expects array|bool|float|int|resource|string|null, stdClass given.', $paramName), $errors[8]->getMessage()); $this->assertSame(87, $errors[8]->getLine()); $this->assertSame('Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to int.', $errors[9]->getMessage()); $this->assertSame(92, $errors[9]->getLine()); - $this->assertSame('Parameter #1 $value of function intval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $errors[10]->getMessage()); + $this->assertSame(sprintf('Parameter #1 %s of function intval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $paramName), $errors[10]->getMessage()); $this->assertSame(93, $errors[10]->getLine()); $this->assertSame('Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to float.', $errors[11]->getMessage()); $this->assertSame(95, $errors[11]->getLine()); - $this->assertSame('Parameter #1 $value of function floatval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $errors[12]->getMessage()); + $this->assertSame(sprintf('Parameter #1 %s of function floatval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $paramName), $errors[12]->getMessage()); $this->assertSame(96, $errors[12]->getLine()); - $this->assertSame('Parameter #1 $value of function doubleval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $errors[13]->getMessage()); + $this->assertSame(sprintf('Parameter #1 %s of function doubleval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $paramName), $errors[13]->getMessage()); $this->assertSame(97, $errors[13]->getLine()); } diff --git a/tests/PHPStan/Rules/Functions/TypevalFamilyParametersRuleTest.php b/tests/PHPStan/Rules/Functions/TypevalFamilyParametersRuleTest.php index 21acb08f9d..291f29ed6a 100644 --- a/tests/PHPStan/Rules/Functions/TypevalFamilyParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/TypevalFamilyParametersRuleTest.php @@ -5,6 +5,8 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use function sprintf; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -20,17 +22,21 @@ protected function getRule(): Rule public function testRule(): void { + $paramName = '$value'; + if (PHP_VERSION_ID < 80000) { + $paramName = '$var'; + } $this->analyse([__DIR__ . '/data/typeval.php'], [ [ - 'Parameter #1 $value of function intval does not accept object, class@anonymous/tests/PHPStan/Rules/Functions/data/typeval.php:3 given.', + sprintf('Parameter #1 %s of function intval does not accept object, class@anonymous/tests/PHPStan/Rules/Functions/data/typeval.php:3 given.', $paramName), 10, ], [ - 'Parameter #1 $value of function floatval does not accept object, class@anonymous/tests/PHPStan/Rules/Functions/data/typeval.php:3 given.', + sprintf('Parameter #1 %s of function floatval does not accept object, class@anonymous/tests/PHPStan/Rules/Functions/data/typeval.php:3 given.', $paramName), 13, ], [ - 'Parameter #1 $value of function doubleval does not accept object, class@anonymous/tests/PHPStan/Rules/Functions/data/typeval.php:3 given.', + sprintf('Parameter #1 %s of function doubleval does not accept object, class@anonymous/tests/PHPStan/Rules/Functions/data/typeval.php:3 given.', $paramName), 16, ], ]); From 9918f6e78d02e335fff7b7f7da4239c828e32757 Mon Sep 17 00:00:00 2001 From: Thomas Gnandt Date: 2022年2月18日 08:46:14 +0100 Subject: [PATCH 5/6] simplfy error listing --- .../Analyser/AnalyserIntegrationTest.php | 60 +++++++++---------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 9b14e6d1a3..3018a6f1bc 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -12,6 +12,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use function array_reverse; +use function count; use function extension_loaded; use function method_exists; use function restore_error_handler; @@ -545,45 +546,38 @@ public function testBug6501(): void public function testTypevalFamilyFunctionParameters(): void { $errors = $this->runAnalyse(__DIR__ . '/data/typeval-functions.php'); - $this->assertCount(14, $errors); $paramName = '$value'; if (PHP_VERSION_ID < 80000) { $paramName = '$var'; } - $this->assertSame('Cannot cast array{} to string.', $errors[0]->getMessage()); - $this->assertSame(19, $errors[0]->getLine()); - $this->assertSame(sprintf('Parameter #1 %s of function strval expects bool|float|int|resource|string|null, array given.', $paramName), $errors[1]->getMessage()); - $this->assertSame(20, $errors[1]->getLine()); + $expectedErrors = [ + ['Cannot cast array{} to string.', 19], + [sprintf('Parameter #1 %s of function strval expects bool|float|int|resource|string|null, array given.', $paramName), 20], - $this->assertSame('Cannot cast stdClass to string.', $errors[2]->getMessage()); - $this->assertSame(79, $errors[2]->getLine()); - $this->assertSame(sprintf('Parameter #1 %s of function strval expects bool|float|int|resource|string|null, stdClass given.', $paramName), $errors[3]->getMessage()); - $this->assertSame(80, $errors[3]->getLine()); - - $this->assertSame('Cannot cast stdClass to int.', $errors[4]->getMessage()); - $this->assertSame(82, $errors[4]->getLine()); - $this->assertSame(sprintf('Parameter #1 %s of function intval expects array|bool|float|int|resource|string|null, stdClass given.', $paramName), $errors[5]->getMessage()); - $this->assertSame(83, $errors[5]->getLine()); - - $this->assertSame('Cannot cast stdClass to float.', $errors[6]->getMessage()); - $this->assertSame(85, $errors[6]->getLine()); - $this->assertSame(sprintf('Parameter #1 %s of function floatval expects array|bool|float|int|resource|string|null, stdClass given.', $paramName), $errors[7]->getMessage()); - $this->assertSame(86, $errors[7]->getLine()); - $this->assertSame(sprintf('Parameter #1 %s of function doubleval expects array|bool|float|int|resource|string|null, stdClass given.', $paramName), $errors[8]->getMessage()); - $this->assertSame(87, $errors[8]->getLine()); - - $this->assertSame('Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to int.', $errors[9]->getMessage()); - $this->assertSame(92, $errors[9]->getLine()); - $this->assertSame(sprintf('Parameter #1 %s of function intval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $paramName), $errors[10]->getMessage()); - $this->assertSame(93, $errors[10]->getLine()); - - $this->assertSame('Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to float.', $errors[11]->getMessage()); - $this->assertSame(95, $errors[11]->getLine()); - $this->assertSame(sprintf('Parameter #1 %s of function floatval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $paramName), $errors[12]->getMessage()); - $this->assertSame(96, $errors[12]->getLine()); - $this->assertSame(sprintf('Parameter #1 %s of function doubleval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $paramName), $errors[13]->getMessage()); - $this->assertSame(97, $errors[13]->getLine()); + ['Cannot cast stdClass to string.', 79], + [sprintf('Parameter #1 %s of function strval expects bool|float|int|resource|string|null, stdClass given.', $paramName),80], + + ['Cannot cast stdClass to int.', 82], + [sprintf('Parameter #1 %s of function intval expects array|bool|float|int|resource|string|null, stdClass given.', $paramName),83], + + ['Cannot cast stdClass to float.', 85], + [sprintf('Parameter #1 %s of function floatval expects array|bool|float|int|resource|string|null, stdClass given.', $paramName),86], + [sprintf('Parameter #1 %s of function doubleval expects array|bool|float|int|resource|string|null, stdClass given.', $paramName),87], + + ['Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to int.', 92], + [sprintf('Parameter #1 %s of function intval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $paramName),93], + + ['Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to float.', 95], + [sprintf('Parameter #1 %s of function floatval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $paramName),96], + [sprintf('Parameter #1 %s of function doubleval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $paramName),97], + ]; + + $this->assertCount(count($expectedErrors), $errors); + foreach ($expectedErrors as $key => [$message, $line]) { + $this->assertSame($message, $errors[$key]->getMessage()); + $this->assertSame($line, $errors[$key]->getLine()); + } } /** From 3ab0c2b5c9e095441e8252e305b334a8a9d875f2 Mon Sep 17 00:00:00 2001 From: Thomas Gnandt Date: 2022年2月18日 08:46:41 +0100 Subject: [PATCH 6/6] disallow casting resource to float --- resources/functionMap.php | 4 ++-- src/Type/ResourceType.php | 2 +- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 3829003324..d30a6030e8 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -2020,7 +2020,7 @@ 'DomXsltStylesheet::result_dump_mem' => ['string', 'xmldoc'=>'DOMDocument'], 'DOTNET::__construct' => ['void', 'assembly_name'=>'string', 'class_name'=>'string', 'codepage='=>'int'], 'dotnet_load' => ['int', 'assembly_name'=>'string', 'datatype_name='=>'string', 'codepage='=>'int'], -'doubleval' => ['float', 'var'=>'array|bool|float|int|resource|string|null'], +'doubleval' => ['float', 'var'=>'array|bool|float|int|string|null'], 'Ds\Collection::clear' => ['void'], 'Ds\Collection::copy' => ['Ds\Collection'], 'Ds\Collection::isEmpty' => ['bool'], @@ -2988,7 +2988,7 @@ 'finfo_file' => ['string|false', 'finfo'=>'resource', 'file_name'=>'string', 'options='=>'int', 'context='=>'resource'], 'finfo_open' => ['resource|false', 'options='=>'int', 'arg='=>'string'], 'finfo_set_flags' => ['bool', 'finfo'=>'resource', 'options'=>'int'], -'floatval' => ['float', 'var'=>'array|bool|float|int|resource|string|null'], +'floatval' => ['float', 'var'=>'array|bool|float|int|string|null'], 'flock' => ['bool', 'fp'=>'resource', 'operation'=>'int', '&w_wouldblock='=>'int'], 'floor' => ['float|false', 'number'=>'float'], 'flush' => ['void'], diff --git a/src/Type/ResourceType.php b/src/Type/ResourceType.php index d622ab6497..afd0005665 100644 --- a/src/Type/ResourceType.php +++ b/src/Type/ResourceType.php @@ -54,7 +54,7 @@ public function toInteger(): Type public function toFloat(): Type { - return new FloatType(); + return new ErrorType(); } public function toArray(): Type diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 3018a6f1bc..e90d46555c 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -555,6 +555,10 @@ public function testTypevalFamilyFunctionParameters(): void ['Cannot cast array{} to string.', 19], [sprintf('Parameter #1 %s of function strval expects bool|float|int|resource|string|null, array given.', $paramName), 20], + ['Cannot cast resource to float.', 65], + [sprintf('Parameter #1 %s of function floatval expects array|bool|float|int|string|null, resource given.', $paramName),66], + [sprintf('Parameter #1 %s of function doubleval expects array|bool|float|int|string|null, resource given.', $paramName),67], + ['Cannot cast stdClass to string.', 79], [sprintf('Parameter #1 %s of function strval expects bool|float|int|resource|string|null, stdClass given.', $paramName),80], @@ -562,8 +566,8 @@ public function testTypevalFamilyFunctionParameters(): void [sprintf('Parameter #1 %s of function intval expects array|bool|float|int|resource|string|null, stdClass given.', $paramName),83], ['Cannot cast stdClass to float.', 85], - [sprintf('Parameter #1 %s of function floatval expects array|bool|float|int|resource|string|null, stdClass given.', $paramName),86], - [sprintf('Parameter #1 %s of function doubleval expects array|bool|float|int|resource|string|null, stdClass given.', $paramName),87], + [sprintf('Parameter #1 %s of function floatval expects array|bool|float|int|string|null, stdClass given.', $paramName),86], + [sprintf('Parameter #1 %s of function doubleval expects array|bool|float|int|string|null, stdClass given.', $paramName),87], ['Cannot cast class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 to int.', 92], [sprintf('Parameter #1 %s of function intval does not accept object, class@anonymous/tests/PHPStan/Analyser/data/typeval-functions.php:10 given.', $paramName),93],

AltStyle によって変換されたページ (->オリジナル) /