diff --git a/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php b/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php index ac57eb12c0..c6a4c34523 100644 --- a/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php +++ b/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php @@ -46,7 +46,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $functionName = strtolower($functionReflection->getName()); $inputArgType = $scope->getType($functionCall->getArgs()[0]->value); - $inputArrayType = $inputArgType; + $inputArrayType = $inputArgType->getArrays()[0] ?? null; $inputConstantArrayType = null; if ($functionName === 'filter_var_array') { if ($inputArgType->isArray()->no()) { @@ -72,28 +72,29 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $inputArrayType = new ArrayType(new StringType(), new MixedType()); } + if ($inputArrayType === null) { + $inputArrayType = new ArrayType(new MixedType(), new MixedType()); + if ($inputArgType->isSuperTypeOf($inputArrayType)->no()) { + return null; + } + } + $filterArgType = $scope->getType($functionCall->getArgs()[1]->value); $filterConstantArrayType = $filterArgType->getConstantArrays()[0] ?? null; $addEmptyType = isset($functionCall->getArgs()[2]) ? $scope->getType($functionCall->getArgs()[2]->value) : null; $addEmpty = $addEmptyType === null || $addEmptyType->isTrue()->yes(); $valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty(); - $inputTypesMap = []; - $optionalKeys = []; if ($filterArgType instanceof ConstantIntegerType) { if ($inputConstantArrayType === null) { - $isList = $inputArrayType->isList()->yes(); - $inputArrayType = $inputArrayType->getArrays()[0] ?? null; + $isList = $inputArgType->isList()->yes(); $valueType = $this->filterFunctionReturnTypeHelper->getType( - $inputArrayType === null ? new MixedType() : $inputArrayType->getItemType(), + $inputArrayType->getItemType(), $filterArgType, null, ); - $arrayType = new ArrayType( - $inputArrayType !== null ? $inputArrayType->getKeyType() : new MixedType(), - $valueType, - ); + $arrayType = new ArrayType($inputArrayType->getKeyType(), $valueType); return $isList ? AccessoryArrayListType::intersectWith($arrayType) : $arrayType; } @@ -116,11 +117,10 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } elseif ($filterConstantArrayType === null) { if ($inputConstantArrayType === null) { $isList = $inputArrayType->isList()->yes(); - $inputArrayType = $inputArrayType->getArrays()[0] ?? null; - $valueType = $this->filterFunctionReturnTypeHelper->getType($inputArrayType ?? new MixedType(), $filterArgType, null); + $valueType = $this->filterFunctionReturnTypeHelper->getType($inputArrayType, $filterArgType, null); $arrayType = new ArrayType( - $inputArrayType !== null ? $inputArrayType->getKeyType() : new MixedType(), + $inputArrayType->getKeyType(), $addEmpty ? TypeCombinator::addNull($valueType) : $valueType, ); @@ -148,7 +148,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } } else { $optionalKeys = $filterKeysList; - $inputTypesMap = array_fill_keys($optionalKeys, $inputArrayType->getArrays()[0]->getItemType()); + $inputTypesMap = array_fill_keys($optionalKeys, $inputArrayType->getItemType()); } } diff --git a/tests/PHPStan/Analyser/data/filter-var-array.php b/tests/PHPStan/Analyser/data/filter-var-array.php index fc959f4d06..746ad08c96 100644 --- a/tests/PHPStan/Analyser/data/filter-var-array.php +++ b/tests/PHPStan/Analyser/data/filter-var-array.php @@ -67,6 +67,52 @@ function constantValues(): void ], false)); } +function mixedInput(mixed $input): void +{ + // filter array with add_empty=default + assertType('array{id: int|false|null}', filter_var_array($input, [ + 'id' => FILTER_VALIDATE_INT, + ])); + + // filter array with add_empty=true + assertType('array{id: int|false|null}', filter_var_array($input, [ + 'id' => FILTER_VALIDATE_INT, + ], true)); + + // filter array with add_empty=false + assertType('array{id?: int|false}', filter_var_array($input, [ + 'id' => FILTER_VALIDATE_INT, + ], false)); + + // filter flag with add_empty=default + assertType('array', filter_var_array($input, FILTER_VALIDATE_INT)); + // filter flag with add_empty=true + assertType('array', filter_var_array($input, FILTER_VALIDATE_INT, true)); + // filter flag with add_empty=false + assertType('array', filter_var_array($input, FILTER_VALIDATE_INT, false)); + + $filter = [ + 'filter' => FILTER_VALIDATE_INT, + 'flag' => FILTER_REQUIRE_SCALAR, + 'options' => ['min_range' => 1, 'max_range' => 10], + ]; + + // filter array with add_empty=default + assertType('array{id: int<1, 10>|false|null}', filter_var_array($input, [ + 'id' => $filter, + ])); + + // filter array with add_empty=default + assertType('array{id: int<1, 10>|false|null}', filter_var_array($input, [ + 'id' => $filter, + ], true)); + + // filter array with add_empty=default + assertType('array{id?: int<1, 10>|false}', filter_var_array($input, [ + 'id' => $filter, + ], false)); +} + function emptyArrayInput(): void { // filter array with add_empty=default