From 2feb9a7e40a69a15a4491299ced1a7736f7ca384 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 19 Dec 2022 21:54:26 +0100 Subject: [PATCH] Fix `pathinfo($s, PATHINFO_ALL)` return type --- ...infoFunctionDynamicReturnTypeExtension.php | 59 +++++++++++++++---- .../Analyser/NodeScopeResolverTest.php | 5 ++ tests/PHPStan/Analyser/data/pathinfo-php8.php | 9 +++ tests/PHPStan/Analyser/data/pathinfo.php | 15 +++++ 4 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/pathinfo-php8.php create mode 100644 tests/PHPStan/Analyser/data/pathinfo.php diff --git a/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php b/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php index 1376ed4ff8..b89b8a32be 100644 --- a/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php @@ -5,17 +5,25 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Reflection\ReflectionProvider; +use PHPStan\ShouldNotHappenException; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\StringType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use function count; +use function sprintf; class PathinfoFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { + public function __construct(private ReflectionProvider $reflectionProvider) + { + } + public function isFunctionSupported(FunctionReflection $functionReflection): bool { return $functionReflection->getName() === 'pathinfo'; @@ -25,24 +33,51 @@ public function getTypeFromFunctionCall( FunctionReflection $functionReflection, Node\Expr\FuncCall $functionCall, Scope $scope, - ): Type + ): ?Type { $argsCount = count($functionCall->getArgs()); if ($argsCount === 0) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } elseif ($argsCount === 1) { - $pathType = $scope->getType($functionCall->getArgs()[0]->value); + return null; + } + + $pathType = $scope->getType($functionCall->getArgs()[0]->value); + + $builder = ConstantArrayTypeBuilder::createEmpty(); + $builder->setOffsetValueType(new ConstantStringType('dirname'), new StringType(), !$pathType->isNonEmptyString()->yes()); + $builder->setOffsetValueType(new ConstantStringType('basename'), new StringType()); + $builder->setOffsetValueType(new ConstantStringType('extension'), new StringType(), true); + $builder->setOffsetValueType(new ConstantStringType('filename'), new StringType()); + $arrayType = $builder->getArray(); + + if ($argsCount === 1) { + return $arrayType; + } else { + $flagsType = $scope->getType($functionCall->getArgs()[1]->value); + if ($flagsType instanceof ConstantIntegerType) { + if ($flagsType->getValue() === $this->getConstant('PATHINFO_ALL')) { + return $arrayType; + } - $builder = ConstantArrayTypeBuilder::createEmpty(); - $builder->setOffsetValueType(new ConstantStringType('dirname'), new StringType(), !$pathType->isNonEmptyString()->yes()); - $builder->setOffsetValueType(new ConstantStringType('basename'), new StringType()); - $builder->setOffsetValueType(new ConstantStringType('extension'), new StringType(), true); - $builder->setOffsetValueType(new ConstantStringType('filename'), new StringType()); + return new StringType(); + } + } + + return TypeCombinator::union($arrayType, new StringType()); + } + + private function getConstant(string $constantName): ?int + { + if (!$this->reflectionProvider->hasConstant(new Node\Name($constantName), null)) { + return null; + } - return $builder->getArray(); + $constant = $this->reflectionProvider->getConstant(new Node\Name($constantName), null); + $valueType = $constant->getValueType(); + if (!$valueType instanceof ConstantIntegerType) { + throw new ShouldNotHappenException(sprintf('Constant %s does not have integer type.', $constantName)); } - return new StringType(); + return $valueType->getValue(); } } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 7be81df954..3ebcbcdee4 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1149,6 +1149,11 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8520.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/filter-var-dynamic-return-type-extension-regression.php'); + + if (PHP_VERSION_ID >= 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/pathinfo-php8.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/pathinfo.php'); } /** diff --git a/tests/PHPStan/Analyser/data/pathinfo-php8.php b/tests/PHPStan/Analyser/data/pathinfo-php8.php new file mode 100644 index 0000000000..632c2c2b96 --- /dev/null +++ b/tests/PHPStan/Analyser/data/pathinfo-php8.php @@ -0,0 +1,9 @@ +