Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix the return types of round()/ceil()/floor()
- Loading branch information
1 parent
aaa8db3
commit afc49fd
Showing
7 changed files
with
214 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Type\Php; | ||
|
||
use PhpParser\Node\Expr\FuncCall; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Php\PhpVersion; | ||
use PHPStan\Reflection\FunctionReflection; | ||
use PHPStan\Type\BenevolentUnionType; | ||
use PHPStan\Type\Constant\ConstantBooleanType; | ||
use PHPStan\Type\DynamicFunctionReturnTypeExtension; | ||
use PHPStan\Type\FloatType; | ||
use PHPStan\Type\IntegerType; | ||
use PHPStan\Type\MixedType; | ||
use PHPStan\Type\NeverType; | ||
use PHPStan\Type\NullType; | ||
use PHPStan\Type\Type; | ||
use PHPStan\Type\TypeCombinator; | ||
use function count; | ||
use function in_array; | ||
|
||
class RoundFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension | ||
{ | ||
|
||
public function __construct(private PhpVersion $phpVersion) | ||
{ | ||
} | ||
|
||
public function isFunctionSupported(FunctionReflection $functionReflection): bool | ||
{ | ||
return in_array( | ||
$functionReflection->getName(), | ||
[ | ||
'round', | ||
'ceil', | ||
'floor', | ||
], | ||
true, | ||
); | ||
} | ||
|
||
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type | ||
{ | ||
if ($this->phpVersion->hasStricterRoundFunctions()) { | ||
// PHP 8 fatals with a missing parameter. | ||
$noArgsReturnType = new NeverType(true); | ||
// PHP 8 can either return a float or fatal. | ||
$defaultReturnType = new BenevolentUnionType([ | ||
new FloatType(), | ||
new NeverType(true), | ||
]); | ||
} else { | ||
// PHP 7 returns null with a missing parameter. | ||
$noArgsReturnType = new NullType(); | ||
// PHP 7 can return either a float or false. | ||
$defaultReturnType = new BenevolentUnionType([ | ||
new FloatType(), | ||
new ConstantBooleanType(false), | ||
]); | ||
} | ||
|
||
if (count($functionCall->getArgs()) < 1) { | ||
return $noArgsReturnType; | ||
} | ||
|
||
$firstArgType = $scope->getType($functionCall->getArgs()[0]->value); | ||
|
||
if ($firstArgType instanceof MixedType) { | ||
return $defaultReturnType; | ||
} | ||
|
||
if ($this->phpVersion->hasStricterRoundFunctions()) { | ||
$allowed = TypeCombinator::union( | ||
new IntegerType(), | ||
new FloatType(), | ||
); | ||
if (!$allowed->accepts($firstArgType, true)->yes()) { | ||
// PHP 8 fatals if the parameter is not an integer or float. | ||
return new NeverType(true); | ||
} | ||
} elseif ($firstArgType->isArray()->yes()) { | ||
// PHP 7 returns false if the parameter is an array. | ||
return new ConstantBooleanType(false); | ||
} | ||
|
||
return new FloatType(); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<?php | ||
|
||
namespace RoundFamilyTestPHP8; | ||
|
||
use function PHPStan\Testing\assertType; | ||
|
||
// Round | ||
assertType('float', round(123)); | ||
assertType('float', round(123.456)); | ||
assertType('float', round($_GET['foo'] / 60)); | ||
assertType('*NEVER*', round('123')); | ||
assertType('*NEVER*', round('123.456')); | ||
assertType('*NEVER*', round(null)); | ||
assertType('*NEVER*', round(true)); | ||
assertType('*NEVER*', round(false)); | ||
assertType('*NEVER*', round(new \stdClass)); | ||
assertType('*NEVER*', round('')); | ||
assertType('*NEVER*', round(array())); | ||
assertType('*NEVER*', round(array(123))); | ||
assertType('*NEVER*', round()); | ||
assertType('(*NEVER*|float)', round($_GET['foo'])); | ||
|
||
// Ceil | ||
assertType('float', ceil(123)); | ||
assertType('float', ceil(123.456)); | ||
assertType('float', ceil($_GET['foo'] / 60)); | ||
assertType('*NEVER*', ceil('123')); | ||
assertType('*NEVER*', ceil('123.456')); | ||
assertType('*NEVER*', ceil(null)); | ||
assertType('*NEVER*', ceil(true)); | ||
assertType('*NEVER*', ceil(false)); | ||
assertType('*NEVER*', ceil(new \stdClass)); | ||
assertType('*NEVER*', ceil('')); | ||
assertType('*NEVER*', ceil(array())); | ||
assertType('*NEVER*', ceil(array(123))); | ||
assertType('*NEVER*', ceil()); | ||
assertType('(*NEVER*|float)', ceil($_GET['foo'])); | ||
|
||
// Floor | ||
assertType('float', floor(123)); | ||
assertType('float', floor(123.456)); | ||
assertType('float', floor($_GET['foo'] / 60)); | ||
assertType('*NEVER*', floor('123')); | ||
assertType('*NEVER*', floor('123.456')); | ||
assertType('*NEVER*', floor(null)); | ||
assertType('*NEVER*', floor(true)); | ||
assertType('*NEVER*', floor(false)); | ||
assertType('*NEVER*', floor(new \stdClass)); | ||
assertType('*NEVER*', floor('')); | ||
assertType('*NEVER*', floor(array())); | ||
assertType('*NEVER*', floor(array(123))); | ||
assertType('*NEVER*', floor()); | ||
assertType('(*NEVER*|float)', floor($_GET['foo'])); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<?php | ||
|
||
namespace RoundFamilyTest; | ||
|
||
use function PHPStan\Testing\assertType; | ||
|
||
// Round | ||
assertType('float', round(123)); | ||
assertType('float', round(123.456)); | ||
assertType('float', round($_GET['foo'] / 60)); | ||
assertType('float', round('123')); | ||
assertType('float', round('123.456')); | ||
assertType('float', round(null)); | ||
assertType('float', round(true)); | ||
assertType('float', round(false)); | ||
assertType('float', round(new \stdClass)); | ||
assertType('float', round('')); | ||
assertType('false', round(array())); | ||
assertType('false', round(array(123))); | ||
assertType('null', round()); | ||
assertType('(float|false)', round($_GET['foo'])); | ||
|
||
// Ceil | ||
assertType('float', ceil(123)); | ||
assertType('float', ceil(123.456)); | ||
assertType('float', ceil($_GET['foo'] / 60)); | ||
assertType('float', ceil('123')); | ||
assertType('float', ceil('123.456')); | ||
assertType('float', ceil(null)); | ||
assertType('float', ceil(true)); | ||
assertType('float', ceil(false)); | ||
assertType('float', ceil(new \stdClass)); | ||
assertType('float', ceil('')); | ||
assertType('false', ceil(array())); | ||
assertType('false', ceil(array(123))); | ||
assertType('null', ceil()); | ||
assertType('(float|false)', ceil($_GET['foo'])); | ||
|
||
// Floor | ||
assertType('float', floor(123)); | ||
assertType('float', floor(123.456)); | ||
assertType('float', floor($_GET['foo'] / 60)); | ||
assertType('float', floor('123')); | ||
assertType('float', floor('123.456')); | ||
assertType('float', floor(null)); | ||
assertType('float', floor(true)); | ||
assertType('float', floor(false)); | ||
assertType('float', floor(new \stdClass)); | ||
assertType('float', floor('')); | ||
assertType('false', floor(array())); | ||
assertType('false', floor(array(123))); | ||
assertType('null', floor()); | ||
assertType('(float|false)', floor($_GET['foo'])); |