diff --git a/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php b/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php index 1376ed4ff8e..b89b8a32beb 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 ac8b6a84bc2..8d3960eaeaf 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1132,6 +1132,11 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8361.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8373.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-8389.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 00000000000..632c2c2b96d --- /dev/null +++ b/tests/PHPStan/Analyser/data/pathinfo-php8.php @@ -0,0 +1,9 @@ +