diff --git a/resources/functionMap.php b/resources/functionMap.php index 82789aea05..de1739df30 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -1650,7 +1650,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'=>'scalar|array|resource|null'], +'doubleval' => ['float', 'var'=>'int|float|__stringnotstringable|bool|array|resource|null'], 'Ds\Deque::__construct' => ['void', 'values='=>'mixed'], 'Ds\Deque::count' => ['0|positive-int'], 'Ds\Deque::jsonSerialize' => ['array'], @@ -2448,7 +2448,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'=>'scalar|array|resource|null'], +'floatval' => ['float', 'var'=>'int|float|__stringnotstringable|bool|array|resource|null'], 'flock' => ['bool', 'fp'=>'resource', 'operation'=>'int-mask', '&w_wouldblock='=>'0|1'], 'floor' => ['__benevolent', 'number'=>'float'], 'flush' => ['void'], @@ -5075,7 +5075,7 @@ 'intltz_to_date_time_zone' => ['DateTimeZone|false', 'obj'=>''], 'intltz_use_daylight_time' => ['bool', 'obj'=>''], 'intlz_create_default' => ['IntlTimeZone'], -'intval' => ['int', 'var'=>'scalar|array|resource|null', 'base='=>'int'], +'intval' => ['int', 'var'=>'int|float|__stringnotstringable|bool|array|resource|null', 'base='=>'int'], 'InvalidArgumentException::__clone' => ['void'], 'InvalidArgumentException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'(?Throwable)|(?InvalidArgumentException)'], 'InvalidArgumentException::__toString' => ['string'], diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index ea29d7c088..e8c2d91c75 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -98,6 +98,7 @@ use PHPStan\Type\StaticType; use PHPStan\Type\StaticTypeFactory; use PHPStan\Type\StringAlwaysAcceptingObjectWithToStringType; +use PHPStan\Type\StringNeverAcceptingObjectWithToStringType; use PHPStan\Type\StringType; use PHPStan\Type\ThisType; use PHPStan\Type\Type; @@ -462,6 +463,8 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return StaticTypeFactory::falsey(); case '__stringandstringable': return new StringAlwaysAcceptingObjectWithToStringType(); + case '__stringnotstringable': + return new StringNeverAcceptingObjectWithToStringType(); } if ($nameScope->getClassName() !== null) { diff --git a/src/Type/StringNeverAcceptingObjectWithToStringType.php b/src/Type/StringNeverAcceptingObjectWithToStringType.php new file mode 100644 index 0000000000..05aaad5467 --- /dev/null +++ b/src/Type/StringNeverAcceptingObjectWithToStringType.php @@ -0,0 +1,13 @@ +checkExplicitMixed = true; + $this->checkImplicitMixed = true; + + $varName = PHP_VERSION_ID < 80000 ? '$var' : '$value'; + $stringableName = PHP_VERSION_ID < 80000 ? 'class' : 'Stringable'; + + $this->analyse([__DIR__ . '/data/bug-6560.php'], [ + [ + sprintf('Parameter #1 %s of function strval expects bool|float|int|resource|string|null, array given.', $varName), + 20, + ], + [ + sprintf('Parameter #1 %s of function strval expects bool|float|int|resource|string|null, stdClass given.', $varName), + 74, + ], + [ + sprintf('Parameter #1 %s of function intval expects array|bool|float|int|resource|string|null, stdClass given.', $varName), + 77, + ], + [ + sprintf('Parameter #1 %s of function floatval expects array|bool|float|int|resource|string|null, stdClass given.', $varName), + 80, + ], + [ + sprintf('Parameter #1 %s of function intval expects array|bool|float|int|resource|string|null, %s@anonymous/tests/PHPStan/Rules/Functions/data/bug-6560.php:10 given.', $varName, $stringableName), + 86, + ], + [ + sprintf('Parameter #1 %s of function floatval expects array|bool|float|int|resource|string|null, %s@anonymous/tests/PHPStan/Rules/Functions/data/bug-6560.php:10 given.', $varName, $stringableName), + 89, + ], + [ + sprintf('Parameter #1 %s of function strval expects bool|float|int|resource|string|null, mixed given.', $varName), + 92, + ], + [ + sprintf('Parameter #1 %s of function intval expects array|bool|float|int|resource|string|null, mixed given.', $varName), + 95, + ], + [ + sprintf('Parameter #1 %s of function floatval expects array|bool|float|int|resource|string|null, mixed given.', $varName), + 98, + ], + ]); + } + #[RequiresPhp('< 8.0')] public function testArrayRandPhp7(): void { diff --git a/tests/PHPStan/Rules/Functions/data/bug-6560.php b/tests/PHPStan/Rules/Functions/data/bug-6560.php new file mode 100644 index 0000000000..a17ce895a3 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-6560.php @@ -0,0 +1,98 @@ +