diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 6698a8a800..bd08d4f364 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -2346,6 +2346,21 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty } } + // array_key_first($a) !== null + // array_key_last($a) !== null + if ( + $unwrappedLeftExpr instanceof FuncCall + && $unwrappedLeftExpr->name instanceof Name + && in_array($unwrappedLeftExpr->name->toLowerString(), ['array_key_first', 'array_key_last'], true) + && isset($unwrappedLeftExpr->getArgs()[0]) + && $rightType->isNull()->yes() + ) { + $argType = $scope->getType($unwrappedLeftExpr->getArgs()[0]->value); + if ($argType->isArray()->yes()) { + return $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NonEmptyArrayType(), $context->negate(), $scope)->setRootExpr($expr); + } + } + // preg_match($a) === $b if ( $context->true() diff --git a/tests/PHPStan/Analyser/nsrt/bug-13546.php b/tests/PHPStan/Analyser/nsrt/bug-13546.php new file mode 100644 index 0000000000..63eb5ee889 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13546.php @@ -0,0 +1,86 @@ + $array */ +function first(array $array): void +{ + if (array_key_first($array) !== null) { + assertType('non-empty-array', $array); + } else { + assertType('array{}', $array); + } + assertType('array', $array); +} + +/** @param array $array */ +function firstReversed(array $array): void +{ + if (null !== array_key_first($array)) { + assertType('non-empty-array', $array); + } else { + assertType('array{}', $array); + } + assertType('array', $array); +} + +/** @param array $array */ +function last(array $array): void +{ + if (array_key_last($array) !== null) { + assertType('non-empty-array', $array); + } else { + assertType('array{}', $array); + } + assertType('array', $array); +} + +function maybeArray(array $array, $mixed): void +{ + $arrayOrMixed = rand(0, 1) ? $array : $mixed; + + if (array_key_last($arrayOrMixed) !== null) { + assertType('mixed', $arrayOrMixed); + } else { + assertType('mixed', $arrayOrMixed); + } + assertType('mixed', $arrayOrMixed); +} + +function mixedLast($mixed): void +{ + if (is_array($mixed)) { + return; + } + + if (array_key_last($mixed) !== null) { + assertType('mixed~array', $mixed); + } else { + assertType('mixed~array', $mixed); + } + assertType('mixed~array', $mixed); +} + +/** @param list $array */ +function firstInCondition(array $array) +{ + if (($key = array_key_first($array)) !== null) { + assertType('list', $array); // could be 'non-empty-list' + return $array[$key]; + } + assertType('list', $array); + return null; +} + +/** @param list $array */ +function maybeNull(array $array, ?int $nullOrInt, ?string $nullOrString): void +{ + if (array_key_first($array) !== $nullOrInt) { + assertType('list', $array); + } + if (array_key_first($array) !== $nullOrString) { + assertType('list', $array); + } + assertType('list', $array); +}