Initial implementation of unsealed array shapes#5501
Initial implementation of unsealed array shapes#5501ondrejmirtes wants to merge 34 commits into2.2.xfrom
Conversation
|
You've opened the pull request against the latest branch 2.2.x. PHPStan 2.2 is not going to be released for months. If your code is relevant on 2.1.x and you want it to be released sooner, please rebase your pull request and change its target to 2.1.x. |
|
Could you explain why unsealed arrays are "flawed"? |
|
@mnapoli There are two meanings of "flawed" I'm referring to.
So this PR is trying to address these problems. With bleeding edge enabled, |
|
Also, a big advantage of these changes is that array shape intersections are now possible! |
|
This can also be incredibly helpful with sealed arrays when they are defined elsewhere. Imagine, you have |
|
Thanks for the details, I see! |
a61b016 to
f61eb22
Compare
Array shapes like `array{a: int}` in PHPDocs are only sealed in Bleeding Edge.
Without Bleeding edge, the goal is to match the current flawed behaviour
as close as possible.
`TypeCombinator::intersect` rebuilds the constant-array side from scratch
via `ConstantArrayTypeBuilder::createEmpty()` whenever the other side is
a non-constant `ArrayType` (or when the maybe-unsealed branch fires). The
fresh builder is sealed, so `array{k: int, ...} & array<…>` silently
collapsed to a sealed `array{k: int}` — losing the openness the user
explicitly wrote in the source shape.
When the source `ConstantArrayType` is unsealed, copy its unsealed extras
onto the new builder, intersecting key/value with the other side's
iterable key/value so the open part keeps both sides' refinements. If
either side of the intersected extras becomes `never`, leave the new
shape sealed.
Update the bug-3931 fixture and two `TypeCombinatorTest` data sets to
reflect the now-preserved unsealed marker on the result.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bug-7963: loosen `@phpstan-return` to `array<int, non-empty-list<mixed>>` to match the actual heterogeneous list-of-lists return shape — the prior shape required positional `string`/`array<string, mixed>` types that no union member satisfies. bug-13978: PHPStan checks `@param-out` at every mutation, so the intermediate state with both `key1` and `key2` (between `$item['key2'] =` and `unset($item['key1'])`) needs to be in the union too. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The two-stage collapse merged from 2.1.x preserves the per-position record shape on unsealed too (the unsealed-types passes in reduceArrays absorb same-signature variants before the list-collapse, and the list-collapse now skips when every variant shares one signature). The earlier "Fix tests: bug-7963, bug-13978" commit's loosening of this @phpstan-return is therefore obsolete on unsealed — revert that one part to match the sealed form already on 2.2.x. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…MissingValueTypehint
Array shapes like
array{a: int}in PHPDocs are only sealed in Bleeding Edge.Without Bleeding edge, the goal is to match the current flawed behaviour as close as possible.
Closes phpstan/phpstan#13565
Closes phpstan/phpstan#8438
Closes phpstan/phpstan#11494
Closes phpstan/phpstan#12110
Closes phpstan/phpstan#14032