Skip to content

Commit

Permalink
More precise IntegerRangeType::toString()
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm authored and ondrejmirtes committed Sep 26, 2024
1 parent f2aa8bf commit 7d49f52
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 13 deletions.
5 changes: 5 additions & 0 deletions src/Type/IntegerRangeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,11 @@ public function toAbsoluteNumber(): Type

public function toString(): Type
{
$finiteTypes = $this->getFiniteTypes();
if ($finiteTypes !== []) {
return TypeCombinator::union(...$finiteTypes)->toString();
}

$isZero = (new ConstantIntegerType(0))->isSuperTypeOf($this);
if ($isZero->no()) {
return new IntersectionType([
Expand Down
22 changes: 21 additions & 1 deletion src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,29 @@ public function getTypeFromFunctionCall(
$checkArgType = $scope->getType($args[$checkArg]->value);
if (
$matches['specifier'] === 's'
&& ($checkArgType->isConstantValue()->no() || $matches['width'] === '')
&& ($checkArgType->isString()->yes() || $checkArgType->isInteger()->yes())
) {
if ($checkArgType instanceof IntegerRangeType) {
$constArgTypes = $checkArgType->getFiniteTypes();
} else {
$constArgTypes = $checkArgType->getConstantScalarTypes();
}
if ($constArgTypes !== []) {
$result = [];
$printfArgs = array_fill(0, count($args) - 1, '');
foreach ($constArgTypes as $constArgType) {
$printfArgs[$checkArg - 1] = $constArgType->getValue();
try {
$result[] = new ConstantStringType(@sprintf($constantString->getValue(), ...$printfArgs));
} catch (Throwable) {
continue 2;
}
}
$singlePlaceholderEarlyReturn = TypeCombinator::union(...$result);

continue;
}

$singlePlaceholderEarlyReturn = $checkArgType->toString();
} elseif ($matches['specifier'] !== 's') {
$singlePlaceholderEarlyReturn = new IntersectionType([
Expand Down
35 changes: 25 additions & 10 deletions tests/PHPStan/Analyser/nsrt/bug-7387.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

class HelloWorld
{
public function inputTypes(int $i, float $f, string $s) {
/**
* @param int<-1, 5> $intRange
*/
public function inputTypes(int $i, float $f, string $s, int $intRange) {
// https://3v4l.org/iXaDX
assertType('numeric-string', sprintf('%.14F', $i));
assertType('numeric-string', sprintf('%.14F', $f));
Expand All @@ -19,6 +22,9 @@ public function inputTypes(int $i, float $f, string $s) {
assertType('numeric-string', sprintf('%14F', $i));
assertType('numeric-string', sprintf('%14F', $f));
assertType('numeric-string', sprintf('%14F', $s));

assertType("'-1'|'0'|'1'|'2'|'3'|'4'|'5'", sprintf('%s', $intRange));
assertType("' 0'|' 1'|' 2'|' 3'|' 4'|' 5'|'-1'", sprintf('%2s', $intRange));
}

public function specifiers(int $i) {
Expand Down Expand Up @@ -53,18 +59,27 @@ public function specifiers(int $i) {
*/
public function positionalArgs($mixed, int $i, float $f, string $s, int $posInt, int $negInt, int $nonZeroIntRange, int $intRange) {
// https://3v4l.org/vVL0c
assertType('numeric-string', sprintf('%2$14s', $mixed, $i));
assertType('non-falsy-string&numeric-string', sprintf('%2$14s', $mixed, $posInt));
assertType('non-falsy-string&numeric-string', sprintf('%2$14s', $mixed, $negInt));
assertType('numeric-string', sprintf('%2$14s', $mixed, $intRange));
assertType('non-falsy-string&numeric-string', sprintf('%2$14s', $mixed, $nonZeroIntRange));

assertType("non-falsy-string", sprintf('%2$14s', $mixed, 1));
assertType("non-falsy-string", sprintf('%2$14s', $mixed, '1'));
assertType("non-falsy-string", sprintf('%2$14s', $mixed, 'abc'));
assertType('numeric-string', sprintf('%2$6s', $mixed, $i));
assertType('non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $posInt));
assertType('non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $negInt));
assertType("' 1'|' 2'|' 3'|' 4'|' 5'", sprintf('%2$6s', $mixed, $nonZeroIntRange));

// https://3v4l.org/1ECIq
assertType('non-falsy-string', sprintf("%2$'#6s", $mixed, 1));
assertType('non-falsy-string', sprintf("%2$'#6s", $mixed, $i));
assertType('non-falsy-string', sprintf("%2$'#6s", $mixed, $posInt));
assertType('non-falsy-string', sprintf("%2$'#6s", $mixed, $negInt));
assertType("' 0'|' 1'|' 2'|' 3'|' 4'|' 5'|' -1'", sprintf('%2$6s', $mixed, $intRange));
assertType('non-falsy-string', sprintf("%2$'#6s", $mixed, $nonZeroIntRange));

assertType("' 1'", sprintf('%2$6s', $mixed, 1));
assertType("' 1'", sprintf('%2$6s', $mixed, '1'));
assertType("' abc'", sprintf('%2$6s', $mixed, 'abc'));
assertType("' 0'|' 1'|' 2'|' 3'|' 4'|' 5'|' -1'", sprintf('%2$6s', $mixed, $intRange));
assertType("'1'", sprintf('%2$s', $mixed, 1));
assertType("'1'", sprintf('%2$s', $mixed, '1'));
assertType("'abc'", sprintf('%2$s', $mixed, 'abc'));
assertType("'-1'|'0'|'1'|'2'|'3'|'4'|'5'", sprintf('%2$s', $mixed, $intRange));

assertType('numeric-string', sprintf('%2$.14F', $mixed, $i));
assertType('numeric-string', sprintf('%2$.14F', $mixed, $f));
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/filter-var.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public function scalars(bool $bool, float $float, int $int, string $string, int
assertType("'17.1'", filter_var(17.1));
assertType("'1.0E-50'", filter_var(1e-50));
assertType('numeric-string', filter_var($int));
assertType('numeric-string', filter_var($intRange));
assertType("'0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'", filter_var($intRange));
assertType("'17'", filter_var(17));
assertType('string', filter_var($string));
assertType('non-empty-string', filter_var($nonEmptyString));
Expand Down
22 changes: 22 additions & 0 deletions tests/PHPStan/Analyser/nsrt/range-to-string.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php declare(strict_types=1);

namespace IntRangeToString;

use function PHPStan\Testing\assertType;

class HelloWorld
{
/**
* @param int<5, 10> $i
* @param int<-10, 10> $ii
* @param int<0, 128> $maxlong
* @param int<0, 129> $toolong
*/
public function sayHello($i, $ii, $maxlong, $toolong): void
{
assertType("'10'|'5'|'6'|'7'|'8'|'9'", (string) $i);
assertType("'-1'|'-10'|'-2'|'-3'|'-4'|'-5'|'-6'|'-7'|'-8'|'-9'|'0'|'1'|'10'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'", (string) $ii);
assertType("'0'|'1'|'10'|'100'|'101'|'102'|'103'|'104'|'105'|'106'|'107'|'108'|'109'|'11'|'110'|'111'|'112'|'113'|'114'|'115'|'116'|'117'|'118'|'119'|'12'|'120'|'121'|'122'|'123'|'124'|'125'|'126'|'127'|'128'|'13'|'14'|'15'|'16'|'17'|'18'|'19'|'2'|'20'|'21'|'22'|'23'|'24'|'25'|'26'|'27'|'28'|'29'|'3'|'30'|'31'|'32'|'33'|'34'|'35'|'36'|'37'|'38'|'39'|'4'|'40'|'41'|'42'|'43'|'44'|'45'|'46'|'47'|'48'|'49'|'5'|'50'|'51'|'52'|'53'|'54'|'55'|'56'|'57'|'58'|'59'|'6'|'60'|'61'|'62'|'63'|'64'|'65'|'66'|'67'|'68'|'69'|'7'|'70'|'71'|'72'|'73'|'74'|'75'|'76'|'77'|'78'|'79'|'8'|'80'|'81'|'82'|'83'|'84'|'85'|'86'|'87'|'88'|'89'|'9'|'90'|'91'|'92'|'93'|'94'|'95'|'96'|'97'|'98'|'99'", (string) $maxlong);
assertType("numeric-string", (string) $toolong);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function doBaz(): void
}
}

assertType('array{a?: bool, b?: numeric-string, c?: int<-1, 1>, d?: int<0, 1>}', $breakdowns);
assertType("array{a?: bool, b?: '0'|'1', c?: int<-1, 1>, d?: int<0, 1>}", $breakdowns);
}

}

0 comments on commit 7d49f52

Please sign in to comment.