diff --git a/conf/config.neon b/conf/config.neon index 4859022f35..a2a376be29 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1667,6 +1667,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\SscanfFunctionDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\StrvalFamilyFunctionReturnTypeExtension tags: diff --git a/src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..085b74bbee --- /dev/null +++ b/src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php @@ -0,0 +1,69 @@ +getName(), ['sscanf', 'fscanf'], true); + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope, + ): ?Type + { + $args = $functionCall->getArgs(); + if (count($args) !== 2) { + return null; + } + + $formatType = $scope->getType($args[1]->value); + + if (!$formatType instanceof ConstantStringType) { + return null; + } + + if (preg_match_all('/%[cdeEfosux]{1}/', $formatType->getValue(), $matches) > 0) { + $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + + for ($i = 0; $i < count($matches[0]); $i++) { + $type = new StringType(); + + if (in_array($matches[0][$i], ['%d', '%o', '%u', '%x'], true)) { + $type = new IntegerType(); + } + + if (in_array($matches[0][$i], ['%e', '%E', '%f'], true)) { + $type = new FloatType(); + } + + $arrayBuilder->setOffsetValueType(new ConstantIntegerType($i), $type); + } + + return TypeCombinator::addNull($arrayBuilder->getArray()); + } + + return null; + } + +} diff --git a/tests/PHPStan/Analyser/data/sscanf.php b/tests/PHPStan/Analyser/data/sscanf.php index 2b7105f939..449249261a 100644 --- a/tests/PHPStan/Analyser/data/sscanf.php +++ b/tests/PHPStan/Analyser/data/sscanf.php @@ -1,6 +1,38 @@