Skip to content
Merged
2 changes: 1 addition & 1 deletion e2e/bug-15151/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function validate_array(mixed $val): ?array {
/**
* @template T of mixed
* @param T $val
* @return ($val is string ? T&string : ($val is int ? decimal-int-string : ($val is float ? ?numeric-string : null)))
* @return ($val is string ? T&string : ($val is int ? decimal-int-string : ($val is float ? ?non-empty-string : null)))
*/
function validate_string(mixed $val): ?string {
if (\is_string($val)) {
Expand Down
12 changes: 12 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,18 @@ parameters:
count: 2
path: src/Type/FiniteTypeSet.php

-
rawMessage: 'Doing instanceof PHPStan\Type\FloatType is error-prone and deprecated. Use Type::isFloat() instead.'
identifier: phpstanApi.instanceofType
count: 4
path: src/Type/FloatRangeType.php

-
rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated.
identifier: phpstanApi.instanceofType
count: 2
path: src/Type/FloatRangeType.php

-
rawMessage: 'Doing instanceof PHPStan\Type\FloatType is error-prone and deprecated. Use Type::isFloat() instead.'
identifier: phpstanApi.instanceofType
Expand Down
2 changes: 1 addition & 1 deletion resources/functionMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -5273,7 +5273,7 @@
'layerObj::setWKTProjection' => ['int', 'proj_params'=>'string'],
'layerObj::updateFromString' => ['int', 'snippet'=>'string'],
'lcfirst' => ['string', 'str'=>'string'],
'lcg_value' => ['float'],
'lcg_value' => ['float<0, 1>'],
'lchgrp' => ['bool', 'filename'=>'string', 'group'=>'string|int'],
'lchown' => ['bool', 'filename'=>'string', 'user'=>'string|int'],
'ldap_8859_to_t61' => ['string|false', 'value'=>'string'],
Expand Down
7 changes: 5 additions & 2 deletions src/Analyser/ExprHandler/BinaryOpHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
use PHPStan\Type\Accessory\NonEmptyArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantFloatType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\IntegerType;
Expand All @@ -57,6 +58,7 @@
use function spl_object_id;
use function sprintf;
use function strtolower;
use const NAN;

/**
* @implements ExprHandler<BinaryOp>
Expand Down Expand Up @@ -664,11 +666,12 @@ static function () use ($expr, $nativeTypesPromoted, $typeCallback): Type {
);
}
} elseif ($context->false()) {
// NAN is neither smaller nor greater than anything: the false branch keeps it
if (!$expr->left instanceof Scalar && !($expr->left instanceof Expr\UnaryMinus && $expr->left->expr instanceof Scalar)) {
$result = $result->unionWith(
$this->defaultNarrowingHelper->createForSubject(
$expr->left,
$orEqual ? $rightType->getGreaterType($this->phpVersion) : $rightType->getGreaterOrEqualType($this->phpVersion),
TypeCombinator::union($orEqual ? $rightType->getGreaterType($this->phpVersion) : $rightType->getGreaterOrEqualType($this->phpVersion), new ConstantFloatType(NAN)),
TypeSpecifierContext::createTruthy(),
$scope,
)->setRootExpr($expr),
Expand All @@ -678,7 +681,7 @@ static function () use ($expr, $nativeTypesPromoted, $typeCallback): Type {
$result = $result->unionWith(
$this->defaultNarrowingHelper->createForSubject(
$expr->right,
$orEqual ? $leftType->getSmallerType($this->phpVersion) : $leftType->getSmallerOrEqualType($this->phpVersion),
TypeCombinator::union($orEqual ? $leftType->getSmallerType($this->phpVersion) : $leftType->getSmallerOrEqualType($this->phpVersion), new ConstantFloatType(NAN)),
TypeSpecifierContext::createTruthy(),
$scope,
)->setRootExpr($expr),
Expand Down
5 changes: 5 additions & 0 deletions src/Php/PhpVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ public function supportsNamedArguments(): bool
return $this->versionId >= 80000;
}

public function throwsOnDivisionByZero(): bool
{
return $this->versionId >= 80000;
}

public function throwsTypeErrorForInternalFunctions(): bool
{
return $this->versionId >= 80000;
Expand Down
46 changes: 46 additions & 0 deletions src/PhpDoc/TypeNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Enum\EnumCaseObjectType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\FloatRangeType;
use PHPStan\Type\FloatType;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\Generic\GenericObjectType;
Expand Down Expand Up @@ -126,6 +127,7 @@
use function explode;
use function get_class;
use function in_array;
use function is_nan;
use function max;
use function min;
use function preg_match;
Expand All @@ -135,6 +137,8 @@
use function str_starts_with;
use function strtolower;
use function substr;
use const INF;
use const PHP_FLOAT_MAX;

/**
* @phpstan-import-type Level from ReportUnsafeArrayStringKeyCastingToggle as ReportUnsafeArrayStringKeyCastingLevel
Expand Down Expand Up @@ -254,6 +258,21 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
case 'non-negative-int':
return IntegerRangeType::fromInterval(0, null);

case 'positive-float':
return FloatRangeType::fromInterval(5.0E-324, INF);

case 'negative-float':
return FloatRangeType::fromInterval(-INF, -5.0E-324);

case 'non-positive-float':
return FloatRangeType::fromInterval(-INF, 0.0);

case 'non-negative-float':
return FloatRangeType::fromInterval(0.0, INF);

case 'finite-float':
return FloatRangeType::fromInterval(-PHP_FLOAT_MAX, PHP_FLOAT_MAX);

case 'non-zero-int':
return new UnionType([
IntegerRangeType::fromInterval(null, -1),
Expand Down Expand Up @@ -804,6 +823,16 @@ static function (string $variance): TemplateTypeVariance {

return IntegerRangeType::fromInterval($min, $max);
}
} elseif ($mainTypeName === 'float') {
if (count($genericTypes) === 2) { // float<min, max>, float<0, 1.5>
$min = $this->resolveFloatRangeBound($genericTypes[0], $typeNode->genericTypes[0], 'min', -INF);
$max = $this->resolveFloatRangeBound($genericTypes[1], $typeNode->genericTypes[1], 'max', INF);
if ($min === null || $max === null) {
return new ErrorType();
}

return FloatRangeType::fromInterval($min, $max);
}
} elseif ($mainTypeName === 'key-of') {
if (count($genericTypes) === 1) { // key-of<ValueType>
$type = new KeyOfType($genericTypes[0]);
Expand Down Expand Up @@ -1459,6 +1488,23 @@ private function resolveOffsetAccessNode(OffsetAccessTypeNode $typeNode, NameSco
return new OffsetAccessType($type, $offset);
}

private function resolveFloatRangeBound(Type $type, TypeNode $typeNode, string $keyword, float $keywordValue): ?float
{
if ($typeNode instanceof IdentifierTypeNode && $typeNode->name === $keyword) {
return $keywordValue;
}

if ($type instanceof ConstantIntegerType) {
return (float) $type->getValue();
}

if ($type instanceof ConstantFloatType && !is_nan($type->getValue())) {
return $type->getValue();
}

return null;
}

private function expandIntMaskToType(Type $type): ?Type
{
$ints = array_map(static fn (ConstantIntegerType $type) => $type->getValue(), TypeUtils::getConstantIntegers($type));
Expand Down
44 changes: 40 additions & 4 deletions src/Type/Constant/ConstantFloatType.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Turbo\ShadowedByTurboExtension;
use PHPStan\Type\BooleanType;
use PHPStan\Type\CompoundType;
use PHPStan\Type\ConstantScalarType;
use PHPStan\Type\FloatRangeType;
use PHPStan\Type\FloatType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\IsSuperTypeOfResult;
use PHPStan\Type\Traits\ConstantNumericComparisonTypeTrait;
use PHPStan\Type\Traits\ConstantScalarTypeTrait;
use PHPStan\Type\Type;
Expand All @@ -27,7 +31,6 @@ class ConstantFloatType extends FloatType implements ConstantScalarType
{

use ConstantScalarTypeTrait;
use ConstantScalarToBooleanTrait;
use ConstantNumericComparisonTypeTrait;

/** @api */
Expand All @@ -41,6 +44,27 @@ public function getValue(): float
return $this->value;
}

public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
{
if ($type instanceof self) {
return IsSuperTypeOfResult::createFromBoolean($this->equals($type));
}

if ($type instanceof FloatRangeType) {
return $type->isSuperTypeOf($this)->no() ? IsSuperTypeOfResult::createNo() : IsSuperTypeOfResult::createMaybe();
}

if ($type instanceof parent) {
return IsSuperTypeOfResult::createMaybe();
}

if ($type instanceof CompoundType) {
return $type->isSubTypeOf($this);
}

return IsSuperTypeOfResult::createNo();
}

public function equals(Type $type): bool
{
return $type instanceof self && ($this->value === $type->value || is_nan($this->value) && is_nan($type->value));
Expand Down Expand Up @@ -86,14 +110,26 @@ public function toString(): Type
return new ConstantStringType((string) $this->value);
}

public function toBoolean(): BooleanType
{
// (bool) $this->value without the warning PHP 8.5 raises for NAN; -0.0 === 0.0
return new ConstantBooleanType($this->value !== 0.0);
}

/** (int) $this->value without the warning PHP 8.5 raises for NAN and the infinities, which cast to 0. */
private function toIntegerValue(): int
{
return is_finite($this->value) ? (int) $this->value : 0;
}

public function toInteger(): Type
{
return new ConstantIntegerType((int) $this->value);
return new ConstantIntegerType($this->toIntegerValue());
}

public function toBitwiseNotType(): Type
{
return new ConstantIntegerType(~ (int) $this->value);
return new ConstantIntegerType(~ $this->toIntegerValue());
}

public function toAbsoluteNumber(): Type
Expand All @@ -103,7 +139,7 @@ public function toAbsoluteNumber(): Type

public function toArrayKey(): Type
{
return new ConstantIntegerType((int) $this->value);
return new ConstantIntegerType($this->toIntegerValue());
}

public function getFiniteTypes(): array
Expand Down
20 changes: 15 additions & 5 deletions src/Type/ExponentiateHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Type\Constant\ConstantFloatType;
use PHPStan\Type\Constant\ConstantIntegerType;
use function count;
use function is_float;
use function is_int;
use function is_numeric;
use function is_string;
Expand Down Expand Up @@ -114,15 +115,23 @@ private static function exponentiateConstantScalar(ConstantScalarType $base, Typ
return null;
}

$result = self::pow($base->getValue(), $exponent->getValue());
$baseValue = $base->getValue();
$result = self::pow($baseValue, $exponent->getValue());
if ($result === null) {
return new ErrorType();
}

if (is_int($result)) {
return new ConstantIntegerType($result);
$resultType = is_int($result) ? new ConstantIntegerType($result) : new ConstantFloatType($result);

// a zero float stands for both zeros (0.0 === -0.0), and a negative odd exponent tells them apart
if (is_float($baseValue) && $baseValue === 0.0) {
$otherZeroResult = self::pow(-$baseValue, $exponent->getValue());
if (is_float($otherZeroResult) && $otherZeroResult !== $result) {
return TypeCombinator::union($resultType, new ConstantFloatType($otherZeroResult));
}
}
return new ConstantFloatType($result);

return $resultType;
}

/**
Expand Down Expand Up @@ -258,7 +267,8 @@ private static function pow(mixed $base, mixed $exp): float|int|null
if (is_string($exp) && !is_numeric($exp)) {
return null;
}
return pow($base, $exp);
// PHP 8.4 deprecates 0 ** -1, which still gives INF
return @pow($base, $exp);
}

}
Loading
Loading