-
Notifications
You must be signed in to change notification settings - Fork 570
Fix phpstan/phpstan#10862: Invalid array key for constant array append when negative keys are used since PHP 8.3 #5412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 2.1.x
Are you sure you want to change the base?
Changes from 6 commits
e8ee204
c54661b
47110f1
369c2a1
e168a11
9ac8db3
8e30d38
557536d
694ddc9
40956df
2ad0306
94e9657
15a85d8
ee2c7a3
4a7fe14
e8fe43a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
|
|
||
| namespace PHPStan\Type\Constant; | ||
|
|
||
| use PHPStan\Reflection\PhpVersionStaticAccessor; | ||
| use PHPStan\ShouldNotHappenException; | ||
| use PHPStan\TrinaryLogic; | ||
| use PHPStan\Type\Accessory\AccessoryArrayListType; | ||
|
|
@@ -41,6 +42,8 @@ final class ConstantArrayTypeBuilder | |
|
|
||
| private bool $oversized = false; | ||
|
|
||
| private bool $isLiteralArray = false; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the "isLiteralArray" naming matches reality. we need to find a better name
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pushed. Here's what I did to address staabm's review: Renamed
The term "array expression" better matches PHP parser terminology ( Regarding staabm's second concern (whether this is needed beyond |
||
|
|
||
| /** | ||
| * @param list<Type> $keyTypes | ||
| * @param array<int, Type> $valueTypes | ||
|
|
@@ -62,6 +65,11 @@ public static function createEmpty(): self | |
| return new self([], [], [0], [], TrinaryLogic::createYes()); | ||
| } | ||
|
|
||
| public function setLiteralArray(): void | ||
| { | ||
| $this->isLiteralArray = true; | ||
| } | ||
|
|
||
| public static function createFromConstantArray(ConstantArrayType $startArrayType): self | ||
| { | ||
| $builder = new self( | ||
|
|
@@ -209,7 +217,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt | |
| $this->isList = TrinaryLogic::createNo(); | ||
| } | ||
|
|
||
| if ($offsetValue >= $max) { | ||
| if ($this->shouldUpdateAutoIndex($offsetValue, $max)) { | ||
| /** @var int|float $newAutoIndex */ | ||
| $newAutoIndex = $offsetValue + 1; | ||
| if (is_float($newAutoIndex)) { | ||
|
|
@@ -419,4 +427,23 @@ public function isList(): bool | |
| return $this->isList->yes(); | ||
| } | ||
|
|
||
| private function shouldUpdateAutoIndex(int $offsetValue, int $max): bool | ||
| { | ||
| if ($offsetValue >= $max) { | ||
| return true; | ||
| } | ||
|
|
||
| if ($offsetValue >= 0 || $max !== 0) { | ||
| return false; | ||
| } | ||
|
|
||
| $phpVersion = PhpVersionStaticAccessor::getInstance(); | ||
|
|
||
| if ($phpVersion->updatesAutoIncrementKeyForNegativeValues()) { | ||
| return true; | ||
| } | ||
|
|
||
| return $this->isLiteralArray && $phpVersion->updatesAutoIncrementKeyForNegativeValuesInArrayLiteral(); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <?php // lint < 8.0 | ||
|
|
||
| declare(strict_types = 1); | ||
|
|
||
| namespace Bug10862Php74; | ||
|
|
||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| // Pre-PHP 8.0: negative keys never affect auto-index | ||
|
|
||
| // Imperative assignment | ||
| function () { | ||
| $a = []; | ||
| $a[-4] = 1; | ||
| $a[] = 2; | ||
|
|
||
| assertType('array{-4: 1, 0: 2}', $a); | ||
| }; | ||
|
|
||
| // Array literal | ||
| function () { | ||
| $a = [-4 => 1]; | ||
| $a[] = 2; | ||
|
|
||
| assertType('array{-4: 1, 0: 2}', $a); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| <?php // lint >= 8.0 | ||
|
|
||
| declare(strict_types = 1); | ||
|
|
||
| namespace Bug10862Php80; | ||
|
|
||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| // PHP 8.0+: array literal with negative keys updates auto-index | ||
|
|
||
| function () { | ||
| $a = [-4 => 1]; | ||
| $a[] = 2; | ||
|
|
||
| assertType('array{-4: 1, -3: 2}', $a); | ||
| }; | ||
|
|
||
| function () { | ||
| $a = [-10 => 'a', -5 => 'b']; | ||
| $a[] = 'c'; | ||
|
|
||
| assertType("array{-10: 'a', -5: 'b', -4: 'c'}", $a); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| <?php // lint <= 8.2 | ||
|
VincentLanglet marked this conversation as resolved.
|
||
|
|
||
| declare(strict_types = 1); | ||
|
|
||
| namespace Bug10862Php82; | ||
|
|
||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| // PHP <= 8.2: imperative assignment with negative keys does not affect auto-index | ||
|
|
||
| function () { | ||
| $a = []; | ||
| $a[-4] = 1; | ||
| $a[] = 2; | ||
|
|
||
| assertType('array{-4: 1, 0: 2}', $a); | ||
| }; | ||
|
|
||
| function () { | ||
| $a = []; | ||
| $a[-1] = 'x'; | ||
| $a[] = 'y'; | ||
|
|
||
| assertType("array{-1: 'x', 0: 'y'}", $a); | ||
| }; | ||
|
|
||
| function () { | ||
| $a = []; | ||
| $a[-10] = 'a'; | ||
| $a[-5] = 'b'; | ||
| $a[] = 'c'; | ||
|
|
||
| assertType("array{-10: 'a', -5: 'b', 0: 'c'}", $a); | ||
| }; | ||
|
|
||
| function () { | ||
| $a = []; | ||
| $a[-3] = 'a'; | ||
| $a[5] = 'b'; | ||
| $a[] = 'c'; | ||
|
|
||
| assertType("array{-3: 'a', 5: 'b', 6: 'c'}", $a); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| <?php // lint >= 8.3 | ||
|
|
||
| declare(strict_types = 1); | ||
|
|
||
| namespace Bug10862Php83; | ||
|
|
||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| // PHP 8.3+: negative keys always affect auto-index (both imperative and literal) | ||
|
|
||
| // Imperative assignment | ||
| function () { | ||
| $a = []; | ||
| $a[-4] = 1; | ||
| $a[] = 2; | ||
|
|
||
| assertType('array{-4: 1, -3: 2}', $a); | ||
| }; | ||
|
|
||
| function () { | ||
| $a = []; | ||
| $a[-1] = 'x'; | ||
| $a[] = 'y'; | ||
|
|
||
| assertType("array{-1: 'x', 0: 'y'}", $a); | ||
| }; | ||
|
|
||
| function () { | ||
| $a = []; | ||
| $a[-10] = 'a'; | ||
| $a[-5] = 'b'; | ||
| $a[] = 'c'; | ||
|
|
||
| assertType("array{-10: 'a', -5: 'b', -4: 'c'}", $a); | ||
| }; | ||
|
|
||
| function () { | ||
| $a = []; | ||
| $a[-3] = 'a'; | ||
| $a[5] = 'b'; | ||
| $a[] = 'c'; | ||
|
|
||
| assertType("array{-3: 'a', 5: 'b', 6: 'c'}", $a); | ||
| }; | ||
|
|
||
| // Array literal | ||
| function () { | ||
| $a = [-4 => 1]; | ||
| $a[] = 2; | ||
|
|
||
| assertType('array{-4: 1, -3: 2}', $a); | ||
| }; | ||
|
|
||
| function () { | ||
| $a = [-10 => 'a', -5 => 'b']; | ||
| $a[] = 'c'; | ||
|
|
||
| assertType("array{-10: 'a', -5: 'b', -4: 'c'}", $a); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does it really make sense only in this scenario? don't we need this behaviour every time a array-shape with known/constant keys is involved?