1212use PHPStan \Type \ObjectType ;
1313use PHPStan \Type \Type ;
1414use Throwable ;
15+ use function array_diff_key ;
16+ use function array_fill_keys ;
17+ use function array_key_exists ;
1518use function array_keys ;
1619use function array_pop ;
1720use function array_reverse ;
@@ -89,6 +92,9 @@ final class VariableLivenessResolver
8992 /** @var array<string, array<string, array<string, int>>> variable name => replaced slot => marker => id of the write leaving it */
9093 private array $ markersBySlot = [];
9194
95+ /** @var array<string, array<string, int>> variable name => marker => id of the write leaving it */
96+ private array $ markersByName = [];
97+
9298 /** @var array<int, true> */
9399 private array $ overwrittenIds = [];
94100
@@ -264,9 +270,7 @@ private function liveBefore(?VariableFlow $flow, array $next, VariableFlowContex
264270 $ this ->observeOverwrite ($ item , $ next );
265271 }
266272 }
267- foreach (array_keys ($ this ->killedKeys [$ id ] ?? []) as $ key ) {
268- unset($ next [$ key ]);
269- }
273+ $ next = self ::withoutKeys ($ next , $ this ->killedKeys [$ id ] ?? []);
270274 $ next = $ this ->killOverwriteMarkers ($ flow ->write , $ next );
271275 if (isset ($ this ->overwriteMarkers [$ id ])) {
272276 $ next [$ this ->overwriteMarkers [$ id ]] = true ;
@@ -532,6 +536,14 @@ private function compileAccesses(): void
532536 $ this ->nameKeys [$ name ][$ key ] = true ;
533537 }
534538 }
539+ // the keys of every slot, shared by all writes of the whole variable
540+ // instead of copied into each of them
541+ $ allKeys = [];
542+ foreach ($ keysBySlot as $ keys ) {
543+ $ allKeys += $ keys ;
544+ }
545+ $ offsetKeys = array_diff_key ($ allKeys , $ keysBySlot ['container ' ] ?? []);
546+ $ allKills = array_fill_keys (array_keys ($ allKeys ), true );
535547 foreach ($ accesses as $ access ) {
536548 $ write = $ access ->write ;
537549 if ($ write === null ) {
@@ -540,25 +552,21 @@ private function compileAccesses(): void
540552 $ id = $ write ->getId ();
541553 $ offset = $ write ->getOffset ();
542554 if ($ write ->isOffsetWrite () && $ offset !== null ) {
543- $ slot = self ::offsetKey ($ offset );
544- $ selectedKeys = [ $ slot => $ keysBySlot [ $ slot ] ?? []] ;
555+ $ keys = $ keysBySlot [ self ::offsetKey ($ offset )] ?? [] ;
556+ $ kills = $ write -> replacesOffset () ;
545557 } else {
546- $ selectedKeys = $ keysBySlot ;
547- if ($ write ->isOffsetWrite ()) {
548- unset($ selectedKeys ['container ' ]);
549- }
558+ $ keys = $ write ->isOffsetWrite () ? $ offsetKeys : $ allKeys ;
559+ $ kills = !$ write ->isOffsetWrite ();
550560 }
551- $ kills = !$ write ->isOffsetWrite () || ($ offset !== null && $ write ->replacesOffset ());
552- foreach ($ selectedKeys as $ keys ) {
553- foreach ($ keys as $ key => $ targetId ) {
554- $ this ->observedKeys [$ id ][$ key ] = $ targetId ;
555- if (!$ kills ) {
556- continue ;
557- }
558-
559- $ this ->killedKeys [$ id ][$ key ] = true ;
560- }
561+ if ($ keys === []) {
562+ continue ;
561563 }
564+ $ this ->observedKeys [$ id ] = isset ($ this ->observedKeys [$ id ]) ? $ this ->observedKeys [$ id ] + $ keys : $ keys ;
565+ if (!$ kills ) {
566+ continue ;
567+ }
568+ $ killedKeys = $ keys === $ allKeys ? $ allKills : array_fill_keys (array_keys ($ keys ), true );
569+ $ this ->killedKeys [$ id ] = isset ($ this ->killedKeys [$ id ]) ? $ this ->killedKeys [$ id ] + $ killedKeys : $ killedKeys ;
562570 }
563571 $ this ->compileOverwrites ($ name , $ accesses );
564572 }
@@ -597,6 +605,11 @@ private function compileOverwrites(string $name, array $accesses): void
597605 }
598606
599607 $ this ->markersBySlot [$ name ] = $ markersBySlot ;
608+ $ markersOfName = [];
609+ foreach ($ markersBySlot as $ markers ) {
610+ $ markersOfName += $ markers ;
611+ }
612+ $ this ->markersByName [$ name ] = $ markersOfName ;
600613 }
601614
602615 /** The slot a write replaces: '' for the whole variable, null when it keeps the old value. */
@@ -616,7 +629,19 @@ private static function replacedSlot(VariableWrite $write): ?string
616629 /** @param array<string, true> $next */
617630 private function observeWrite (int $ id , array $ next ): void
618631 {
619- foreach ($ this ->observedKeys [$ id ] ?? [] as $ key => $ targetId ) {
632+ $ observedKeys = $ this ->observedKeys [$ id ] ?? [];
633+ // walk the smaller of the two sets
634+ if (count ($ observedKeys ) > count ($ next )) {
635+ $ live = [];
636+ foreach (array_keys ($ next ) as $ key ) {
637+ if (!array_key_exists ($ key , $ observedKeys )) {
638+ continue ;
639+ }
640+ $ live [$ key ] = $ observedKeys [$ key ];
641+ }
642+ $ observedKeys = $ live ;
643+ }
644+ foreach ($ observedKeys as $ key => $ targetId ) {
620645 if (!isset ($ next [$ key ])) {
621646 continue ;
622647 }
@@ -689,23 +714,34 @@ private function killOverwriteMarkers(VariableWrite $write, array $next): array
689714 if ($ slot === null ) {
690715 return $ next ;
691716 }
692- foreach ($ markersBySlot as $ markerSlot => $ markers ) {
693- if ($ slot !== '' && $ markerSlot !== $ slot ) {
694- continue ;
717+ if ($ slot === '' ) {
718+ return self ::withoutKeys ($ next , $ this ->markersByName [$ write ->getVariableName ()]);
719+ }
720+
721+ return self ::withoutKeys ($ next , $ markersBySlot [$ slot ] ?? []);
722+ }
723+
724+ /**
725+ * Walks the smaller of the two sets.
726+ *
727+ * @param array<string, true> $next
728+ * @param array<string, mixed> $keys
729+ * @return array<string, true>
730+ */
731+ private static function withoutKeys (array $ next , array $ keys ): array
732+ {
733+ if (count ($ keys ) <= count ($ next )) {
734+ foreach (array_keys ($ keys ) as $ key ) {
735+ unset($ next [$ key ]);
695736 }
696- // walk the smaller of the two sets
697- if ( count ( $ markers ) <= count ( $ next)) {
698- foreach ( array_keys ( $ markers ) as $ marker ) {
699- unset( $ next[ $ marker ]);
700- }
737+
738+ return $ next;
739+ }
740+ foreach ( array_keys ( $ next) as $ key ) {
741+ if (! array_key_exists ( $ key , $ keys )) {
701742 continue ;
702743 }
703- foreach (array_keys ($ next ) as $ key ) {
704- if (!isset ($ markers [$ key ])) {
705- continue ;
706- }
707- unset($ next [$ key ]);
708- }
744+ unset($ next [$ key ]);
709745 }
710746
711747 return $ next ;
0 commit comments