diff --git a/src/Type/Php/FilterFunctionReturnTypeHelper.php b/src/Type/Php/FilterFunctionReturnTypeHelper.php index 2a9f6bc977..de748f5368 100644 --- a/src/Type/Php/FilterFunctionReturnTypeHelper.php +++ b/src/Type/Php/FilterFunctionReturnTypeHelper.php @@ -3,6 +3,7 @@ namespace PHPStan\Type\Php; use PhpParser\Node; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; @@ -19,6 +20,7 @@ use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; +use PHPStan\Type\NeverType; use PHPStan\Type\NullType; use PHPStan\Type\StringType; use PHPStan\Type\Type; @@ -46,7 +48,9 @@ final class FilterFunctionReturnTypeHelper /** @var array>|null */ private ?array $filterTypeOptions = null; - public function __construct(private ReflectionProvider $reflectionProvider) + private ?Type $supportedFilterInputTypes = null; + + public function __construct(private ReflectionProvider $reflectionProvider, private PhpVersion $phpVersion) { $this->flagsString = new ConstantStringType('flags'); } @@ -69,6 +73,33 @@ public function getOffsetValueType(Type $inputType, Type $offsetType, ?Type $fil : $filteredType; } + public function getInputType(Type $typeType, Type $varNameType, ?Type $filterType, ?Type $flagsType): ?Type + { + $this->supportedFilterInputTypes ??= TypeCombinator::union( + $this->reflectionProvider->getConstant(new Node\Name('INPUT_GET'), null)->getValueType(), + $this->reflectionProvider->getConstant(new Node\Name('INPUT_POST'), null)->getValueType(), + $this->reflectionProvider->getConstant(new Node\Name('INPUT_COOKIE'), null)->getValueType(), + $this->reflectionProvider->getConstant(new Node\Name('INPUT_SERVER'), null)->getValueType(), + $this->reflectionProvider->getConstant(new Node\Name('INPUT_ENV'), null)->getValueType(), + ); + + if (!$typeType->isInteger()->yes() || $this->supportedFilterInputTypes->isSuperTypeOf($typeType)->no()) { + if ($this->phpVersion->throwsTypeErrorForInternalFunctions()) { + return new NeverType(); + } + + // Using a null as input mimics pre PHP 8 behaviour where filter_input + // would return the same as if the offset does not exist + $inputType = new NullType(); + } else { + // Pragmatical solution since global expressions are not passed through the scope for performance reasons + // See https://github.com/phpstan/phpstan-src/pull/2012 for details + $inputType = new ArrayType(new StringType(), new MixedType()); + } + + return $this->getOffsetValueType($inputType, $varNameType, $filterType, $flagsType); + } + public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): Type { $mixedType = new MixedType(); diff --git a/src/Type/Php/FilterInputDynamicReturnTypeExtension.php b/src/Type/Php/FilterInputDynamicReturnTypeExtension.php index cd16600475..0dd934cd32 100644 --- a/src/Type/Php/FilterInputDynamicReturnTypeExtension.php +++ b/src/Type/Php/FilterInputDynamicReturnTypeExtension.php @@ -2,23 +2,17 @@ namespace PHPStan\Type\Php; -use PhpParser\Node; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ReflectionProvider; -use PHPStan\Type\ArrayType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; -use PHPStan\Type\MixedType; -use PHPStan\Type\StringType; use PHPStan\Type\Type; -use PHPStan\Type\TypeCombinator; use function count; class FilterInputDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { - public function __construct(private FilterFunctionReturnTypeHelper $filterFunctionReturnTypeHelper, private ReflectionProvider $reflectionProvider) + public function __construct(private FilterFunctionReturnTypeHelper $filterFunctionReturnTypeHelper) { } @@ -33,24 +27,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return null; } - $supportedTypes = TypeCombinator::union( - $this->reflectionProvider->getConstant(new Node\Name('INPUT_GET'), null)->getValueType(), - $this->reflectionProvider->getConstant(new Node\Name('INPUT_POST'), null)->getValueType(), - $this->reflectionProvider->getConstant(new Node\Name('INPUT_COOKIE'), null)->getValueType(), - $this->reflectionProvider->getConstant(new Node\Name('INPUT_SERVER'), null)->getValueType(), - $this->reflectionProvider->getConstant(new Node\Name('INPUT_ENV'), null)->getValueType(), - ); - $typeType = $scope->getType($functionCall->getArgs()[0]->value); - if (!$typeType->isInteger()->yes() || $supportedTypes->isSuperTypeOf($typeType)->no()) { - return null; - } - - // Pragmatical solution since global expressions are not passed through the scope for performance reasons - // See https://github.com/phpstan/phpstan-src/pull/2012 for details - $inputType = new ArrayType(new StringType(), new MixedType()); - - return $this->filterFunctionReturnTypeHelper->getOffsetValueType( - $inputType, + return $this->filterFunctionReturnTypeHelper->getInputType( + $scope->getType($functionCall->getArgs()[0]->value), $scope->getType($functionCall->getArgs()[1]->value), isset($functionCall->getArgs()[2]) ? $scope->getType($functionCall->getArgs()[2]->value) : null, isset($functionCall->getArgs()[3]) ? $scope->getType($functionCall->getArgs()[3]->value) : null, diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index bfdf68b759..4b78ffd3e1 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -623,7 +623,13 @@ public function dataFileAsserts(): iterable } yield from $this->gatherAssertTypes(__DIR__ . '/data/filesystem-functions.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/filter-input.php'); + if (PHP_VERSION_ID >= 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/filter-input-php8.php'); + } else { + yield from $this->gatherAssertTypes(__DIR__ . '/data/filter-input-php7.php'); + } yield from $this->gatherAssertTypes(__DIR__ . '/data/filter-var.php'); if (PHP_VERSION_ID >= 80100) { diff --git a/tests/PHPStan/Analyser/data/filter-input-php7.php b/tests/PHPStan/Analyser/data/filter-input-php7.php new file mode 100644 index 0000000000..b5ee1d393d --- /dev/null +++ b/tests/PHPStan/Analyser/data/filter-input-php7.php @@ -0,0 +1,16 @@ +