Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
simplify
  • Loading branch information
staabm committed Jul 19, 2024
commit 9b0bf7faed7452c187553a7e3a544cbe1a83f798
55 changes: 39 additions & 16 deletions src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
use PHPStan\Type\Accessory\AccessoryNumericStringType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\IntegerRangeType;
Expand All @@ -21,6 +22,7 @@
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use Throwable;
use function array_fill;
use function array_key_exists;
use function array_shift;
use function count;
Expand Down Expand Up @@ -77,7 +79,12 @@ public function getTypeFromFunctionCall(
$allPatternsNonEmpty = count($formatStrings) !== 0;
$allPatternsNonFalsy = count($formatStrings) !== 0;
foreach ($formatStrings as $constantString) {
$constantParts = $this->getFormatConstantParts($constantString->getValue());
$constantParts = $this->getFormatConstantParts(
$constantString->getValue(),
$functionReflection,
$functionCall,
$scope,
);
if ($constantParts !== null) {
if ($constantParts->isNonFalsyString()->yes()) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
// keep all bool flags as is
Expand Down Expand Up @@ -158,26 +165,42 @@ public function getTypeFromFunctionCall(
return $returnType;
}

private function getFormatConstantParts(string $format): ?ConstantStringType
private function getFormatConstantParts(
string $format,
FunctionReflection $functionReflection,
FuncCall $functionCall,
Scope $scope,
): ?ConstantStringType
{
$dummyValues = [];
for ($i = 0; $i < self::MAX_INTERPOLATION_RETRIES; $i++) {
$dummyValues[] = '';

try {
$formatted = @sprintf($format, ...$dummyValues);
if ($formatted === false) { // @phpstan-ignore identical.alwaysFalse
continue;
}
return new ConstantStringType($formatted);
} catch (ArgumentCountError) {
continue;
} catch (Throwable) {
$args = $functionCall->getArgs();
if ($functionReflection->getName() === 'sprintf') {
$valuesCount = count($args) - 1;
} elseif (
$functionReflection->getName() === 'vsprintf'
&& count($args) >= 2
) {
$arraySize = $scope->getType($args[1]->value)->getArraySize();
if (!($arraySize instanceof ConstantIntegerType)) {
return null;
}

$valuesCount = $arraySize->getValue();
} else {
return null;
}

return null;
try {
$dummyValues = array_fill(0, $valuesCount, '');
$formatted = @sprintf($format, ...$dummyValues);
if ($formatted === false) { // @phpstan-ignore identical.alwaysFalse
return null;
}
return new ConstantStringType($formatted);
} catch (ArgumentCountError) {
return null;
} catch (Throwable) {
return null;
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/non-empty-string.php
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ public function doFoo(string $s, string $nonEmpty, string $nonFalsy, int $i, boo

assertType('non-empty-string', sprintf("%s0%s", $s, $s));
assertType('non-empty-string', sprintf("%s0%s%s%s%s", $s, $s, $s, $s, $s));
assertType('string', sprintf("%s0%s%s%s%s%s", $s, $s, $s, $s, $s, $s)); // max interpolation limit reached
assertType('non-empty-string', sprintf("%s0%s%s%s%s%s", $s, $s, $s, $s, $s, $s));

assertType('0', strlen(''));
assertType('5', strlen('hallo'));
Expand Down
5 changes: 4 additions & 1 deletion tests/PHPStan/Analyser/nsrt/non-falsy-string.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,13 @@ function stringFunctions(string $s, $nonFalsey, $arrayOfNonFalsey, $nonEmptyArra
assertType("non-falsy-string", vsprintf('foo', [])); // should be 'foo'
assertType("string", vsprintf('%s', ...$arr));
assertType("string", vsprintf(...$arr));
assertType('non-falsy-string', vsprintf('%sAA%s', [$s, $s]));
assertType('non-falsy-string', vsprintf('%d%d', [$s, $s])); // could be non-falsy-string&numeric-string

assertType('non-falsy-string', sprintf("%sAA%s", $s, $s));
assertType('non-falsy-string', sprintf("%d%d", $s, $s)); // could be non-falsy-string&numeric-string
assertType('non-falsy-string', sprintf("%sAA%s%s%s%s", $s, $s, $s, $s, $s));
assertType('string', sprintf("%sAA%s%s%s%s%s", $s, $s, $s, $s, $s, $s)); // max interpolation limit reached
assertType('non-falsy-string', sprintf("%sAA%s%s%s%s%s", $s, $s, $s, $s, $s, $s));

assertType('int<1, max>', strlen($nonFalsey));

Expand Down