From 1872fef5c007dd62deb4622ab38d51dfbff5ca6f Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sat, 24 Oct 2026 04:23:37 +0800 Subject: [PATCH] fix(javascript): retain activation order for captured scope exits --- .../javascriptSemanticMutationInitializers.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/domain/javascript/javascriptSemanticMutationInitializers.ts b/src/domain/javascript/javascriptSemanticMutationInitializers.ts index 89b4a5e352..61f70b1f8c 100644 --- a/src/domain/javascript/javascriptSemanticMutationInitializers.ts +++ b/src/domain/javascript/javascriptSemanticMutationInitializers.ts @@ -1075,8 +1075,6 @@ export const semanticEscapeFollowsCapture = ( !semanticBindingPrecedesCapture(binding, capture, parents) ) return false; - if (scopeExitOwners.has(escape)) - return semanticEffectFollowsReference(capture, escape, parents); // A literal allocates here. Aliases are evaluated at the same capture point, // so an outer/persistent origin still receives all of its own escape effects. const origin = unwrapJavaScriptExpression(initializer.node).node; @@ -1089,6 +1087,20 @@ export const semanticEscapeFollowsCapture = ( ) return false; const capturePosition = semanticCapturePosition(capture, parents); + const exit = scopeExitOwners.get(escape); + if (exit !== undefined) { + // Unlike a source-global ordering proof, this binding was initialized in + // the captured body. Its later sibling scope exits follow this activation. + const exitPosition = t.isStatement(exit) + ? statementPosition(exit, parents) + : null; + return ( + withinScopeExit(capture, exit, parents) || + (capturePosition !== null && + exitPosition !== null && + positionPrecedes(capturePosition, exitPosition)) + ); + } const escapePosition = directMutationPosition(escape, parents); return ( capturePosition !== null &&