From 7c3aa40797ce469b0a84420a803674c9af8beab8 Mon Sep 17 00:00:00 2001 From: Jesper Skytte Date: 2024年5月11日 23:09:49 +0200 Subject: [PATCH 1/8] Fix bug #10862 This fix ensures that we use correct array indicies in>= PHP8.3. (See https://www.php.net/manual/en/migration83.incompatible.php#migration83.incompatible.core.negative-index-to-empty-array). We ensure, that if - PHP version is 8.3+ - AND the current element to add is the first element - AND the key is less than 0 (it's negative) then we set the newAutoIndexes to the negative value + 1. Added tests to show correctness for both < PHP8.3 and>= PHP8.3 --- .../Constant/ConstantArrayTypeBuilder.php | 9 ++++ tests/PHPStan/Analyser/Bug10862Test.php | 30 +++++++++++++ .../Analyser/data/bug-10862-php8.3.php | 42 +++++++++++++++++++ tests/PHPStan/Analyser/data/bug-10862.php | 42 +++++++++++++++++++ 4 files changed, 123 insertions(+) create mode 100644 tests/PHPStan/Analyser/Bug10862Test.php create mode 100644 tests/PHPStan/Analyser/data/bug-10862-php8.3.php create mode 100644 tests/PHPStan/Analyser/data/bug-10862.php diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index 8547fa39a9..08ae008dc1 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -21,6 +21,7 @@ use function max; use function min; use function range; +use const PHP_VERSION_ID; /** @api */ class ConstantArrayTypeBuilder @@ -181,6 +182,14 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt $this->nextAutoIndexes[] = $newAutoIndex; } } + if (PHP_VERSION_ID>= 80300 && count($this->keyTypes) === 1 && $offsetType->getValue() < 0) { + $newAutoIndex = $offsetType->getValue() + 1; + if (!$optional) { + $this->nextAutoIndexes = [$newAutoIndex]; + } else { + $this->nextAutoIndexes[] = $newAutoIndex; + } + } } else { $this->isList = TrinaryLogic::createNo(); } diff --git a/tests/PHPStan/Analyser/Bug10862Test.php b/tests/PHPStan/Analyser/Bug10862Test.php new file mode 100644 index 0000000000..b914a5f045 --- /dev/null +++ b/tests/PHPStan/Analyser/Bug10862Test.php @@ -0,0 +1,30 @@ += 80300 ? 'bug-10862-php8.3' : 'bug-10862'; + yield from self::gatherAssertTypes(__DIR__ . '/data/' . $path . '.php'); + } + + /** + * @dataProvider dataFileAsserts + * @param mixed ...$args + */ + public function testFileAsserts( + string $assertType, + string $file, + ...$args, + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + +} diff --git a/tests/PHPStan/Analyser/data/bug-10862-php8.3.php b/tests/PHPStan/Analyser/data/bug-10862-php8.3.php new file mode 100644 index 0000000000..1c05b9764d --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-10862-php8.3.php @@ -0,0 +1,42 @@ + Date: 2024年5月12日 14:30:01 +0200 Subject: [PATCH 2/8] Use PhpVersion class instead --- src/Php/PhpVersion.php | 6 ++++++ src/Type/Constant/ConstantArrayTypeBuilder.php | 9 ++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 6a1fe74bf2..2a51c0ec61 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -287,4 +287,10 @@ public function supportsNeverReturnTypeInArrowFunction(): bool return $this->versionId>= 80200; } + // See https://www.php.net/manual/en/migration83.incompatible.php#migration83.incompatible.core.negative-index-to-empty-array + public function supportsAssigningANegativeIndexToAnEmptyArray(): bool + { + return $this->versionId>= 80300; + } + } diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index 08ae008dc1..185500bdd0 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -2,6 +2,8 @@ namespace PHPStan\Type\Constant; +use PHPStan\Php\PhpVersion; +use PHPStan\Php\PhpVersionFactory; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; @@ -21,7 +23,6 @@ use function max; use function min; use function range; -use const PHP_VERSION_ID; /** @api */ class ConstantArrayTypeBuilder @@ -40,6 +41,7 @@ class ConstantArrayTypeBuilder * @param array $optionalKeys */ private function __construct( + private readonly PhpVersion $phpVersion, private array $keyTypes, private array $valueTypes, private array $nextAutoIndexes, @@ -51,12 +53,13 @@ private function __construct( public static function createEmpty(): self { - return new self([], [], [0], [], TrinaryLogic::createYes()); + return new self((new PhpVersionFactory(null, null))->create(), [], [], [0], [], TrinaryLogic::createYes()); } public static function createFromConstantArray(ConstantArrayType $startArrayType): self { $builder = new self( + (new PhpVersionFactory(null, null))->create(), $startArrayType->getKeyTypes(), $startArrayType->getValueTypes(), $startArrayType->getNextAutoIndexes(), @@ -182,7 +185,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt $this->nextAutoIndexes[] = $newAutoIndex; } } - if (PHP_VERSION_ID>= 80300 && count($this->keyTypes) === 1 && $offsetType->getValue() < 0) { + if ($this->phpVersion->supportsAssigningANegativeIndexToAnEmptyArray() && count($this->keyTypes) === 1 && $offsetType->getValue() < 0) { $newAutoIndex = $offsetType->getValue() + 1; if (!$optional) { $this->nextAutoIndexes = [$newAutoIndex]; From ad9f71683340dd09b5e710b19c9ba7e9e0ec3d6d Mon Sep 17 00:00:00 2001 From: Jesper Skytte Date: 2024年5月12日 14:53:05 +0200 Subject: [PATCH 3/8] Replace PhpVersionFactory with PhpVersionStaticAccessor --- src/Type/Constant/ConstantArrayTypeBuilder.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index 185500bdd0..8de4e8d1f1 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -2,8 +2,7 @@ namespace PHPStan\Type\Constant; -use PHPStan\Php\PhpVersion; -use PHPStan\Php\PhpVersionFactory; +use PHPStan\Reflection\PhpVersionStaticAccessor; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; @@ -41,7 +40,6 @@ class ConstantArrayTypeBuilder * @param array $optionalKeys */ private function __construct( - private readonly PhpVersion $phpVersion, private array $keyTypes, private array $valueTypes, private array $nextAutoIndexes, @@ -53,13 +51,12 @@ private function __construct( public static function createEmpty(): self { - return new self((new PhpVersionFactory(null, null))->create(), [], [], [0], [], TrinaryLogic::createYes()); + return new self([], [], [0], [], TrinaryLogic::createYes()); } public static function createFromConstantArray(ConstantArrayType $startArrayType): self { $builder = new self( - (new PhpVersionFactory(null, null))->create(), $startArrayType->getKeyTypes(), $startArrayType->getValueTypes(), $startArrayType->getNextAutoIndexes(), @@ -134,6 +131,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt return; } + $phpVersion = PhpVersionStaticAccessor::getInstance(); if ($offsetType instanceof ConstantIntegerType || $offsetType instanceof ConstantStringType) { /** @var ConstantIntegerType|ConstantStringType $keyType */ foreach ($this->keyTypes as $i => $keyType) { @@ -185,7 +183,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt $this->nextAutoIndexes[] = $newAutoIndex; } } - if ($this->phpVersion->supportsAssigningANegativeIndexToAnEmptyArray() && count($this->keyTypes) === 1 && $offsetType->getValue() < 0) { + if ($phpVersion->supportsAssigningANegativeIndexToAnEmptyArray() && count($this->keyTypes) === 1 && $offsetType->getValue() < 0) { $newAutoIndex = $offsetType->getValue() + 1; if (!$optional) { $this->nextAutoIndexes = [$newAutoIndex]; From 7d590e7513d59b4b2297787d702ffb70037f6078 Mon Sep 17 00:00:00 2001 From: Jesper Skytte Date: Sun, 9 Jun 2024 17:09:18 +0200 Subject: [PATCH 4/8] Fix bug bug-10862 Fixing negative array indexes --- src/Php/PhpVersion.php | 10 ++- .../InitializerExprTypeResolver.php | 3 + src/Type/Constant/ConstantArrayType.php | 4 + .../Constant/ConstantArrayTypeBuilder.php | 10 ++- tests/PHPStan/Analyser/Bug10862Test.php | 8 +- .../Analyser/data/bug-10862-php8.0.php | 88 +++++++++++++++++++ .../Analyser/data/bug-10862-php8.3.php | 46 ++++++++++ tests/PHPStan/Analyser/data/bug-10862.php | 46 ++++++++++ 8 files changed, 209 insertions(+), 6 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-10862-php8.0.php diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 2a51c0ec61..f00b196b67 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -287,10 +287,16 @@ public function supportsNeverReturnTypeInArrowFunction(): bool return $this->versionId>= 80200; } + // https://www.php.net/manual/en/migration80.incompatible.php + // Any array that has a number n as its first numeric key will use n+1 for its next implicit key, even if n is negative. + public function supportsNegativeArrayIndexOnCreate(): bool + { + return $this->versionId>= 80000; + } + // See https://www.php.net/manual/en/migration83.incompatible.php#migration83.incompatible.core.negative-index-to-empty-array - public function supportsAssigningANegativeIndexToAnEmptyArray(): bool + public function supportsNegativeArrayIndexOnAssign(): bool { return $this->versionId>= 80300; } - } diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 875df63b9b..ab1f0ca058 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -562,6 +562,9 @@ public function getArrayType(Expr\Array_ $expr, callable $getTypeCallback): Type $arrayBuilder->setOffsetValueType($offsetType, $valueType->getIterableValueType(), !$valueType->isIterableAtLeastOnce()->yes()); } } else { + if (!$this->phpVersion->supportsNegativeArrayIndexOnCreate()) { + $arrayBuilder->resetNextAutoIndexIfNegative(); + } $arrayBuilder->setOffsetValueType( $arrayItem->key !== null ? $getTypeCallback($arrayItem->key) : null, $valueType, diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 7bd109897e..6487f7ef83 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -694,6 +694,10 @@ public function getOffsetValueType(Type $offsetType): Type public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type { $builder = ConstantArrayTypeBuilder::createFromConstantArray($this); + $phpVersion = PhpVersionStaticAccessor::getInstance(); + if (!$phpVersion->supportsNegativeArrayIndexOnAssign()) { + $builder->resetNextAutoIndexToZeroIfNegative(); + } $builder->setOffsetValueType($offsetType, $valueType); return $builder->getArray(); diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index 8de4e8d1f1..9463d7ebbd 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -2,7 +2,6 @@ namespace PHPStan\Type\Constant; -use PHPStan\Reflection\PhpVersionStaticAccessor; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; @@ -131,7 +130,6 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt return; } - $phpVersion = PhpVersionStaticAccessor::getInstance(); if ($offsetType instanceof ConstantIntegerType || $offsetType instanceof ConstantStringType) { /** @var ConstantIntegerType|ConstantStringType $keyType */ foreach ($this->keyTypes as $i => $keyType) { @@ -183,7 +181,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt $this->nextAutoIndexes[] = $newAutoIndex; } } - if ($phpVersion->supportsAssigningANegativeIndexToAnEmptyArray() && count($this->keyTypes) === 1 && $offsetType->getValue() < 0) { + if (count($this->keyTypes) === 1 && $offsetType->getValue() < 0) { $newAutoIndex = $offsetType->getValue() + 1; if (!$optional) { $this->nextAutoIndexes = [$newAutoIndex]; @@ -324,4 +322,10 @@ public function isList(): bool return $this->isList->yes(); } + public function resetNextAutoIndexToZeroIfNegative(): void + { + if (count($this->nextAutoIndexes) === 1 && $this->nextAutoIndexes[0] < 0) { + $this->nextAutoIndexes = [0]; + } + } } diff --git a/tests/PHPStan/Analyser/Bug10862Test.php b/tests/PHPStan/Analyser/Bug10862Test.php index b914a5f045..1eb6533217 100644 --- a/tests/PHPStan/Analyser/Bug10862Test.php +++ b/tests/PHPStan/Analyser/Bug10862Test.php @@ -10,7 +10,13 @@ class Bug10862Test extends TypeInferenceTestCase public function dataFileAsserts(): iterable { - $path = PHP_VERSION_ID>= 80300 ? 'bug-10862-php8.3' : 'bug-10862'; + $path = 'bug-10862-php8.3'; + if (PHP_VERSION_ID < 80300) { + $path = 'bug-10862-php8.0'; + } + if (PHP_VERSION_ID < 80000) { + $path = 'bug-10862'; + } yield from self::gatherAssertTypes(__DIR__ . '/data/' . $path . '.php'); } diff --git a/tests/PHPStan/Analyser/data/bug-10862-php8.0.php b/tests/PHPStan/Analyser/data/bug-10862-php8.0.php new file mode 100644 index 0000000000..e3444adaaa --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-10862-php8.0.php @@ -0,0 +1,88 @@ + 1, 2]; + +assertType('array{0, 1}', array_keys($a)); + +$a = [4 => 1, 5]; + +assertType('array{4, 5}', array_keys($a)); + +$a = [-4 => 1, 2]; + +assertType('array{-4, -3}', array_keys($a)); + +$a = [-4 => 1, -2 => 1, 2]; + +assertType('array{-4, -2, -1}', array_keys($a)); + +$a = [-3 => 1, -4 => 1, -2 => 1, 2]; + +assertType('array{-3, -4, -2, -1}', array_keys($a)); + +$a = [-1 => 1, 2]; + +assertType('array{-1, 0}', array_keys($a)); + +$a = [0 => 1, 2]; + +assertType('array{0, 1}', array_keys($a)); + +$a = [-268 => 1, -267 => 1, -266 => 1, -265 => 1, -264 => 1, -263 => 1, -262 => 1, -261 => 1, -260 => 1, -259 => 1, -258 => 1, -257 => 1, -256 => 1, -255 => 1, -254 => 1, -253 => 1, -252 => 1, -251 => 1, -250 => 1, -249 => 1, -248 => 1, -247 => 1, -246 => 1, -245 => 1, -244 => 1, -243 => 1, -242 => 1, -241 => 1, -240 => 1, -239 => 1, -238 => 1, -237 => 1, -236 => 1, -235 => 1, -234 => 1, -233 => 1, -232 => 1, -231 => 1, -230 => 1, -229 => 1, -228 => 1, -227 => 1, -226 => 1, -225 => 1, -224 => 1, -223 => 1, -222 => 1, -221 => 1, -220 => 1, -219 => 1, -218 => 1, -217 => 1, -216 => 1, -215 => 1, -214 => 1, -213 => 1, -212 => 1, -211 => 1, -210 => 1, -209 => 1, -208 => 1, -207 => 1, -206 => 1, -205 => 1, -204 => 1, -203 => 1, -202 => 1, -201 => 1, -200 => 1, -199 => 1, -198 => 1, -197 => 1, -196 => 1, -195 => 1, -194 => 1, -193 => 1, -192 => 1, -191 => 1, -190 => 1, -189 => 1, -188 => 1, -187 => 1, -186 => 1, -185 => 1, -184 => 1, -183 => 1, -182 => 1, -181 => 1, -180 => 1, -179 => 1, -178 => 1, -177 => 1, -176 => 1, -175 => 1, -174 => 1, -173 => 1, -172 => 1, -171 => 1, -170 => 1, -169 => 1, -168 => 1, -167 => 1, -166 => 1, -165 => 1, -164 => 1, -163 => 1, -162 => 1, -161 => 1, -160 => 1, -159 => 1, -158 => 1, -157 => 1, -156 => 1, -155 => 1, -154 => 1, -153 => 1, -152 => 1, -151 => 1, -150 => 1, -149 => 1, -148 => 1, -147 => 1, -146 => 1, -145 => 1, -144 => 1, -143 => 1, -142 => 1, -141 => 1, -140 => 1, -139 => 1, -138 => 1, -137 => 1, -136 => 1, -135 => 1, -134 => 1, -133 => 1, -132 => 1, -131 => 1, -130 => 1, -129 => 1, -128 => 1, -127 => 1, -126 => 1, -125 => 1, -124 => 1, -123 => 1, -122 => 1, -121 => 1, -120 => 1, -119 => 1, -118 => 1, -117 => 1, -116 => 1, -115 => 1, -114 => 1, -113 => 1, -112 => 1, -111 => 1, -110 => 1, -109 => 1, -108 => 1, -107 => 1, -106 => 1, -105 => 1, -104 => 1, -103 => 1, -102 => 1, -101 => 1, -100 => 1, -99 => 1, -98 => 1, -97 => 1, -96 => 1, -95 => 1, -94 => 1, -93 => 1, -92 => 1, -91 => 1, -90 => 1, -89 => 1, -88 => 1, -87 => 1, -86 => 1, -85 => 1, -84 => 1, -83 => 1, -82 => 1, -81 => 1, -80 => 1, -79 => 1, -78 => 1, -77 => 1, -76 => 1, -75 => 1, -74 => 1, -73 => 1, -72 => 1, -71 => 1, -70 => 1, -69 => 1, -68 => 1, -67 => 1, -66 => 1, -65 => 1, -64 => 1, -63 => 1, -62 => 1, -61 => 1, -60 => 1, -59 => 1, -58 => 1, -57 => 1, -56 => 1, -55 => 1, -54 => 1, -53 => 1, -52 => 1, -51 => 1, -50 => 1, -49 => 1, -48 => 1, -47 => 1, -46 => 1, -45 => 1, -44 => 1, -43 => 1, -42 => 1, -41 => 1, -40 => 1, -39 => 1, -38 => 1, -37 => 1, -36 => 1, -35 => 1, -34 => 1, -33 => 1, -32 => 1, -31 => 1, -30 => 1, -29 => 1, -28 => 1, -27 => 1, -26 => 1, -25 => 1, -24 => 1, -23 => 1, -22 => 1, -21 => 1, -20 => 1, -19 => 1, -18 => 1, -17 => 1, -16 => 1, -15 => 1, -14 => 1, -13 => 1, -12 => 1, -11 => 1, -10 => 1, 2]; + +assertType('non-empty-array&oversized-array', array_keys($a)); diff --git a/tests/PHPStan/Analyser/data/bug-10862-php8.3.php b/tests/PHPStan/Analyser/data/bug-10862-php8.3.php index 1c05b9764d..47e29702f3 100644 --- a/tests/PHPStan/Analyser/data/bug-10862-php8.3.php +++ b/tests/PHPStan/Analyser/data/bug-10862-php8.3.php @@ -40,3 +40,49 @@ $a[] = 2; assertType('array{0, 1}', array_keys($a)); + +$a = []; +$a[-3] = 1; +$a[13] = 2; +$a[] = 3; + +assertType('array{-3, 13, 14}', array_keys($a)); + +$a = []; +$a[13] = 1; +$a[-3] = 2; +$a[] = 3; + +assertType('array{13, -3, 14}', array_keys($a)); + +$a = [0 => 1, 2]; + +assertType('array{0, 1}', array_keys($a)); + +$a = [4 => 1, 5]; + +assertType('array{4, 5}', array_keys($a)); + +$a = [-4 => 1, 2]; + +assertType('array{-4, -3}', array_keys($a)); + +$a = [-4 => 1, -2 => 1, 2]; + +assertType('array{-4, -2, -1}', array_keys($a)); + +$a = [-3 => 1, -4 => 1, -2 => 1, 2]; + +assertType('array{-3, -4, -2, -1}', array_keys($a)); + +$a = [-1 => 1, 2]; + +assertType('array{-1, 0}', array_keys($a)); + +$a = [0 => 1, 2]; + +assertType('array{0, 1}', array_keys($a)); + +$a = [-268 => 1, -267 => 1, -266 => 1, -265 => 1, -264 => 1, -263 => 1, -262 => 1, -261 => 1, -260 => 1, -259 => 1, -258 => 1, -257 => 1, -256 => 1, -255 => 1, -254 => 1, -253 => 1, -252 => 1, -251 => 1, -250 => 1, -249 => 1, -248 => 1, -247 => 1, -246 => 1, -245 => 1, -244 => 1, -243 => 1, -242 => 1, -241 => 1, -240 => 1, -239 => 1, -238 => 1, -237 => 1, -236 => 1, -235 => 1, -234 => 1, -233 => 1, -232 => 1, -231 => 1, -230 => 1, -229 => 1, -228 => 1, -227 => 1, -226 => 1, -225 => 1, -224 => 1, -223 => 1, -222 => 1, -221 => 1, -220 => 1, -219 => 1, -218 => 1, -217 => 1, -216 => 1, -215 => 1, -214 => 1, -213 => 1, -212 => 1, -211 => 1, -210 => 1, -209 => 1, -208 => 1, -207 => 1, -206 => 1, -205 => 1, -204 => 1, -203 => 1, -202 => 1, -201 => 1, -200 => 1, -199 => 1, -198 => 1, -197 => 1, -196 => 1, -195 => 1, -194 => 1, -193 => 1, -192 => 1, -191 => 1, -190 => 1, -189 => 1, -188 => 1, -187 => 1, -186 => 1, -185 => 1, -184 => 1, -183 => 1, -182 => 1, -181 => 1, -180 => 1, -179 => 1, -178 => 1, -177 => 1, -176 => 1, -175 => 1, -174 => 1, -173 => 1, -172 => 1, -171 => 1, -170 => 1, -169 => 1, -168 => 1, -167 => 1, -166 => 1, -165 => 1, -164 => 1, -163 => 1, -162 => 1, -161 => 1, -160 => 1, -159 => 1, -158 => 1, -157 => 1, -156 => 1, -155 => 1, -154 => 1, -153 => 1, -152 => 1, -151 => 1, -150 => 1, -149 => 1, -148 => 1, -147 => 1, -146 => 1, -145 => 1, -144 => 1, -143 => 1, -142 => 1, -141 => 1, -140 => 1, -139 => 1, -138 => 1, -137 => 1, -136 => 1, -135 => 1, -134 => 1, -133 => 1, -132 => 1, -131 => 1, -130 => 1, -129 => 1, -128 => 1, -127 => 1, -126 => 1, -125 => 1, -124 => 1, -123 => 1, -122 => 1, -121 => 1, -120 => 1, -119 => 1, -118 => 1, -117 => 1, -116 => 1, -115 => 1, -114 => 1, -113 => 1, -112 => 1, -111 => 1, -110 => 1, -109 => 1, -108 => 1, -107 => 1, -106 => 1, -105 => 1, -104 => 1, -103 => 1, -102 => 1, -101 => 1, -100 => 1, -99 => 1, -98 => 1, -97 => 1, -96 => 1, -95 => 1, -94 => 1, -93 => 1, -92 => 1, -91 => 1, -90 => 1, -89 => 1, -88 => 1, -87 => 1, -86 => 1, -85 => 1, -84 => 1, -83 => 1, -82 => 1, -81 => 1, -80 => 1, -79 => 1, -78 => 1, -77 => 1, -76 => 1, -75 => 1, -74 => 1, -73 => 1, -72 => 1, -71 => 1, -70 => 1, -69 => 1, -68 => 1, -67 => 1, -66 => 1, -65 => 1, -64 => 1, -63 => 1, -62 => 1, -61 => 1, -60 => 1, -59 => 1, -58 => 1, -57 => 1, -56 => 1, -55 => 1, -54 => 1, -53 => 1, -52 => 1, -51 => 1, -50 => 1, -49 => 1, -48 => 1, -47 => 1, -46 => 1, -45 => 1, -44 => 1, -43 => 1, -42 => 1, -41 => 1, -40 => 1, -39 => 1, -38 => 1, -37 => 1, -36 => 1, -35 => 1, -34 => 1, -33 => 1, -32 => 1, -31 => 1, -30 => 1, -29 => 1, -28 => 1, -27 => 1, -26 => 1, -25 => 1, -24 => 1, -23 => 1, -22 => 1, -21 => 1, -20 => 1, -19 => 1, -18 => 1, -17 => 1, -16 => 1, -15 => 1, -14 => 1, -13 => 1, -12 => 1, -11 => 1, -10 => 1, 2]; + +assertType('non-empty-array&oversized-array', array_keys($a)); diff --git a/tests/PHPStan/Analyser/data/bug-10862.php b/tests/PHPStan/Analyser/data/bug-10862.php index 795fc3321c..b94573a16d 100644 --- a/tests/PHPStan/Analyser/data/bug-10862.php +++ b/tests/PHPStan/Analyser/data/bug-10862.php @@ -40,3 +40,49 @@ $a[] = 2; assertType('array{0, 1}', array_keys($a)); + +$a = []; +$a[-3] = 1; +$a[13] = 2; +$a[] = 3; + +assertType('array{-3, 13, 14}', array_keys($a)); + +$a = []; +$a[13] = 1; +$a[-3] = 2; +$a[] = 3; + +assertType('array{13, -3, 14}', array_keys($a)); + +$a = [0 => 1, 2]; + +assertType('array{0, 1}', array_keys($a)); + +$a = [4 => 1, 5]; + +assertType('array{4, 5}', array_keys($a)); + +$a = [-4 => 1, 2]; + +assertType('array{-4, -3}', array_keys($a)); + +$a = [-4 => 1, -2 => 1, 2]; + +assertType('array{-4, -2, -1}', array_keys($a)); + +$a = [-3 => 1, -4 => 1, -2 => 1, 2]; + +assertType('array{-3, -4, -2, -1}', array_keys($a)); + +$a = [-1 => 1, 2]; + +assertType('array{-1, 0}', array_keys($a)); + +$a = [0 => 1, 2]; + +assertType('array{0, 1}', array_keys($a)); + +$a = [-268 => 1, -267 => 1, -266 => 1, -265 => 1, -264 => 1, -263 => 1, -262 => 1, -261 => 1, -260 => 1, -259 => 1, -258 => 1, -257 => 1, -256 => 1, -255 => 1, -254 => 1, -253 => 1, -252 => 1, -251 => 1, -250 => 1, -249 => 1, -248 => 1, -247 => 1, -246 => 1, -245 => 1, -244 => 1, -243 => 1, -242 => 1, -241 => 1, -240 => 1, -239 => 1, -238 => 1, -237 => 1, -236 => 1, -235 => 1, -234 => 1, -233 => 1, -232 => 1, -231 => 1, -230 => 1, -229 => 1, -228 => 1, -227 => 1, -226 => 1, -225 => 1, -224 => 1, -223 => 1, -222 => 1, -221 => 1, -220 => 1, -219 => 1, -218 => 1, -217 => 1, -216 => 1, -215 => 1, -214 => 1, -213 => 1, -212 => 1, -211 => 1, -210 => 1, -209 => 1, -208 => 1, -207 => 1, -206 => 1, -205 => 1, -204 => 1, -203 => 1, -202 => 1, -201 => 1, -200 => 1, -199 => 1, -198 => 1, -197 => 1, -196 => 1, -195 => 1, -194 => 1, -193 => 1, -192 => 1, -191 => 1, -190 => 1, -189 => 1, -188 => 1, -187 => 1, -186 => 1, -185 => 1, -184 => 1, -183 => 1, -182 => 1, -181 => 1, -180 => 1, -179 => 1, -178 => 1, -177 => 1, -176 => 1, -175 => 1, -174 => 1, -173 => 1, -172 => 1, -171 => 1, -170 => 1, -169 => 1, -168 => 1, -167 => 1, -166 => 1, -165 => 1, -164 => 1, -163 => 1, -162 => 1, -161 => 1, -160 => 1, -159 => 1, -158 => 1, -157 => 1, -156 => 1, -155 => 1, -154 => 1, -153 => 1, -152 => 1, -151 => 1, -150 => 1, -149 => 1, -148 => 1, -147 => 1, -146 => 1, -145 => 1, -144 => 1, -143 => 1, -142 => 1, -141 => 1, -140 => 1, -139 => 1, -138 => 1, -137 => 1, -136 => 1, -135 => 1, -134 => 1, -133 => 1, -132 => 1, -131 => 1, -130 => 1, -129 => 1, -128 => 1, -127 => 1, -126 => 1, -125 => 1, -124 => 1, -123 => 1, -122 => 1, -121 => 1, -120 => 1, -119 => 1, -118 => 1, -117 => 1, -116 => 1, -115 => 1, -114 => 1, -113 => 1, -112 => 1, -111 => 1, -110 => 1, -109 => 1, -108 => 1, -107 => 1, -106 => 1, -105 => 1, -104 => 1, -103 => 1, -102 => 1, -101 => 1, -100 => 1, -99 => 1, -98 => 1, -97 => 1, -96 => 1, -95 => 1, -94 => 1, -93 => 1, -92 => 1, -91 => 1, -90 => 1, -89 => 1, -88 => 1, -87 => 1, -86 => 1, -85 => 1, -84 => 1, -83 => 1, -82 => 1, -81 => 1, -80 => 1, -79 => 1, -78 => 1, -77 => 1, -76 => 1, -75 => 1, -74 => 1, -73 => 1, -72 => 1, -71 => 1, -70 => 1, -69 => 1, -68 => 1, -67 => 1, -66 => 1, -65 => 1, -64 => 1, -63 => 1, -62 => 1, -61 => 1, -60 => 1, -59 => 1, -58 => 1, -57 => 1, -56 => 1, -55 => 1, -54 => 1, -53 => 1, -52 => 1, -51 => 1, -50 => 1, -49 => 1, -48 => 1, -47 => 1, -46 => 1, -45 => 1, -44 => 1, -43 => 1, -42 => 1, -41 => 1, -40 => 1, -39 => 1, -38 => 1, -37 => 1, -36 => 1, -35 => 1, -34 => 1, -33 => 1, -32 => 1, -31 => 1, -30 => 1, -29 => 1, -28 => 1, -27 => 1, -26 => 1, -25 => 1, -24 => 1, -23 => 1, -22 => 1, -21 => 1, -20 => 1, -19 => 1, -18 => 1, -17 => 1, -16 => 1, -15 => 1, -14 => 1, -13 => 1, -12 => 1, -11 => 1, -10 => 1, 2]; + +assertType('array{0, 1}', array_keys($a)); From 1f602758167cff5f1ef7016ccfa2f881564565fa Mon Sep 17 00:00:00 2001 From: Jesper Skytte Date: Sun, 9 Jun 2024 17:18:12 +0200 Subject: [PATCH 5/8] CSFixer --- src/Php/PhpVersion.php | 1 + src/Type/Constant/ConstantArrayTypeBuilder.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index f00b196b67..d4ed64ff9b 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -299,4 +299,5 @@ public function supportsNegativeArrayIndexOnAssign(): bool { return $this->versionId>= 80300; } + } diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index 9463d7ebbd..aaffab4da0 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -328,4 +328,5 @@ public function resetNextAutoIndexToZeroIfNegative(): void $this->nextAutoIndexes = [0]; } } + } From fce8b152381caf84b958baa67e2d57aa33ab69b2 Mon Sep 17 00:00:00 2001 From: Jesper Skytte Date: Sun, 9 Jun 2024 18:40:27 +0200 Subject: [PATCH 6/8] PHPStan, wrong method and CS Fixer --- src/Reflection/InitializerExprTypeResolver.php | 2 +- src/Type/Constant/ConstantArrayTypeBuilder.php | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index ab1f0ca058..328c710392 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -563,7 +563,7 @@ public function getArrayType(Expr\Array_ $expr, callable $getTypeCallback): Type } } else { if (!$this->phpVersion->supportsNegativeArrayIndexOnCreate()) { - $arrayBuilder->resetNextAutoIndexIfNegative(); + $arrayBuilder->resetNextAutoIndexToZeroIfNegative(); } $arrayBuilder->setOffsetValueType( $arrayItem->key !== null ? $getTypeCallback($arrayItem->key) : null, diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index aaffab4da0..d16c7fd7ab 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -324,9 +324,10 @@ public function isList(): bool public function resetNextAutoIndexToZeroIfNegative(): void { - if (count($this->nextAutoIndexes) === 1 && $this->nextAutoIndexes[0] < 0) { - $this->nextAutoIndexes = [0]; + if (count($this->nextAutoIndexes) !== 1 || $this->nextAutoIndexes[0]>= 0) { + return; } + $this->nextAutoIndexes = [0]; } } From 5e9f5b0c2a1a0a786f5852477f5e3c0791988a24 Mon Sep 17 00:00:00 2001 From: Jesper Skytte Date: Sun, 9 Jun 2024 18:54:07 +0200 Subject: [PATCH 7/8] Fix 1, -2 => 1, 2]; -assertType('array{-4, -2, -1}', array_keys($a)); +assertType('array{-4, -2, 0}', array_keys($a)); $a = [-3 => 1, -4 => 1, -2 => 1, 2]; -assertType('array{-3, -4, -2, -1}', array_keys($a)); +assertType('array{-3, -4, -2, 0}', array_keys($a)); $a = [-1 => 1, 2]; @@ -85,4 +85,4 @@ $a = [-268 => 1, -267 => 1, -266 => 1, -265 => 1, -264 => 1, -263 => 1, -262 => 1, -261 => 1, -260 => 1, -259 => 1, -258 => 1, -257 => 1, -256 => 1, -255 => 1, -254 => 1, -253 => 1, -252 => 1, -251 => 1, -250 => 1, -249 => 1, -248 => 1, -247 => 1, -246 => 1, -245 => 1, -244 => 1, -243 => 1, -242 => 1, -241 => 1, -240 => 1, -239 => 1, -238 => 1, -237 => 1, -236 => 1, -235 => 1, -234 => 1, -233 => 1, -232 => 1, -231 => 1, -230 => 1, -229 => 1, -228 => 1, -227 => 1, -226 => 1, -225 => 1, -224 => 1, -223 => 1, -222 => 1, -221 => 1, -220 => 1, -219 => 1, -218 => 1, -217 => 1, -216 => 1, -215 => 1, -214 => 1, -213 => 1, -212 => 1, -211 => 1, -210 => 1, -209 => 1, -208 => 1, -207 => 1, -206 => 1, -205 => 1, -204 => 1, -203 => 1, -202 => 1, -201 => 1, -200 => 1, -199 => 1, -198 => 1, -197 => 1, -196 => 1, -195 => 1, -194 => 1, -193 => 1, -192 => 1, -191 => 1, -190 => 1, -189 => 1, -188 => 1, -187 => 1, -186 => 1, -185 => 1, -184 => 1, -183 => 1, -182 => 1, -181 => 1, -180 => 1, -179 => 1, -178 => 1, -177 => 1, -176 => 1, -175 => 1, -174 => 1, -173 => 1, -172 => 1, -171 => 1, -170 => 1, -169 => 1, -168 => 1, -167 => 1, -166 => 1, -165 => 1, -164 => 1, -163 => 1, -162 => 1, -161 => 1, -160 => 1, -159 => 1, -158 => 1, -157 => 1, -156 => 1, -155 => 1, -154 => 1, -153 => 1, -152 => 1, -151 => 1, -150 => 1, -149 => 1, -148 => 1, -147 => 1, -146 => 1, -145 => 1, -144 => 1, -143 => 1, -142 => 1, -141 => 1, -140 => 1, -139 => 1, -138 => 1, -137 => 1, -136 => 1, -135 => 1, -134 => 1, -133 => 1, -132 => 1, -131 => 1, -130 => 1, -129 => 1, -128 => 1, -127 => 1, -126 => 1, -125 => 1, -124 => 1, -123 => 1, -122 => 1, -121 => 1, -120 => 1, -119 => 1, -118 => 1, -117 => 1, -116 => 1, -115 => 1, -114 => 1, -113 => 1, -112 => 1, -111 => 1, -110 => 1, -109 => 1, -108 => 1, -107 => 1, -106 => 1, -105 => 1, -104 => 1, -103 => 1, -102 => 1, -101 => 1, -100 => 1, -99 => 1, -98 => 1, -97 => 1, -96 => 1, -95 => 1, -94 => 1, -93 => 1, -92 => 1, -91 => 1, -90 => 1, -89 => 1, -88 => 1, -87 => 1, -86 => 1, -85 => 1, -84 => 1, -83 => 1, -82 => 1, -81 => 1, -80 => 1, -79 => 1, -78 => 1, -77 => 1, -76 => 1, -75 => 1, -74 => 1, -73 => 1, -72 => 1, -71 => 1, -70 => 1, -69 => 1, -68 => 1, -67 => 1, -66 => 1, -65 => 1, -64 => 1, -63 => 1, -62 => 1, -61 => 1, -60 => 1, -59 => 1, -58 => 1, -57 => 1, -56 => 1, -55 => 1, -54 => 1, -53 => 1, -52 => 1, -51 => 1, -50 => 1, -49 => 1, -48 => 1, -47 => 1, -46 => 1, -45 => 1, -44 => 1, -43 => 1, -42 => 1, -41 => 1, -40 => 1, -39 => 1, -38 => 1, -37 => 1, -36 => 1, -35 => 1, -34 => 1, -33 => 1, -32 => 1, -31 => 1, -30 => 1, -29 => 1, -28 => 1, -27 => 1, -26 => 1, -25 => 1, -24 => 1, -23 => 1, -22 => 1, -21 => 1, -20 => 1, -19 => 1, -18 => 1, -17 => 1, -16 => 1, -15 => 1, -14 => 1, -13 => 1, -12 => 1, -11 => 1, -10 => 1, 2]; -assertType('array{0, 1}', array_keys($a)); +assertType('non-empty-array&oversized-array', array_keys($a)); From 2a5d0e14bf578477defb1b9db7a81e53325a8cc0 Mon Sep 17 00:00:00 2001 From: Jesper Skytte Date: Sun, 9 Jun 2024 19:29:29 +0200 Subject: [PATCH 8/8] Bugger... Missed one... --- tests/PHPStan/Analyser/data/bug-10862.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/data/bug-10862.php b/tests/PHPStan/Analyser/data/bug-10862.php index d875c2c634..5aba94b9eb 100644 --- a/tests/PHPStan/Analyser/data/bug-10862.php +++ b/tests/PHPStan/Analyser/data/bug-10862.php @@ -65,7 +65,7 @@ $a = [-4 => 1, 2]; -assertType('array{-4, -3}', array_keys($a)); +assertType('array{-4, 0}', array_keys($a)); $a = [-4 => 1, -2 => 1, 2];

AltStyle によって変換されたページ (->オリジナル) /