Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
3e258a5
Initial implementation of unsealed array shapes
ondrejmirtes Apr 17, 2026
f4a2f61
So intersecting of constant arrays works
ondrejmirtes Apr 22, 2026
183febb
Dedup code
ondrejmirtes Apr 22, 2026
b2c4d83
intersecting improvement
ondrejmirtes Apr 22, 2026
f389a5e
isSuperTypeOf
ondrejmirtes Apr 22, 2026
bb4ea57
union improvement
ondrejmirtes Apr 22, 2026
a394713
Regression tests
ondrejmirtes Apr 23, 2026
e22e420
Optimization
ondrejmirtes Apr 23, 2026
f2ddada
Remove unrelated tip
ondrejmirtes Apr 27, 2026
d2a82f6
Preserve unsealed extras when intersecting array{...} with another array
ondrejmirtes Apr 27, 2026
6b57c1e
Tip in assertNoErrors
ondrejmirtes Apr 27, 2026
77dcce4
Re-tighten bug-7963 @phpstan-return on unsealed
ondrejmirtes Apr 27, 2026
219b075
Update levels tests
ondrejmirtes Apr 28, 2026
71b347e
Refactor - allow other message to be passed from getIterableTypesWith…
ondrejmirtes Apr 28, 2026
fab9c53
Better "missing iterable value type" for unsealed types
ondrejmirtes Apr 28, 2026
9479027
Generics
ondrejmirtes Apr 28, 2026
c685b9c
Preserve array shape and make it unsealed when non-constant key is as…
ondrejmirtes Apr 29, 2026
58e0f05
Correctly generalize unsealed array shapes
ondrejmirtes Apr 30, 2026
b4700ee
Fix array_search
ondrejmirtes Apr 30, 2026
99156bf
Unsealed types awareness in more methods
ondrejmirtes Apr 28, 2026
8b2a6c6
Fix byref
ondrejmirtes May 2, 2026
612ae50
Awareness of unsealed array shape when unrolling foreach with constan…
ondrejmirtes May 2, 2026
c3e0ba2
Less `instanceof NeverType` to detect sealed array
ondrejmirtes May 2, 2026
e7a8791
Consider unsealed types in hasOffsetValueType
ondrejmirtes May 5, 2026
d960da3
makeOffsetRequired
ondrejmirtes May 6, 2026
94d65a4
Make `ConstantArrayType::filterArrayRemovingFalsey()` unsealed-aware
ondrejmirtes May 7, 2026
ace369d
Make `ConstantArrayType::changeKeyCaseArray()` unsealed-aware
ondrejmirtes May 7, 2026
7baa327
Carry unsealed slot through `array_unshift` prepend branch
ondrejmirtes May 8, 2026
52021ac
Carry unsealed slot through `count()` narrowing (unbounded max)
ondrejmirtes May 8, 2026
3f41928
Carry unsealed slots through `array_merge` all-constant fast path
ondrejmirtes May 8, 2026
bf996e0
Carry unsealed slots through `array_replace` all-constant fast path
ondrejmirtes May 8, 2026
98e8699
Carry unsealed slot through `array_filter` callback path
ondrejmirtes May 8, 2026
1a8af3e
Carry unsealed slot through `array_column` constant-array path
ondrejmirtes May 8, 2026
e215c05
Carry unsealed slot through `filter_var_array` constant-input path
ondrejmirtes May 8, 2026
e7e2c3b
Make `array_unshift` produce unsealed CAT for assoc + non-constant un…
ondrejmirtes May 8, 2026
095b8ff
Make `array_merge` slow path produce unsealed CAT for non-constant un…
ondrejmirtes May 12, 2026
4fc494b
Make `array_replace` slow path produce unsealed CAT for non-constant …
ondrejmirtes May 12, 2026
4585c2d
Preserve int-keyed values from CATs in `array_merge` slow path
ondrejmirtes May 12, 2026
1f6a697
Honour unsealed shape in `ConstantArrayType::isCallable()`
ondrejmirtes May 13, 2026
71efd0d
isConstantValue
ondrejmirtes May 18, 2026
65917b6
More ConstantArrayType methods
ondrejmirtes May 19, 2026
8202c48
Cover `ConstantArrayType::getFiniteTypes()` with a data provider
ondrejmirtes May 19, 2026
ccb668b
hasTemplateOrLateResolvableType
ondrejmirtes May 19, 2026
9ee0108
Carry unsealed slot through `ConstantArrayType::flipArray()`
ondrejmirtes May 19, 2026
805bdb8
Carry unsealed slot through `ConstantArrayType::fillKeysArray()`
ondrejmirtes May 19, 2026
ffb3adf
Carry unsealed slot through `ConstantArrayType::intersectKeyArray()`
ondrejmirtes May 19, 2026
80eadc5
Carry unsealed slot through `ConstantArrayType::reverseArray()`
ondrejmirtes May 19, 2026
b2fa4e3
Visit unsealed value in `ConstantArrayType::traverseSimultaneously()`
ondrejmirtes May 19, 2026
c919101
Make `popArray` / `removeLastElements` unsealed-aware
ondrejmirtes May 19, 2026
8c89176
Carry unsealed slot through `shiftArray` / `removeFirstElements`
ondrejmirtes May 19, 2026
fd44d98
Carry unsealed slot through `getKeysArray` / `getValuesArray`
ondrejmirtes May 19, 2026
3e49a3e
Carry unsealed slot through `ConstantArrayType::sliceArray()`
ondrejmirtes May 19, 2026
6a43069
Degrade `chunkArray` on unsealed sources to the general trait path
ondrejmirtes May 19, 2026
c726c2a
Include unsealed extras in `ConstantArrayTypeBuilder` degraded-array …
ondrejmirtes May 19, 2026
ea71a35
Carry unsealed slot through `ConstantArrayType::spliceArray()`
ondrejmirtes May 19, 2026
51b7794
Cover `ConstantArrayType::generalize()` with a data provider
ondrejmirtes May 19, 2026
f9c295a
Generalize unsealed `ConstantArrayType` instead of returning it uncha…
ondrejmirtes May 19, 2026
38b3496
Broaden unsealed value in `ConstantArrayType::generalizeValues()`
ondrejmirtes May 19, 2026
82d7754
Treat the `isList`-from-shape inference in `ConstantArrayType` symmet…
ondrejmirtes May 19, 2026
741527c
Fold unsealed extras into `array_sum()` over a constant array
ondrejmirtes May 20, 2026
d67b3a8
Skip `implode()` constant-fold for unsealed constant arrays
ondrejmirtes May 20, 2026
11b6609
Range `min()` / `max()` over unsealed extras of a constant array
ondrejmirtes May 20, 2026
6988c81
Carry unsealed slots through `array_combine()` all-constant fast path
ondrejmirtes May 20, 2026
3699e95
Bail `compact()` enumeration for unsealed name arrays
ondrejmirtes May 20, 2026
e40cef4
Treat unsealed from-encoding arrays as multi-candidate in `mb_convert…
ondrejmirtes May 20, 2026
e2d4e60
Treat legacy-null and sealed-marker as equal in `ConstantArrayType::e…
ondrejmirtes May 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Carry unsealed slot through ConstantArrayType::flipArray()
`array_flip` swaps keys and values pair-by-pair. For an unsealed
source `array{a: 'foo', b: 'bar', ...<int, string>}`, the explicit
entries flip as before and the unsealed slot becomes
`<string, int>` — `unsealed[1]->toArrayKey()` lands in the new key
slot, `unsealed[0]` in the new value slot. Sealed stays sealed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
  • Loading branch information
ondrejmirtes and claude committed May 20, 2026
commit 9ee0108f819485441997364afadb60c83bda3bf8
5 changes: 5 additions & 0 deletions src/Type/Constant/ConstantArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,11 @@ public function flipArray(): Type
);
}

if ($this->isUnsealed()->yes() && $this->unsealed !== null) {
[$unsealedKey, $unsealedValue] = $this->unsealed;
$builder->makeUnsealed($unsealedValue->toArrayKey(), $unsealedKey);
}

return $builder->getArray();
}

Expand Down
24 changes: 24 additions & 0 deletions tests/PHPStan/Analyser/nsrt/unsealed-derivations.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,30 @@ public function unshiftAssocWithUnpack(array $sealed, array $unknownItems): void

}

class FlipArray
{

/**
* @param array{a: 'foo', b: 'bar', ...<int, string>} $arr
*/
public function flipPreservesUnsealed(array $arr): void
{
// `array_flip` swaps keys and values pair-by-pair, so the
// unsealed `<int, string>` becomes `<string, int>` — the value
// type passes through `toArrayKey()` to land in the new key slot.
assertType("array{foo: 'a', bar: 'b', ...<string, int>}", array_flip($arr));
}

/**
* @param array{a: 1, b: 2} $sealed
*/
public function flipSealedStaysSealed(array $sealed): void
{
assertType("array{1: 'a', 2: 'b'}", array_flip($sealed));
}

}

class CountNarrowing
{

Expand Down