From fc3129207726ee65cb53338dccb9790c7807bd12 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 18 Sep 2025 11:59:36 +0200 Subject: [PATCH 1/3] Narrow offset after isset() on list --- src/Rules/Arrays/AllowedArrayKeysTypes.php | 6 ++++ tests/PHPStan/Analyser/nsrt/bug-12274.php | 8 ++--- tests/PHPStan/Analyser/nsrt/bug-12933.php | 39 ++++++++++++++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12933.php diff --git a/src/Rules/Arrays/AllowedArrayKeysTypes.php b/src/Rules/Arrays/AllowedArrayKeysTypes.php index 2178b579fb..c9dc27a9ec 100644 --- a/src/Rules/Arrays/AllowedArrayKeysTypes.php +++ b/src/Rules/Arrays/AllowedArrayKeysTypes.php @@ -8,6 +8,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FloatType; +use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; @@ -71,6 +72,11 @@ public static function narrowOffsetKeyType(Type $varType, Type $keyType): ?Type return $narrowedKey; } elseif ($varIterableKeyType->isInteger()->yes() && $keyType->isString()->yes()) { return TypeCombinator::intersect($varIterableKeyType->toString(), $keyType); + } elseif ($varType->isList()->yes()) { + return TypeCombinator::intersect( + IntegerRangeType::fromInterval(0, null), + $varIterableKeyType, + ); } return new MixedType( diff --git a/tests/PHPStan/Analyser/nsrt/bug-12274.php b/tests/PHPStan/Analyser/nsrt/bug-12274.php index f0536a0c15..b17b11aed8 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12274.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12274.php @@ -92,9 +92,9 @@ function testShouldLooseListbyAst(array $list, int $i): void assertType('list', $list); $list[1+$i] = 21; - assertType('non-empty-array', $list); + assertType('non-empty-array, int>', $list); } - assertType('array', $list); + assertType('array, int>', $list); } /** @param list $list */ @@ -103,7 +103,7 @@ function testShouldLooseListbyAst2(array $list, int $i): void if (isset($list[$i])) { assertType('list', $list); $list[2+$i] = 21; - assertType('non-empty-array', $list); + assertType('non-empty-array, int>', $list); } - assertType('array', $list); + assertType('array, int>', $list); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12933.php b/tests/PHPStan/Analyser/nsrt/bug-12933.php new file mode 100644 index 0000000000..45123349ea --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12933.php @@ -0,0 +1,39 @@ += 8.0 + +namespace Bug12933; + +use function PHPStan\Testing\assertType; + +/** + * @phpstan-type record array{id: positive-int, name: string} + */ +class Collection +{ + /** @param list $list */ + public function __construct( + public array $list + ) + { + } + + public function updateNameIsset(int $index, string $name): void + { + assert(isset($this->list[$index])); + assertType('int<0, max>', $index); + } + + public function updateNameArrayKeyExists(int $index, string $name): void + { + assert(array_key_exists($index, $this->list)); + assertType('int<0, max>', $index); + } + + /** + * @param int<-5, 5> $index + */ + public function issetNarrowsIntRange(int $index, string $name): void + { + assert(isset($this->list[$index])); + assertType('int<0, 5>', $index); + } +} From 2faab413de81520d4aed9448b71b116904ffb1b4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 18 Sep 2025 12:10:41 +0200 Subject: [PATCH 2/3] Update AllowedArrayKeysTypes.php --- src/Rules/Arrays/AllowedArrayKeysTypes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rules/Arrays/AllowedArrayKeysTypes.php b/src/Rules/Arrays/AllowedArrayKeysTypes.php index c9dc27a9ec..6fc74fd95c 100644 --- a/src/Rules/Arrays/AllowedArrayKeysTypes.php +++ b/src/Rules/Arrays/AllowedArrayKeysTypes.php @@ -74,8 +74,8 @@ public static function narrowOffsetKeyType(Type $varType, Type $keyType): ?Type return TypeCombinator::intersect($varIterableKeyType->toString(), $keyType); } elseif ($varType->isList()->yes()) { return TypeCombinator::intersect( - IntegerRangeType::fromInterval(0, null), $varIterableKeyType, + $keyType, ); } From 991bffd981ac4014c83a6b1190cfdb9492538eb3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 18 Sep 2025 12:12:32 +0200 Subject: [PATCH 3/3] Update bug-12933.php --- src/Rules/Arrays/AllowedArrayKeysTypes.php | 1 - tests/PHPStan/Analyser/nsrt/bug-12933.php | 9 +++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Rules/Arrays/AllowedArrayKeysTypes.php b/src/Rules/Arrays/AllowedArrayKeysTypes.php index 6fc74fd95c..7d920e167c 100644 --- a/src/Rules/Arrays/AllowedArrayKeysTypes.php +++ b/src/Rules/Arrays/AllowedArrayKeysTypes.php @@ -8,7 +8,6 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FloatType; -use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; diff --git a/tests/PHPStan/Analyser/nsrt/bug-12933.php b/tests/PHPStan/Analyser/nsrt/bug-12933.php index 45123349ea..bc57d3c57d 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12933.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12933.php @@ -36,4 +36,13 @@ public function issetNarrowsIntRange(int $index, string $name): void assert(isset($this->list[$index])); assertType('int<0, 5>', $index); } + + /** + * @param int<5, 15> $index + */ + public function issetNotWidensIntRange(int $index, string $name): void + { + assert(isset($this->list[$index])); + assertType('int<5, 15>', $index); + } }