Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0e59b27
Read and replace parsed array reviver accessors (#3562)
Nov 8, 2026
286ee18
Keep the value supplied to the findLast predicate (#3568)
Nov 8, 2026
5389ec0
Preserve undefined class fields in JSON revivers (#3570)
Nov 8, 2026
599f528
Convert interpreter enum indices to property keys (#3580)
Nov 8, 2026
8d8f378
Respect parsed array length when deleting by key (#3584)
Nov 8, 2026
50fb2b2
Apply primitive coercion before interpreter comparisons (#3591)
Nov 8, 2026
e85da5a
Validate path references independently of import order (#3593)
Nov 8, 2026
f17b6d4
Continue enum sequences after numeric constants (#3572)
Nov 8, 2026
212ea96
Resolve earlier members during interpreted enum initialization (#3563)
Nov 8, 2026
738defe
Erase ordinary ambient enum declarations from both runtimes (#3589)
Nov 8, 2026
6c6acb9
Resolve checker enum aliases in declaration order (#3573)
Nov 8, 2026
a55a9a7
Validate numeric writes through Array subclasses (#3579)
Nov 8, 2026
aa231d0
Preserve opaque enum member identity in compatibility (#3574)
Nov 8, 2026
37813cf
Refresh class snapshots after deferred field inference (#3592)
Nov 8, 2026
7a0cdd4
Reinfer method returns after getter-dependent fields complete (#3571)
Nov 8, 2026
77a7ea8
Report eager reads of later class fields (#3581)
Nov 8, 2026
1cae4bb
Expand spread arguments in compiled console output (Fixes #3567)
Nov 8, 2026
4b22bfe
Spill direct Array arguments before constructing the array (Fixes #3578)
Nov 8, 2026
22b3f9f
Adapt lexical super in nominal derived declarations (Fixes #3569)
Nov 8, 2026
775a127
Bind nested generator receivers using lexical strictness (Fixes #3566)
Nov 8, 2026
fb8fa9d
Use the guest async iterator protocol inside compiled async generator…
Nov 8, 2026
2ceb877
Close compiled async generator iterators on abrupt loop body completi…
Nov 8, 2026
903e5f0
Route synchronous binary and call errors through async generator hand…
Nov 8, 2026
2663f53
Protect callee on interpreted unmapped arguments (#3586)
Nov 8, 2026
0768e63
Read arguments length through its descriptor during iteration (#3582)
Nov 8, 2026
bc7b95f
Keep arguments iterator indices in the ToLength range (#3583)
Nov 8, 2026
8709b5d
Honor own arguments length descriptors on assignment (#3585)
Nov 8, 2026
e06cc37
Keep borrowed arguments iterators lazy (#3587)
Nov 8, 2026
92f4e17
Honor strict directives when creating async expression arguments (#3590)
Nov 8, 2026
51fecc2
Store compiled arguments length as an ordinary deletable property (#3…
Nov 8, 2026
d336a06
Expose the invoked function on mapped arguments (#3588)
Nov 8, 2026
41401a0
Fix runtime emitter metadata and override CI checks
Nov 8, 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
130 changes: 1 addition & 129 deletions src/SharpTS/Compilation/AsyncFunctionMoveNextEmitter.ForAwait.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ protected void EmitForAwaitOf(Stmt.ForOf f, IReadOnlyList<string>? labelNames)
var varField = AsyncBuilder.GetVariableField(varName);

var iterableLocal = IL.DeclareLocal(Types.Object);
var asyncIterFnLocal = IL.DeclareLocal(Types.Object);

// The iterator and its protocol kind must survive the per-iteration suspensions (a MoveNext
// re-entry wipes IL locals), so store them in state-machine fields. Unique per loop so nested /
Expand All @@ -64,49 +63,7 @@ void EmitPrelude()
EnsureBoxed();
IL.Emit(OpCodes.Stloc, iterableLocal);

IL.Emit(OpCodes.Ldloc, iterableLocal);
IL.Emit(OpCodes.Ldsfld, Ctx.Runtime!.Symbols.AsyncIterator);
IL.Emit(OpCodes.Call, Ctx.Runtime.IteratorProtocol.Function);
IL.Emit(OpCodes.Stloc, asyncIterFnLocal);

var customSetup = IL.DefineLabel();
var adaptSyncIteratorLabel = IL.DefineLabel();
var afterSetup = IL.DefineLabel();
IL.Emit(OpCodes.Ldloc, asyncIterFnLocal);
IL.Emit(OpCodes.Brfalse, adaptSyncIteratorLabel);
IL.Emit(OpCodes.Ldloc, asyncIterFnLocal);
IL.Emit(OpCodes.Isinst, Ctx.Runtime.Sentinels.UndefinedType);
IL.Emit(OpCodes.Brfalse, customSetup);

// No async iterator: adapt any synchronous iterable per
// CreateAsyncFromSyncIterator semantics (#1287).
IL.MarkLabel(adaptSyncIteratorLabel);
IL.Emit(OpCodes.Ldarg_0);
IL.Emit(OpCodes.Ldloc, iterableLocal);
IL.Emit(OpCodes.Call, Ctx.Runtime.RequireAsyncGenerators().RequireFromSync().Adapt);
IL.Emit(OpCodes.Stfld, iteratorField);
IL.Emit(OpCodes.Ldarg_0);
IL.Emit(OpCodes.Ldc_I4_0);
IL.Emit(OpCodes.Stfld, isCustomField);
IL.Emit(OpCodes.Br, afterSetup);

// iterator = iterable[Symbol.asyncIterator](); isCustom = true
IL.MarkLabel(customSetup);
var iteratorTemp = IL.DeclareLocal(Types.Object);
IL.Emit(OpCodes.Ldloc, iterableLocal);
IL.Emit(OpCodes.Ldloc, asyncIterFnLocal);
IL.Emit(OpCodes.Ldc_I4_0);
IL.Emit(OpCodes.Newarr, Types.Object);
IL.Emit(OpCodes.Call, Ctx.Runtime.Invocation.Method);
IL.Emit(OpCodes.Stloc, iteratorTemp);
IL.Emit(OpCodes.Ldarg_0);
IL.Emit(OpCodes.Ldloc, iteratorTemp);
IL.Emit(OpCodes.Stfld, iteratorField);
IL.Emit(OpCodes.Ldarg_0);
IL.Emit(OpCodes.Ldc_I4_1);
IL.Emit(OpCodes.Stfld, isCustomField);

IL.MarkLabel(afterSetup);
EmitResolveAsyncIterator(iterableLocal, iteratorField, isCustomField);
}

// The prelude is await-free unless the iterable expression itself awaits; only then can it not
Expand Down Expand Up @@ -468,89 +425,4 @@ private static bool ContainsEscapingBreakOrContinue2(List<Stmt> statements, bool
private void EmitAsyncStep(LocalBuilder resultLocal, FieldBuilder iteratorField, FieldBuilder isCustomField, bool isReturn)
=> EmitGuardedSyncSegment(() => EmitProduceAsyncStep(resultLocal, iteratorField, isCustomField, isReturn));

/// <summary>
/// Stores into <paramref name="resultLocal"/> the result of one async-iterator protocol call —
/// <c>iterator.next()</c> when <paramref name="isReturn"/> is false, else <c>iterator.return()</c> —
/// selecting the call by the protocol kind in <paramref name="isCustomField"/>. The value (a
/// Task&lt;object&gt;, $Promise, or plain value) is later coerced and awaited by
/// <see cref="EmitAwaitFromValueOnStack"/>. A custom iterator with no <c>return</c> method stores
/// <c>undefined</c> (awaited as an already-resolved value), so the reserved return-await state is
/// still consumed and its resume label stays marked.
/// </summary>
private void EmitProduceAsyncStep(LocalBuilder resultLocal, FieldBuilder iteratorField, FieldBuilder isCustomField, bool isReturn)
{
var stepLocal = resultLocal;
var customLabel = IL.DefineLabel();
var doneLabel = IL.DefineLabel();

IL.Emit(OpCodes.Ldarg_0);
IL.Emit(OpCodes.Ldfld, isCustomField);
IL.Emit(OpCodes.Brtrue, customLabel);

// ----- $IAsyncGenerator path: invoke the interface method (returns Task<object>) -----
IL.Emit(OpCodes.Ldarg_0);
IL.Emit(OpCodes.Ldfld, iteratorField);
IL.Emit(OpCodes.Castclass, Ctx.Runtime!.RequireAsyncGenerators().Type);
if (isReturn)
{
IL.Emit(OpCodes.Ldnull);
IL.Emit(OpCodes.Callvirt, Ctx.Runtime.RequireAsyncGenerators().Return);
}
else
{
// for-await-of never sends a value; pass undefined so the yield expression sees undefined
// (not null) if the consumer somehow observes it (#473).
IL.Emit(OpCodes.Ldsfld, Ctx.Runtime.Sentinels.UndefinedInstance);
IL.Emit(OpCodes.Callvirt, Ctx.Runtime.RequireAsyncGenerators().Next);
}
IL.Emit(OpCodes.Stloc, stepLocal);
IL.Emit(OpCodes.Br, doneLabel);

// ----- custom Symbol.asyncIterator path -----
IL.MarkLabel(customLabel);
if (isReturn)
{
// fn = GetProperty(iterator, "return"); absent/undefined → no-op close (push undefined).
IL.Emit(OpCodes.Ldarg_0);
IL.Emit(OpCodes.Ldfld, iteratorField);
IL.Emit(OpCodes.Ldstr, "return");
IL.Emit(OpCodes.Call, Ctx.Runtime.ObjectRead.Property);
var fnLocal = IL.DeclareLocal(Types.Object);
IL.Emit(OpCodes.Stloc, fnLocal);

var noFnLabel = IL.DefineLabel();
var haveFnLabel = IL.DefineLabel();
IL.Emit(OpCodes.Ldloc, fnLocal);
IL.Emit(OpCodes.Brfalse, noFnLabel);
IL.Emit(OpCodes.Ldloc, fnLocal);
IL.Emit(OpCodes.Isinst, Ctx.Runtime.Sentinels.UndefinedType);
IL.Emit(OpCodes.Brtrue, noFnLabel);

// InvokeMethodValue(iterator, fn, [])
IL.Emit(OpCodes.Ldarg_0);
IL.Emit(OpCodes.Ldfld, iteratorField);
IL.Emit(OpCodes.Ldloc, fnLocal);
IL.Emit(OpCodes.Ldc_I4_0);
IL.Emit(OpCodes.Newarr, Types.Object);
IL.Emit(OpCodes.Call, Ctx.Runtime.Invocation.Method);
IL.Emit(OpCodes.Stloc, stepLocal);
IL.Emit(OpCodes.Br, haveFnLabel);

IL.MarkLabel(noFnLabel);
IL.Emit(OpCodes.Ldsfld, Ctx.Runtime.Sentinels.UndefinedInstance);
IL.Emit(OpCodes.Stloc, stepLocal);
IL.MarkLabel(haveFnLabel);
}
else
{
IL.Emit(OpCodes.Ldarg_0);
IL.Emit(OpCodes.Ldfld, iteratorField);
IL.Emit(OpCodes.Call, Ctx.Runtime.IteratorProtocol.InvokeNext);
IL.Emit(OpCodes.Stloc, stepLocal);
}

IL.MarkLabel(doneLabel);
// Result is left in resultLocal (== stepLocal); the caller loads and awaits it.
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,79 @@ namespace SharpTS.Compilation;

public partial class AsyncGeneratorMoveNextEmitter
{
// EmitExpression dispatch is inherited from ExpressionEmitterBase
protected override void EmitBinary(Expr.Binary binary)
{
if (_protectedRegionDepth != 0 || (_currentTryExceptionLocal is null && !_inHandlerBody)
|| !ExprContainsSuspension(binary))
{
base.EmitBinary(binary);
return;
}

// Protect each stack-empty synchronous segment, leaving nested
// suspension resume labels outside the protected regions.
var left = SpillAwaitSafeArgument(binary.Left);
var right = SpillAwaitSafeArgument(binary.Right);
EmitGuardedExpressionResult(() => EmitSpilledBinaryOperation(binary, left, right));
}

protected override void EmitPreparedStaticCall(Expr.Call call, MethodInfo target)
{
if (!NeedsExpressionCapture(call))
{
base.EmitPreparedStaticCall(call, target);
return;
}

// Move the prepared typed arguments off the stack before entering the
// exception region. This also covers direct rest and arguments calls.
var arguments = target.GetParameters()
.Select(parameter => _il.DeclareLocal(parameter.ParameterType)).ToArray();
for (int i = arguments.Length - 1; i >= 0; i--)
_il.Emit(OpCodes.Stloc, arguments[i]);
EmitGuardedExpressionResult(() =>
{
foreach (var argument in arguments)
_il.Emit(OpCodes.Ldloc, argument);
base.EmitPreparedStaticCall(call, target);
});
}

protected override void EmitCallCompletion(Expr.Call call, Action emit)
{
if (NeedsExpressionCapture(call))
EmitGuardedExpressionResult(emit);
else
emit();
}

protected override LocalBuilder SpillAwaitSafeArgument(Expr argument, Action? emitBoxed = null)
{
if (!CanCaptureExpressionException || ExprContainsSuspension(argument))
return base.SpillAwaitSafeArgument(argument, emitBoxed);

EmitGuardedExpressionResult(emitBoxed ?? (() => EmitExpression(argument)));
return _helpers.SpillStoreObject();
}

private bool CanCaptureExpressionException
=> _protectedRegionDepth == 0 && (_currentTryExceptionLocal != null || _inHandlerBody);

private bool NeedsExpressionCapture(Expr expression)
=> CanCaptureExpressionException && ExprContainsSuspension(expression);

private void EmitGuardedExpressionResult(Action emit)
{
var result = _il.DeclareLocal(_types.Object);
EmitCaptureTryOperation(() =>
{
emit();
EnsureBoxed();
_il.Emit(OpCodes.Stloc, result);
});
_il.Emit(OpCodes.Ldloc, result);
SetStackUnknown();
}

#region Yield Expressions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,30 +354,10 @@ private void EmitTryCatchWithSuspensions(Stmt.TryCatch t)
_exitScopes.Add(frame);
}

// Throws in the try body are captured by their sync segments, not routed.
bool previousInHandler = _inHandlerBody;
_inHandlerBody = false;
// Carry this try's capture target down to the suspension points emitted at the top level, so a
// rejected `await` inside the try routes its exception into the same flag + catch/finally
// instead of escaping MoveNextAsync (#617), setting the present flag so a null rejection still
// engages the catch (#628). After the body these revert to the enclosing try, which a throw
// escaping this try's catch/finally must route to (#632). Saved/restored for correct nesting.
var previousTryExceptionLocal = _currentTryExceptionLocal;
var previousTryExceptionPresentLocal = _currentTryExceptionPresentLocal;
var previousTryCleanupLabel = _currentTryCleanupLabel;
var previousTryScopeDepth = _currentTryScopeDepth;
int previousTryFinallyBodyDepth = _currentTryFinallyBodyDepth;
_currentTryExceptionLocal = caughtExceptionLocal;
_currentTryExceptionPresentLocal = exceptionPresentLocal;
_currentTryCleanupLabel = afterTryBodyLabel;
_currentTryScopeDepth = _exitScopes.Count;
_currentTryFinallyBodyDepth = _finallyBodyDepth;
EmitScopedTryBodyWithSuspensions(t.TryBlock, caughtExceptionLocal, exceptionPresentLocal, afterTryBodyLabel);
_currentTryExceptionLocal = previousTryExceptionLocal;
_currentTryExceptionPresentLocal = previousTryExceptionPresentLocal;
_currentTryCleanupLabel = previousTryCleanupLabel;
_currentTryScopeDepth = previousTryScopeDepth;
_currentTryFinallyBodyDepth = previousTryFinallyBodyDepth;
WithAsyncTryCapture(caughtExceptionLocal, exceptionPresentLocal, afterTryBodyLabel,
() => EmitScopedTryBodyWithSuspensions(t.TryBlock, caughtExceptionLocal, exceptionPresentLocal, afterTryBodyLabel));
_inHandlerBody = previousInHandler;

_il.MarkLabel(afterTryBodyLabel);
Expand Down Expand Up @@ -495,6 +475,28 @@ private void EmitTryCatchWithSuspensions(Stmt.TryCatch t)
_il.MarkLabel(afterTryCatchLabel);
}

/// <summary>Routes errors from a resumable region to its local handler.</summary>
private void WithAsyncTryCapture(LocalBuilder caughtExceptionLocal, LocalBuilder exceptionPresentLocal,
Label failureLabel, Action emit)
{
var previousTryExceptionLocal = _currentTryExceptionLocal;
var previousTryExceptionPresentLocal = _currentTryExceptionPresentLocal;
var previousTryCleanupLabel = _currentTryCleanupLabel;
var previousTryScopeDepth = _currentTryScopeDepth;
int previousTryFinallyBodyDepth = _currentTryFinallyBodyDepth;
_currentTryExceptionLocal = caughtExceptionLocal;
_currentTryExceptionPresentLocal = exceptionPresentLocal;
_currentTryCleanupLabel = failureLabel;
_currentTryScopeDepth = _exitScopes.Count;
_currentTryFinallyBodyDepth = _finallyBodyDepth;
emit();
_currentTryExceptionLocal = previousTryExceptionLocal;
_currentTryExceptionPresentLocal = previousTryExceptionPresentLocal;
_currentTryCleanupLabel = previousTryCleanupLabel;
_currentTryScopeDepth = previousTryScopeDepth;
_currentTryFinallyBodyDepth = previousTryFinallyBodyDepth;
}

/// <summary>
/// Walks the try body, wrapping runs of plain statements in mini IL try/catch blocks while
/// emitting suspension points and non-local exits (return/break/continue) at the top level.
Expand Down
Loading
Loading