Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion Jint/AstExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ or NodeType.ArrayExpression
or NodeType.ObjectExpression)
{
var context = engine._activeEvaluationContext ?? new EvaluationContext(engine);
return JintExpression.Build(expression).GetValue(context);
var result = JintExpression.Build(expression).GetValue(context);

// If the expression suspended the generator (e.g., yield in computed property name),
// return the value. The caller should check ExecutionContext.Suspended.
return result;
}

return JsValue.Undefined;
Expand Down
20 changes: 20 additions & 0 deletions Jint/Native/Function/ClassDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ public JsValue BuildConstructor(EvaluationContext context, Environment env)

var target = !isStatic ? proto : F;
var element = ClassElementEvaluation(engine, target, e);

// Check for generator suspension after evaluating class element
if (engine.ExecutionContext.Suspended)
{
return JsValue.Undefined;
}

if (element is PrivateElement privateElement)
{
var container = !isStatic ? instancePrivateMethods : staticPrivateMethods;
Expand Down Expand Up @@ -258,6 +265,12 @@ private static ClassFieldDefinition ClassFieldDefinitionEvaluation(Engine engine
{
var name = fieldDefinition.GetKey(engine);

// Check for generator suspension after evaluating computed property key
if (engine.ExecutionContext.Suspended)
{
return new ClassFieldDefinition { Name = JsValue.Undefined, Initializer = null };
}

ScriptFunction? initializer = null;
if (fieldDefinition.Value is not null)
{
Expand Down Expand Up @@ -366,6 +379,13 @@ public ClassStaticBlockFunction(StaticBlock staticBlock) : base(NodeType.StaticB
var intrinsics = engine.Realm.Intrinsics;

var value = method.TryGetKey(engine);

// Check for generator suspension after evaluating computed property key
if (engine.ExecutionContext.Suspended)
{
return null;
}

var propKey = TypeConverter.ToPropertyKey(value);
var env = engine.ExecutionContext.LexicalEnvironment;
var privateEnv = engine.ExecutionContext.PrivateEnvironment;
Expand Down
115 changes: 28 additions & 87 deletions Jint/Native/Generator/GeneratorInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,11 @@ internal sealed class GeneratorInstance : ObjectInstance
internal ObjectInstance? _delegationInnerResult;

/// <summary>
/// When set, indicates that the generator should complete immediately with this value.
/// Used by yield* when the inner iterator's return method is null/undefined.
/// Signals that generator.return() was called and execution should complete
/// after running finally blocks. Aligns with spec's Return completion handling
/// in GeneratorResumeAbrupt.
/// </summary>
internal JsValue? _earlyReturnValue;

/// <summary>
/// Whether an early return was triggered (e.g., from yield* with null return method).
/// </summary>
internal bool _shouldEarlyReturn;
internal bool _returnRequested;

/// <summary>
/// The completion type used when resuming via GeneratorResumeAbrupt.
Expand All @@ -106,18 +102,11 @@ internal sealed class GeneratorInstance : ObjectInstance
internal object? _currentFinallyStatement;

/// <summary>
/// Maps for-of/for-in statement nodes to their suspended iterator state.
/// When a generator yields inside a for-of loop, the iterator is stored here
/// so it can be restored on resume instead of creating a new one.
/// </summary>
internal Dictionary<object, ForOfSuspendData>? _forOfSuspendData;

/// <summary>
/// Maps array destructuring pattern nodes to their suspended iterator state.
/// When a generator yields inside array destructuring (e.g., [x[yield]] = iterable),
/// the iterator is stored here so it can be properly closed on generator.return().
/// Unified dictionary for all suspend data (for-of loops, destructuring patterns, etc.).
/// Keys are Jint expression/statement instances (not AST nodes) to avoid collisions
/// when the same script runs on multiple Engine instances.
/// </summary>
internal Dictionary<object, DestructuringSuspendData>? _destructuringSuspendData;
internal Dictionary<object, SuspendData>? _suspendData;

public GeneratorInstance(Engine engine) : base(engine)
{
Expand Down Expand Up @@ -220,16 +209,6 @@ private ObjectInstance ResumeExecution(in ExecutionContext genContext, Evaluatio
var result = _generatorBody.Execute(context);
_engine.LeaveExecutionContext();

// Check for early return (e.g., from yield* with null return method)
if (_shouldEarlyReturn)
{
var earlyValue = _earlyReturnValue ?? JsValue.Undefined;
_shouldEarlyReturn = false;
_earlyReturnValue = null;
_generatorState = GeneratorState.Completed;
return IteratorResult.CreateValueIteratorPosition(_engine, earlyValue, done: JsBoolean.True);
}

ObjectInstance? resultValue = null;
if (result.Type == CompletionType.Normal)
{
Expand Down Expand Up @@ -285,77 +264,41 @@ private GeneratorState GeneratorValidate(JsValue? generatorBrand)
}

/// <summary>
/// Gets or creates suspend data for a for-of statement.
/// Called when a generator is about to execute a for-of loop body.
/// </summary>
internal ForOfSuspendData GetOrCreateForOfSuspendData(object statement, IteratorInstance iterator)
{
_forOfSuspendData ??= new Dictionary<object, ForOfSuspendData>();
if (!_forOfSuspendData.TryGetValue(statement, out var data))
{
data = new ForOfSuspendData { Iterator = iterator };
_forOfSuspendData[statement] = data;
}
return data;
}

/// <summary>
/// Tries to get existing suspend data for a for-of statement.
/// Returns true if we're resuming into this for-of loop.
/// </summary>
internal bool TryGetForOfSuspendData(object statement, out ForOfSuspendData? data)
{
if (_forOfSuspendData?.TryGetValue(statement, out data) == true)
{
return true;
}
data = null;
return false;
}

/// <summary>
/// Clears suspend data for a for-of statement when the loop completes normally.
/// </summary>
internal void ClearForOfSuspendData(object statement)
{
_forOfSuspendData?.Remove(statement);
}

/// <summary>
/// Gets or creates suspend data for an array destructuring pattern.
/// Called when a generator is about to execute destructuring that may contain yields.
/// Gets or creates suspend data of the specified type.
/// Keys should be Jint expression/statement instances (this) to avoid collisions across engines.
/// </summary>
internal DestructuringSuspendData GetOrCreateDestructuringSuspendData(object pattern, IteratorInstance iterator)
internal T GetOrCreateSuspendData<T>(object key, IteratorInstance iterator) where T : SuspendData, new()
{
_destructuringSuspendData ??= new Dictionary<object, DestructuringSuspendData>();
if (!_destructuringSuspendData.TryGetValue(pattern, out var data))
_suspendData ??= new Dictionary<object, SuspendData>();
if (!_suspendData.TryGetValue(key, out var data))
{
data = new DestructuringSuspendData { Iterator = iterator };
_destructuringSuspendData[pattern] = data;
data = new T { Iterator = iterator };
_suspendData[key] = data;
}
return data;
return (T) data;
}

/// <summary>
/// Tries to get existing suspend data for an array destructuring pattern.
/// Returns true if we're resuming into this destructuring.
/// Tries to get existing suspend data of the specified type.
/// Returns true if suspend data exists for the given key.
/// </summary>
internal bool TryGetDestructuringSuspendData(object pattern, out DestructuringSuspendData? data)
internal bool TryGetSuspendData<T>(object key, out T? data) where T : SuspendData
{
if (_destructuringSuspendData?.TryGetValue(pattern, out data) == true)
if (_suspendData?.TryGetValue(key, out var baseData) == true)
{
data = (T) baseData;
return true;
}
data = null;
data = default;
return false;
}

/// <summary>
/// Clears suspend data for an array destructuring pattern when it completes.
/// Clears suspend data for the given key when the construct completes.
/// </summary>
internal void ClearDestructuringSuspendData(object pattern)
internal void ClearSuspendData(object key)
{
_destructuringSuspendData?.Remove(pattern);
_suspendData?.Remove(key);
}

/// <summary>
Expand All @@ -365,20 +308,18 @@ internal void ClearDestructuringSuspendData(object pattern)
/// </summary>
internal void CloseAllDestructuringIterators(CompletionType completionType)
{
if (_destructuringSuspendData is null)
if (_suspendData is null)
{
return;
}

foreach (var kvp in _destructuringSuspendData)
foreach (var kvp in _suspendData)
{
var data = kvp.Value;
if (!data.Done)
if (kvp.Value is DestructuringSuspendData data && !data.Done)
{
data.Iterator.Close(completionType);
data.Done = true;
}
}
_destructuringSuspendData.Clear();
}
}
34 changes: 0 additions & 34 deletions Jint/Runtime/DestructuringSuspendData.cs

This file was deleted.

33 changes: 0 additions & 33 deletions Jint/Runtime/ForOfSuspendData.cs

This file was deleted.

18 changes: 0 additions & 18 deletions Jint/Runtime/GeneratorReturnException.cs

This file was deleted.

20 changes: 20 additions & 0 deletions Jint/Runtime/Interpreter/EvaluationContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
using Jint.Native.Generator;

namespace Jint.Runtime.Interpreter;

Expand Down Expand Up @@ -27,6 +28,25 @@ public EvaluationContext()
public readonly Engine Engine;
public bool DebugMode => Engine._isDebugMode;

/// <summary>
/// Returns true if the generator is suspended (yielded) or a return was requested.
/// This is the combined check that should be used after evaluating sub-expressions.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsGeneratorAborted()
{
var generator = Engine.ExecutionContext.Generator;
return generator is not null &&
(generator._generatorState == GeneratorState.SuspendedYield || generator._returnRequested);
}

/// <summary>
/// Returns true if the generator is suspended (yielded).
/// Use this when you only need to check for yield suspension without return request.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsGeneratorSuspended() => Engine.ExecutionContext.Suspended;

public Node LastSyntaxElement
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
Loading