diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 7d2d0c9bc1..c11a30e778 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -734,7 +734,9 @@ public function unsetOffset(Type $offsetType): Type $k++; } - return new self($newKeyTypes, $newValueTypes, $this->nextAutoIndexes, $newOptionalKeys, TrinaryLogic::createNo()); + $wasLastOffset = TrinaryLogic::createFromBoolean($i === count($this->keyTypes) - 1); + + return new self($newKeyTypes, $newValueTypes, $this->nextAutoIndexes, $newOptionalKeys, $this->isList->and($wasLastOffset)); } return $this; diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index a306833e8d..349f0855b5 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -122,14 +122,21 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt $newAutoIndexes[] = $newAutoIndex; $this->nextAutoIndexes = array_values(array_unique($newAutoIndexes)); + $keyTypesCount = count($this->keyTypes); + if ($optional || $hasOptional) { - $this->optionalKeys[] = count($this->keyTypes) - 1; + $this->optionalKeys[] = $keyTypesCount - 1; } - if (count($this->keyTypes) > self::ARRAY_COUNT_LIMIT) { + if ($keyTypesCount > self::ARRAY_COUNT_LIMIT) { $this->degradeToGeneralArray = true; } + if ($keyTypesCount - 1 !== $max && $this->isList->yes()) { + // The array is not a list any longer if there is gap in the offsets + $this->isList = TrinaryLogic::createNo(); + } + return; } diff --git a/tests/PHPStan/Analyser/nsrt/array-is-list-unset.php b/tests/PHPStan/Analyser/nsrt/array-is-list-unset.php index b14b8c97df..49ba96215a 100644 --- a/tests/PHPStan/Analyser/nsrt/array-is-list-unset.php +++ b/tests/PHPStan/Analyser/nsrt/array-is-list-unset.php @@ -19,7 +19,7 @@ function () { assertType('true', array_is_list($a)); unset($a[2]); assertType('array{1, 2}', $a); - assertType('false', array_is_list($a)); + assertType('true', array_is_list($a)); $a[] = 4; assertType('array{0: 1, 1: 2, 3: 4}', $a); assertType('false', array_is_list($a)); diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 6d374a6f1c..11860d5513 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1054,4 +1054,9 @@ public function testBug10715(): void $this->analyse([__DIR__ . '/data/bug-10715.php'], []); } + public function testBug11718(): void + { + $this->analyse([__DIR__ . '/data/bug-11718.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-11718.php b/tests/PHPStan/Rules/Methods/data/bug-11718.php new file mode 100644 index 0000000000..db806df145 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-11718.php @@ -0,0 +1,18 @@ + + */ + public function test(string $a, string $b): array + { + $list = ['x', 'y', $a, $b]; + + unset($list[3]); // no error if uncommented! + + return $list; + } +}