diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 6698a8a800..22934f082d 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -273,7 +273,8 @@ public function specifyTypesInCondition( && in_array(strtolower((string) $expr->right->name), ['count', 'sizeof'], true) && $leftType->isInteger()->yes() ) { - $argType = $scope->getType($expr->right->getArgs()[0]->value); + $argExpr = $expr->right->getArgs()[0]->value; + $argType = $scope->getType($argExpr); if ($leftType instanceof ConstantIntegerType) { if ($orEqual) { @@ -291,6 +292,26 @@ public function specifyTypesInCondition( if ($specifiedTypes !== null) { $result = $result->unionWith($specifiedTypes); } + if ( + $context->true() + && $expr instanceof Node\Expr\BinaryOp\Smaller + && $argType->isList()->yes() + && $argExpr instanceof Expr\Variable + && IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($leftType)->yes() + ) { + $dimFetch = new ArrayDimFetch( + $argExpr, + $expr->left, + ); + $result = $result->unionWith( + $this->create( + $dimFetch, + $argType->getIterableValueType(), + TypeSpecifierContext::createTrue(), + $scope, + ) + ); + } if ( $context->true() && (IntegerRangeType::createAllGreaterThanOrEqualTo(1 - $offset)->isSuperTypeOf($leftType)->yes()) diff --git a/tests/PHPStan/Analyser/nsrt/bug-10264.php b/tests/PHPStan/Analyser/nsrt/bug-10264.php index 20b1361a25..52e44ccb88 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10264.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10264.php @@ -33,7 +33,7 @@ public function sayHello(array $c): void assertType('list', $c); if (count($c) > 0) { $c = array_map(fn() => new stdClass(), $c); - assertType('non-empty-list', $c); + assertType('non-empty-list&hasOffsetValue(0, stdClass)', $c); } else { assertType('array{}', $c); } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 879c86f791..e14e6e4f37 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -700,6 +700,22 @@ public static function dataReportPossiblyNonexistentArrayOffset(): iterable "Offset 'foo' might not exist on array.", 9, ], + [ + 'Offset int might not exist on list.', + 77 + ], + [ + 'Offset int<0, max> might not exist on list.', + 88 + ], + [ + 'Offset int<0, max> might not exist on array.', + 100 + ], + [ + 'Offset int<0, max> might not exist on array|int<6, max>, int>.', + 112 + ], ]]; yield [true, true, [ [ @@ -710,6 +726,22 @@ public static function dataReportPossiblyNonexistentArrayOffset(): iterable 'Offset string might not exist on array{foo: 1}.', 20, ], + [ + 'Offset int might not exist on list.', + 77 + ], + [ + 'Offset int<0, max> might not exist on list.', + 88 + ], + [ + 'Offset int<0, max> might not exist on array.', + 100 + ], + [ + 'Offset int<0, max> might not exist on array|int<6, max>, int>.', + 112 + ], ]]; } diff --git a/tests/PHPStan/Rules/Arrays/data/report-possibly-nonexistent-array-offset.php b/tests/PHPStan/Rules/Arrays/data/report-possibly-nonexistent-array-offset.php index fc54d96f00..6ccc313ced 100644 --- a/tests/PHPStan/Rules/Arrays/data/report-possibly-nonexistent-array-offset.php +++ b/tests/PHPStan/Rules/Arrays/data/report-possibly-nonexistent-array-offset.php @@ -57,4 +57,60 @@ public function nonEmpty(array $a): void echo $a[0]; } + /** + * @param list $array + * @param non-negative-int $index + */ + public function guard(array $array, int $index) { + if ($index < count($array)) { + return $array[$index]; + } + return null; + } + + + /** + * @param list $array + */ + public function guardNotSafeLowerBound(array $array, int $index) { + if ($index < count($array)) { + return $array[$index]; + } + return null; + } + + /** + * @param list $array + * @param non-negative-int $index + */ + public function guardNotSafeUpperBound(array $array, int $index) { + if ($index <= count($array)) { + return $array[$index]; + } + return null; + } + + + /** + * @param array $array + * @param non-negative-int $index + */ + public function guardNotSafeArray(array $array, int $index) { + if ($index <= count($array)) { + return $array[$index]; + } + return null; + } + + /** + * @param array $array + * @param non-negative-int $index + */ + public function guardNotSafeBecauseRewrite(array $array, int $index) { + if ($index <= count($array)) { + unset($array[5]); + return $array[$index]; + } + return null; + } }