@@ -476,6 +476,59 @@ class ScopeOps
476476 return zv::Val ();
477477 }
478478
479+ /* A guard is only ever consumed paired with a target: a *different* key
480+ * in the first target loop below, any key in the second one. Deriving a
481+ * guard costs a TypeCombinator::remove() of the two branch types - the
482+ * most expensive step of the merge - so settle which keys can be targets
483+ * with the cheap checks first, bail out when none can, and skip the
484+ * subtraction for a guard no target can pair with. The first target loop
485+ * reuses this set instead of repeating the checks. Mirrors the twin. */
486+ zv::ScratchTable targets (8 );
487+ bool hasUndefinedTarget = false ;
488+ for (auto diffEntry : differingKeys) {
489+ zend_string *key = diffEntry.stringKeyOrNull ();
490+ zend_ulong idx = diffEntry.indexKey ();
491+
492+ zval *ourSlot = pt_ht_find (ours.table (), key, idx);
493+ zval *mergedSlot = pt_ht_find (merged.table (), key, idx);
494+ if (ourSlot == NULL ) {
495+ if (mergedSlot != NULL ) {
496+ zv::Ref mergedHolder = zv::Ref (mergedSlot).deref ();
497+ if (UNEXPECTED (!pt_check_holder (mergedHolder.raw ()))) {
498+ return zv::Val ();
499+ }
500+ if (!instanceof_function (holderExpr (mergedHolder)->ce , virtualNodeCe)) {
501+ hasUndefinedTarget = true ;
502+ }
503+ }
504+ continue ;
505+ }
506+ zv::Ref holder = zv::Ref (ourSlot).deref ();
507+ if (UNEXPECTED (!pt_check_holder (holder.raw ()))) {
508+ return zv::Val ();
509+ }
510+ if (instanceof_function (holderExpr (holder)->ce , virtualNodeCe)) {
511+ continue ;
512+ }
513+ if (mergedSlot != NULL ) {
514+ zv::Ref mergedHolder = zv::Ref (mergedSlot).deref ();
515+ bool equal;
516+ if (UNEXPECTED (!pt_holder_equals (mergedHolder.raw (), holder.raw (), &equal))) {
517+ return zv::Val ();
518+ }
519+ if (equal) {
520+ continue ;
521+ }
522+ }
523+ zval targetTrue;
524+ ZVAL_TRUE (&targetTrue);
525+ pt_ht_update (targets.table (), key, idx, &targetTrue);
526+ }
527+ if (!hasUndefinedTarget && targets.size () == 0 ) {
528+ return zv::Arr::copyOfTable (conditional.table ());
529+ }
530+ bool onlySelfIsTarget = !hasUndefinedTarget && targets.size () == 1 ;
531+
479532 zv::ScratchTable guardsToExclude (8 );
480533 zv::ScratchTable typeGuards (8 );
481534 /* owns the remainder-typed holders created below; the scratch table
@@ -587,6 +640,12 @@ class ScopeOps
587640 continue ;
588641 }
589642
643+ if (onlySelfIsTarget && pt_ht_exists (targets.table (), key, idx)) {
644+ /* the sole target is this very key, which the target loop unsets
645+ * from its own guard set - no pairing can use this guard */
646+ continue ;
647+ }
648+
590649 /* the branch set difference — see the twin for why an unchanged
591650 * remainder falls back to the merged-type comparison */
592651 zv::Val remainder = typeCombinatorRemove (holderType (holder), holderType (theirHolder));
@@ -641,32 +700,18 @@ class ScopeOps
641700 zv::ScratchTable guardIsConstantArrayCache (8 );
642701 zv::Arr result; /* stays UNDEF until the first append duplicates the input */
643702
644- /* main loop: pair non-merged expressions with guards */
645- for (auto diffEntry : differingKeys) {
646- zend_string *key = diffEntry.stringKeyOrNull ();
647- zend_ulong idx = diffEntry.indexKey ();
703+ /* main loop: pair non-merged expressions with guards - the settled
704+ * target set already carries the cheap checks */
705+ for (auto targetEntry : zv::TableRef (targets.table ())) {
706+ zend_string *key = targetEntry.stringKeyOrNull ();
707+ zend_ulong idx = targetEntry.indexKey ();
648708
649709 zval *ourSlot = pt_ht_find (ours.table (), key, idx);
650- if (ourSlot == NULL ) {
710+ if (UNEXPECTED ( ourSlot == NULL ) ) {
651711 continue ;
652712 }
653713 zv::Ref holder = zv::Ref (ourSlot).deref ();
654714
655- if (instanceof_function (holderExpr (holder)->ce , virtualNodeCe)) {
656- continue ;
657- }
658- zval *mergedSlot = pt_ht_find (merged.table (), key, idx);
659- if (mergedSlot != NULL ) {
660- zv::Ref mergedHolder = zv::Ref (mergedSlot).deref ();
661- bool equal;
662- if (UNEXPECTED (!pt_holder_equals (mergedHolder.raw (), holder.raw (), &equal))) {
663- return zv::Val ();
664- }
665- if (equal) {
666- continue ;
667- }
668- }
669-
670715 bool hasSelfGuard = pt_ht_exists (typeGuards.table (), key, idx);
671716 if (typeGuards.size () - (hasSelfGuard ? 1 : 0 ) == 0 ) {
672717 continue ;
0 commit comments