Skip to content

Commit

Permalink
support integer-ranges in pow()
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm committed Oct 27, 2022
1 parent aaf4b7d commit e45cdcc
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 0 deletions.
60 changes: 60 additions & 0 deletions src/Reflection/InitializerExprTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,66 @@ public function getPowType(Expr $left, Expr $right, callable $getTypeCallback):
return TypeCombinator::union(...$resultTypes);
}

if ($leftType instanceof IntegerRangeType && $rightType instanceof IntegerRangeType) {
$min = null;
$max = null;
if ($leftType->getMin() !== null && $rightType->getMin() !== null) {
$min = $leftType->getMin() ** $rightType->getMin();
}
if ($leftType->getMax() !== null && $rightType->getMax() !== null) {
$max = $leftType->getMax() ** $rightType->getMax();
}

if (!is_float($min) && !is_float($max)) {
return IntegerRangeType::fromInterval($min, $max);
}
}

if (
($leftType instanceof IntegerRangeType && $rightTypesCount > 0)
|| ($rightType instanceof IntegerRangeType && $leftTypesCount > 0)
) {
if ($leftType instanceof IntegerRangeType) {
$min = $leftType->getMin();
$max = $leftType->getMax();

$minType = $min === null ? new NullType() : $this->getPowType(new LNumber($min), $right, $getTypeCallback);
$maxType = $max === null ? new NullType() : $this->getPowType(new LNumber($max), $right, $getTypeCallback);
} elseif ($rightType instanceof IntegerRangeType) {
$min = $rightType->getMin();
$max = $rightType->getMax();

$minType = $min === null ? new NullType() : $this->getPowType($left, new LNumber($min), $getTypeCallback);
$maxType = $max === null ? new NullType() : $this->getPowType($left, new LNumber($max), $getTypeCallback);
} else {
throw new ShouldNotHappenException();
}

$minTypes = TypeUtils::getConstantIntegers($minType);
$maxTypes = TypeUtils::getConstantIntegers($maxType);

$resultTypes = [];
if (count($minTypes) === count($maxTypes)) {
foreach (array_keys($minTypes) as $i) {
$resultTypes[] = IntegerRangeType::fromInterval($minTypes[$i]->getValue(), $maxTypes[$i]->getValue());
}
} elseif (count($minTypes) === 1) {
$minType = $minTypes[0];
foreach ($maxTypes as $constantMaxType) {
$resultTypes[] = IntegerRangeType::fromInterval($minType->getValue(), $constantMaxType->getValue());
}
} elseif (count($maxTypes) === 1) {
$maxType = $maxTypes[0];
foreach ($minTypes as $constantMinType) {
$resultTypes[] = IntegerRangeType::fromInterval($constantMinType->getValue(), $maxType->getValue());
}
}

if (count($resultTypes) > 0) {
return TypeCombinator::union(...$resultTypes);
}
}

return $this->resolveCommonMath(new BinaryOp\Pow($left, $right), $leftType, $rightType);
}

Expand Down
47 changes: 47 additions & 0 deletions tests/PHPStan/Analyser/data/pow.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,50 @@ function (\stdClass $a, \GMP $b): void {
assertType('GMP|stdClass', pow($a, $b));
assertType('GMP|stdClass', $a ** $b);
};

function (): void {
$range = rand(1, 3);
assertType('int<1, 3>', $range);

assertType('int<1, 9>', pow($range, 2));
assertType('int<1, 9>', $range ** 2);

assertType('int<2, 8>', pow(2, $range));
assertType('int<2, 8>', 2 ** $range);
};

function (): void {
$range = rand(2, 3);
$x = 2;
if (rand(0, 1)) {
$x = 3;
} else if (rand(0, 10)) {
$x = 4;
}

assertType('int<4, 27>|int<16, 81>', pow($range, $x));
assertType('int<4, 27>|int<16, 81>', $range ** $x);

assertType('int<4, 27>|int<16, 64>', pow($x, $range));
assertType('int<4, 27>|int<16, 64>', $x ** $range);

assertType('int<4, 27>', pow($range, $range));
assertType('int<4, 27>', $range ** $range);
};

/**
* @param positive-int $positiveInt
* @param int<min, 3> $range2
*/
function foo($positiveInt, $range2): void {
$range = rand(2, 3);

assertType('int<2, max>', pow($range, $positiveInt));
assertType('int<2, max>', $range ** $positiveInt);

assertType('int<min, 27>', pow($range, $range2));
assertType('int<min, 27>', $range ** $range2);

assertType('(float|int)', pow($range, PHP_INT_MAX));
assertType('(float|int)', $range ** PHP_INT_MAX);
}

0 comments on commit e45cdcc

Please sign in to comment.