-
Notifications
You must be signed in to change notification settings - Fork 575
Narrow array key type when removing HasOffsetType via ArrayType::tryRemove
#5756
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 all commits
bba81ce
bf09eb9
f243240
d6ba962
92a94f3
c661bcb
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 |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| <?php // lint >= 8.0 | ||
|
|
||
| declare(strict_types = 1); | ||
|
|
||
| namespace Bug9461; | ||
|
|
||
| use function array_key_exists; | ||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| function test(): void { | ||
| /** @var array<0|string, mixed> */ | ||
| $defaults = ['test', 'a' => 1]; | ||
|
|
||
| assertType('array<0|string, mixed>', $defaults); | ||
| if (!array_key_exists(0, $defaults)) { | ||
| assertType('array<string, mixed>', $defaults); | ||
| } else { | ||
| assertType('non-empty-array<0|string, mixed>&hasOffset(0)', $defaults); | ||
| } | ||
|
Comment on lines
+11
to
+19
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. might make sense to do a similar test for a non-empty-array (a IntersectionType)
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:
All tests pass (12160 tests, 79841 assertions) and |
||
| } | ||
|
|
||
| function testStringKey(): void { | ||
| /** @var array<'foo'|int, mixed> */ | ||
| $arr = []; | ||
|
|
||
| if (!array_key_exists('foo', $arr)) { | ||
| assertType('array<int, mixed>', $arr); | ||
| } else { | ||
| assertType("non-empty-array<'foo'|int, mixed>&hasOffset('foo')", $arr); | ||
| } | ||
| } | ||
|
|
||
| function testGenericIntKey(): void { | ||
| /** @var array<int, mixed> */ | ||
| $arr = []; | ||
|
|
||
| if (!array_key_exists(0, $arr)) { | ||
|
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. should we have similar tests for
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. Added a test for I didn't add an |
||
| assertType('array<int<min, -1>|int<1, max>, mixed>', $arr); | ||
| } | ||
| } | ||
|
|
||
| function testKeyExists(): void { | ||
| /** @var array<0|string, mixed> */ | ||
| $arr = []; | ||
|
|
||
| if (!key_exists(0, $arr)) { | ||
| assertType('array<string, mixed>', $arr); | ||
| } | ||
| } | ||
|
|
||
| function testConstantArrayValueTypeGuard(): void { | ||
| $arr = ['hello', 'a' => 'world']; | ||
| assertType("array{0: 'hello', a: 'world'}", $arr); | ||
|
|
||
| if (!array_key_exists(0, $arr)) { | ||
| assertType('*NEVER*', $arr); | ||
| } | ||
| } | ||
|
|
||
| function testNonEmptyArray(): void { | ||
| /** @var non-empty-array<0|string, mixed> */ | ||
| $arr = ['test']; | ||
|
|
||
| assertType('non-empty-array<0|string, mixed>', $arr); | ||
| if (!array_key_exists(0, $arr)) { | ||
| assertType('non-empty-array<string, mixed>', $arr); | ||
| } else { | ||
| assertType('non-empty-array<0|string, mixed>&hasOffset(0)', $arr); | ||
| } | ||
| } | ||
|
|
||
| function testIssetDoesNotNarrowKeyType(): void { | ||
| /** @var array<0|string, mixed> */ | ||
| $arr = []; | ||
|
|
||
| if (!isset($arr[0])) { | ||
| // isset also checks for null, so !isset doesn't mean key doesn't exist | ||
| assertType('array<0|string, mixed>', $arr); | ||
| } else { | ||
| assertType('non-empty-array<0|string, mixed>&hasOffsetValue(0, mixed~null)', $arr); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @param array<0|string, mixed>|string $label | ||
| * @return array<string, mixed> | ||
| */ | ||
| function makeSeedFromLabel($label = []): array | ||
| { | ||
| $defaults = is_array($label) ? $label : [$label]; | ||
| assertType('array<0|string, mixed>', $defaults); | ||
|
|
||
| if (array_key_exists(0, $defaults)) { | ||
| $defaults['content'] = $defaults[0]; | ||
| unset($defaults[0]); | ||
| } | ||
| assertType('array<string, mixed>', $defaults); | ||
|
|
||
| return $defaults; | ||
| } | ||
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.
The same bug for HasOffsetValueType exists in ConstantArrayType, please fix it.
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.
Pushed. The fix applies the same value type supertype guard to
ConstantArrayType::tryRemove()that was already added toArrayType::tryRemove(). Without the guard, removingHasOffsetValueType(0, string)fromarray{0: int, a: string}would incorrectly unset offset 0 even though the value typestringdoesn't coverint. Now it returnsnull(no narrowing) in that case, matching theArrayTypebehavior.