- 
  Notifications
 You must be signed in to change notification settings 
- Fork 544
fix null coalesce false positive for multi-dimensional array in loop #4475
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
fix null coalesce false positive for multi-dimensional array in loop #4475
Conversation
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.
I'm ignoring this change. This test was added as a performance regression test (I'm not sure if/how that works, or why it makes assertions about the errors in the first place) and it's extremely hard to reason about the code. So I'm assuming that the reported errors don't matter.
Fundamentally, the array handling in PHPStan has a set of bugs and as a result of this PR it will have a slightly different (hopefully smaller) set of bugs. But there can definitely be cases where the new code causes a regression.
If you want to see the difference detected in the test for yourself here are the errors:
Previous:
115: Offset 'oooo_yyyy' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: float, oooo_yyyy: 0, ssss: 0, ssss_next: 0}|array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: float, oooo_yyyy: float, ssss: float, ssss_next: float}|array{oooo_rrrrr: float}.
125: Offset 'oooo_yyyy' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{oooo_rrrrr: float}.
130: Offset 'oooo_yyyy' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{oooo_rrrrr: float}.
135: Offset 'oooo_yyyy' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{oooo_rrrrr: float}.
169: Offset 'ccccc_rrrrr' might not exist on array{ccccc_rrrrr: 0|float, ccccc_yyyy: 0|float, oooo_rrrrr: 0|float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{oooo_rrrrr: float}.
170: Offset 'ccccc_yyyy' might not exist on array{ccccc_rrrrr: float, ccccc_yyyy: 0|float, oooo_rrrrr: 0|float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{oooo_rrrrr?: float, ccccc_rrrrr: float}.
174: Offset 'ccccc_yyyy' might not exist on array{ccccc_rrrrr: float, ccccc_yyyy: 0|float, oooo_rrrrr: 0|float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{oooo_rrrrr?: float, ccccc_rrrrr: float}.
178: Offset 'ccccc_yyyy' might not exist on array{ccccc_rrrrr: float, ccccc_yyyy: 0|float, oooo_rrrrr: 0|float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{oooo_rrrrr: float, ccccc_rrrrr: float}.
182: Offset 'ccccc_yyyy' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0|float, oooo_rrrrr: 0, oooo_yyyy: 0|float, ssss: 0, ssss_next: 0}|array{oooo_yyyy: float}.
184: Offset 'ccccc_yyyy' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0|float, oooo_rrrrr: 0, oooo_yyyy: 0|float, ssss: 0, ssss_next: 0}|array{oooo_yyyy: float}.
194: Offset 'ccccc_yyyy' might not exist on array{ccccc_rrrrr: 0|float, ccccc_yyyy: 0|float, oooo_rrrrr: 0|float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{oooo_rrrrr: float}.
195: Offset 'ccccc_yyyy' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0|float, oooo_rrrrr: 0, oooo_yyyy: 0|float, ssss: 0, ssss_next: 0}|array{oooo_yyyy: float}.
198: Offset 'oooo_yyyy' might not exist on array{ccccc_rrrrr: 0|float, ccccc_yyyy: 0|float, oooo_rrrrr: 0|float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{oooo_rrrrr: float}.
199: Offset 'ssss_next' might not exist on array{ccccc_rrrrr: 0|float, ccccc_yyyy: 0|float, oooo_rrrrr: 0|float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{oooo_rrrrr: float}.
203: Offset 'ccccc_yyyy' might not exist on array{ccccc_rrrrr: 0|float, ccccc_yyyy: 0|float, oooo_rrrrr: 0|float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{oooo_rrrrr: float}.
207: Offset 'oooo_yyyy' might not exist on array{ccccc_rrrrr: 0|float, ccccc_yyyy: 0|float, oooo_rrrrr: 0|float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{oooo_rrrrr: float}.
208: Offset 'ssss_next' might not exist on array{ccccc_rrrrr: 0|float, ccccc_yyyy: 0|float, oooo_rrrrr: 0|float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{oooo_rrrrr: float}.
212: Comparison operation ">" between 0 and 0 is always false.
213: Comparison operation ">" between 0 and 0 is always false.
214: Comparison operation ">" between 0 and 0 is always false.
215: Comparison operation ">" between 0 and 0 is always false.
229: Comparison operation ">" between 0 and 0 is always false.
230: Comparison operation ">" between 0 and 0 is always false.
New:
115: Offset 'oooo_yyyy' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: float, oooo_yyyy: 0, ssss: 0, ssss_next: 0}|array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: float, oooo_yyyy: float, ssss: float, ssss_next: float}|array{oooo_rrrrr: float, oooo_yyyy: float, ssss: float, ssss_next: float}|array{oooo_rrrrr: float}.
116: Offset 'ssss' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: float, oooo_yyyy: float, ssss: 0, ssss_next: 0}|array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: float, oooo_yyyy: float, ssss: float, ssss_next: float}|array{oooo_rrrrr: float, oooo_yyyy: float, ssss: float, ssss_next: float}|array{oooo_rrrrr: float, oooo_yyyy: float}.
117: Offset 'ssss_next' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: float, oooo_yyyy: float, ssss: float, ssss_next: 0|float}|array{oooo_rrrrr: float, oooo_yyyy: float, ssss: float, ssss_next?: float}.
125: Offset 'oooo_yyyy' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: float, oooo_yyyy?: 0|float, ssss?: 0|float, ssss_next?: 0|float}|array{oooo_rrrrr: float, oooo_yyyy?: float, ssss?: float, ssss_next?: float}.
126: Offset 'ssss' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: 0|float, oooo_yyyy: float, ssss?: 0|float, ssss_next?: 0|float}|array{oooo_rrrrr: float, oooo_yyyy: float, ssss?: float, ssss_next?: float}.
127: Offset 'ssss_next' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: 0|float, oooo_yyyy?: 0|float, ssss: float, ssss_next?: 0|float}|array{oooo_rrrrr: float, oooo_yyyy?: float, ssss: float, ssss_next?: float}.
130: Offset 'oooo_yyyy' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: float, oooo_yyyy?: 0|float, ssss?: 0|float, ssss_next?: 0|float}|array{oooo_rrrrr: float, oooo_yyyy?: float, ssss?: float, ssss_next?: float}.
131: Offset 'ssss' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: 0|float, oooo_yyyy: float, ssss?: 0|float, ssss_next?: 0|float}|array{oooo_rrrrr: float, oooo_yyyy: float, ssss?: float, ssss_next?: float}.
132: Offset 'ssss_next' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: 0|float, oooo_yyyy?: 0|float, ssss: float, ssss_next?: 0|float}|array{oooo_rrrrr: float, oooo_yyyy?: float, ssss: float, ssss_next?: float}.
135: Offset 'oooo_yyyy' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: float, oooo_yyyy?: 0|float, ssss?: 0|float, ssss_next?: 0|float}|array{oooo_rrrrr: float, oooo_yyyy?: float, ssss?: float, ssss_next?: float}.
136: Offset 'ssss' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: 0|float, oooo_yyyy: float, ssss?: 0|float, ssss_next?: 0|float}|array{oooo_rrrrr: float, oooo_yyyy: float, ssss?: float, ssss_next?: float}.
137: Offset 'ssss_next' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0, oooo_rrrrr: 0|float, oooo_yyyy?: 0|float, ssss: float, ssss_next?: 0|float}|array{oooo_rrrrr: float, oooo_yyyy?: float, ssss: float, ssss_next?: float}.
170: Offset 'ccccc_yyyy' might not exist on array{ccccc_rrrrr: float, ccccc_yyyy?: 0|float, oooo_rrrrr?: 0|float, oooo_yyyy?: 0|float, ssss?: 0|float, ssss_next?: 0|float}.
172: Offset 'ccccc_yyyy' might not exist on array{ccccc_rrrrr: float, ccccc_yyyy?: float, oooo_rrrrr: 0|float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{ccccc_rrrrr: float, ccccc_yyyy?: float}.
174: Offset 'ccccc_yyyy' might not exist on array{ccccc_rrrrr: float, ccccc_yyyy?: 0|float, oooo_rrrrr?: 0|float, oooo_yyyy?: 0|float, ssss?: 0|float, ssss_next?: 0|float}.
176: Offset 'ccccc_yyyy' might not exist on array{ccccc_rrrrr: float, ccccc_yyyy: 0|float, oooo_rrrrr: 0|float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{ccccc_rrrrr: float}.
178: Offset 'ccccc_yyyy' might not exist on array{ccccc_rrrrr: float, ccccc_yyyy: 0|float, oooo_rrrrr: 0|float, oooo_yyyy: 0|float, ssss: 0|float, ssss_next: 0|float}|array{ccccc_rrrrr: float}.
200: Offset 'oooo_yyyy' might not exist on array{ccccc_rrrrr: 0, ccccc_yyyy: 0|float, oooo_rrrrr: 0, oooo_yyyy?: 0|float, ssss: 0, ssss_next: 0}|non-empty-array{oooo_yyyy?: float, ccccc_yyyy?: float}.
212: Comparison operation ">" between 0 and 0 is always false.
213: Comparison operation ">" between 0 and 0 is always false.
214: Comparison operation ">" between 0 and 0 is always false.
215: Comparison operation ">" between 0 and 0 is always false.
229: Comparison operation ">" between 0 and 0 is always false.
230: Comparison operation ">" between 0 and 0 is always false.
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 test in tests/PHPStan/Analyser/nsrt/ with some assertType calls would be nice. Make sure it fails first 😊
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.
Added and it does fail in 2.1.x: https://phpstan.org/r/b97ea05c-33dc-4287-8223-cd5a6369730d
There is a regression in doctrine/orm integration test. I'm not sure why it worked before, but I suspect that it was a bug, because AFAIK PHPStan isn't able to understand that class key cannot be unset by this line.
 ------ ----------------------------------------------------------------------- 
 Line src/Internal/Hydration/AbstractHydrator.php 
 ------ ----------------------------------------------------------------------- 
 358 Method 
 Doctrine\ORM\Internal\Hydration\AbstractHydrator::gatherRowData() 
 should return array{data: array<array>, newObjects?: array<array{clas 
 s: ReflectionClass, args: array, obj: object}>, scalars?: array} but 
 returns array{data: array<non-empty-array>, newObjects: array<array{c 
 lass?: mixed, args: non-empty-array, obj: mixed}>, scalars?: non-empt 
 y-array}. 
 🪪 return.type 
 💡 Offset 'newObjects' (array<array{class: ReflectionClass, args: arr 
 ay, obj: object}>) does not accept type array<array{class?: mixed, ar 
 gs: non-empty-array, obj: mixed}>: Array might not have offset 'class 
 '. 
 ------ ----------------------------------------------------------------------- 
Make sure it's not something they already have in the baseline but the message changed slightly. I suspect it might be the case of the PMMP error as well.
I did check the baseline and there is no message about the return type there. I also confirmed that the class key is not optional in 2.1.x.
The PMMP error is not related to this PR because it also happens in PR 4474.
38a7dfd
 into
 
 
 phpstan:2.1.x
 
 Thank you!
Fixes: https://phpstan.org/r/9e10fb00-443f-4c05-9506-da40bf191f8e
The bug is triggered the second time the loop is processed on line 17 of the test file in
produceArrayDimFetchAssignValueToWrite:Original
$valueToWrite:array{foo: 5, bar: 5, amount?: 0.0}$offserValueType:non-empty-array<int, array{foo: 5, bar: 5, amount: 0.0}|array{foo: 5}>The original version of
setExistingOffsetValueTypedisregards optional keys, so it results in new$valueTypeToWrite:non-empty-array<int, array{foo: 5, bar: 5, amount: 0.0}>