Skip to content

Commit 7827f9a

Browse files
authored
Merge pull request #224 from asynkron/codex/fix-array-fromasync-promise
Fix realm promise access and stabilize Array.fromAsync
2 parents 882c207 + cfd379f commit 7827f9a

12 files changed

+258
-127
lines changed

continue.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@
2424
- Array index assignments now walk prototype accessors/writable descriptors (including inherited setters) before falling back to element storage, so member-expression for-in targets trigger Array.prototype setters and typed array for-in over resizable buffers enumerates the expected indices.
2525
- `using` and `await using` declarations still TypeError on non-object initializers but now allow initializer-less for-in/of heads with per-iteration lexical environments, so the TDZ/fresh-binding for-of cases are green.
2626
- Typed array subclass instances now report their concrete @@toStringTag, generator/async generator functions (and their bound/proxy wrappers) are treated as non-constructors during class heritage resolution, and derived classes extending null leave `this` uninitialized until super, so the class subclass builtins/null-proto/generator superclass cases are green.
27+
- Super calls now bind sloppy callees through the global when the caller's `this` is uninitialized, resolve the base constructor from the current constructor `[[Prototype]]`, and defer `super` property key coercion until after capturing the super base, so the `Expressions_super` cluster is green.
28+
- Promise constructor is exposed on RealmState and pulled from closure or realm for async generators; `Array.fromAsync` async-path now drives iteration iteratively (no recursive promise callbacks) and the full `Array_fromAsync` filter passes. Strict `arguments-object` assignment tests now build proper JS error objects and pass.
2729
- Module evaluation now walks requested modules ahead of body execution, ResolveExport/export\* follow the spec (cycle-aware, ambiguous names filtered), module namespaces expose unambiguous exports only, and namespace [[Set]]/Reflect.set return false instead of throwing.
2830
- Object destructuring rest now walks [[OwnPropertyKeys]] with exclusions checked before GetOwnPropertyDescriptor, skips Get for non-enumerables, and uses the original object (including proxies) instead of cloning; the proxy rest destructuring get/gOPD/ownKeys-order tests are passing.
2931

3032
## Next Iteration Plan
31-
1. Re-run the Language suite to refresh the current failure set after the for-in/of using and array setter fixes.
32-
2. Triage the next largest failing cluster from that run (modules/import/defer if they resurface, otherwise whatever is hottest), adding realm logging if the failure isn’t obvious.
33-
3. Iterate through the refreshed list in priority order, landing targeted fixes and re-running the filtered clusters as they’re addressed.
33+
1. When resuming broader work, run a narrow Language filter outside `ArgumentsObject`/`Array_fromAsync` to find the next hot cluster (avoid full 43k sweep).
34+
2. Triage any new failures/hangs with targeted filters; add realm logging only if symptoms remain opaque.

src/Asynkron.JsEngine/Ast/AssignmentReference.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,62 @@ private static AssignmentReference ResolveMember(
104104
context,
105105
context.RealmState);
106106
}
107+
108+
var superPropertyValue = evaluateExpression(member.Property, environment, context);
109+
if (context.ShouldStopEvaluation)
110+
{
111+
return new AssignmentReference(() => Symbol.Undefined, _ => { });
112+
}
113+
114+
var binding = TypedAstEvaluator.ExpectSuperBinding(environment, context);
115+
string? propertyNameCache = null;
116+
string GetPropertyName()
117+
{
118+
propertyNameCache ??= JsOps.GetRequiredPropertyName(superPropertyValue, context);
119+
return propertyNameCache;
120+
}
121+
122+
return new AssignmentReference(
123+
() =>
124+
{
125+
if (binding.Prototype is null)
126+
{
127+
throw StandardLibrary.ThrowTypeError(
128+
"Cannot read properties of null (reading from super)",
129+
context,
130+
context.RealmState);
131+
}
132+
133+
var propertyName = GetPropertyName();
134+
return binding.TryGetProperty(propertyName, out var value)
135+
? value
136+
: Symbol.Undefined;
137+
},
138+
newValue =>
139+
{
140+
if (!binding.IsThisInitialized)
141+
{
142+
throw TypedAstEvaluator.CreateSuperReferenceError(environment, context, null);
143+
}
144+
145+
if (binding.Prototype is null)
146+
{
147+
throw StandardLibrary.ThrowTypeError(
148+
"Cannot assign to super property when prototype is null or undefined.",
149+
context,
150+
context.RealmState);
151+
}
152+
153+
var propertyName = GetPropertyName();
154+
if (!binding.TrySetProperty(propertyName, newValue, out _) &&
155+
context.CurrentScope.IsStrict)
156+
{
157+
throw StandardLibrary.ThrowTypeError(
158+
$"Cannot assign to read only property '{propertyName}' of object",
159+
context,
160+
context.RealmState);
161+
}
162+
});
107163
}
108164

109165
var target = evaluateExpression(member.Target, environment, context);

src/Asynkron.JsEngine/Ast/ExpressionNodeExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,8 @@ private bool ContainsDirectEvalCall()
274274
// For a constructor, the active function is available via NewTarget when it's
275275
// a constructor being invoked via 'new'.
276276
object? dynamicSuperConstructor = binding.Constructor;
277-
if (environment.TryGet(Symbol.NewTarget, out var newTarget) &&
277+
if (dynamicSuperConstructor is null &&
278+
environment.TryGet(Symbol.NewTarget, out var newTarget) &&
278279
newTarget is IJsObjectLike activeFunction)
279280
{
280281
// Get the current [[Prototype]] of the active function (constructor)

src/Asynkron.JsEngine/Ast/JsEnvironmentExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ private void EnsureFunctionScopedVarBinding(Symbol name,
104104
environment.DefineFunctionScoped(name, Symbol.Undefined, false, context: context, canDelete: allowDelete);
105105
}
106106

107-
private SuperBinding ExpectSuperBinding(EvaluationContext context)
107+
internal SuperBinding ExpectSuperBinding(EvaluationContext context)
108108
{
109109
var logger = environment.RealmState?.Logger;
110110
try
@@ -220,7 +220,7 @@ private bool TryCreateSuperBindingFromThis(
220220
return true;
221221
}
222222

223-
private Exception CreateSuperReferenceError(EvaluationContext context,
223+
internal Exception CreateSuperReferenceError(EvaluationContext context,
224224
Exception? inner)
225225
{
226226
environment.RealmState?.Logger?.LogInformation("SuperBinding: reference error thisInit? {ThisInit}", context.IsThisInitialized);

src/Asynkron.JsEngine/Ast/TypedAstEvaluator.AsyncGeneratorInstance.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,18 @@ public JsObject CreateAsyncIteratorObject()
5757
private object? CreateStepPromise(TypedGeneratorInstance.ResumeMode mode, object? argument)
5858
{
5959
// Look up the global Promise constructor from the closure environment.
60-
if (!closure.TryGet(Symbol.PromiseIdentifier, out var promiseCtorObj) ||
61-
promiseCtorObj is not IJsCallable promiseCtor)
60+
IJsCallable? promiseCtor = null;
61+
if (closure.TryGet(Symbol.PromiseIdentifier, out var promiseCtorObj) &&
62+
promiseCtorObj is IJsCallable promiseFromEnv)
63+
{
64+
promiseCtor = promiseFromEnv;
65+
}
66+
else if (realmState.PromiseConstructor is IJsCallable promiseFromRealm)
67+
{
68+
promiseCtor = promiseFromRealm;
69+
}
70+
71+
if (promiseCtor is null)
6272
{
6373
throw new InvalidOperationException("Promise constructor is not available in the current environment.");
6474
}

src/Asynkron.JsEngine/Ast/TypedAstEvaluator.TypedFunction.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,17 @@ public bool TryDefineProperty(string name, PropertyDescriptor descriptor)
618618
{
619619
if (thisValue is null || ReferenceEquals(thisValue, Symbol.Undefined))
620620
{
621-
boundThis = CallingJsEnvironment?.Get(Symbol.This);
621+
try
622+
{
623+
boundThis = CallingJsEnvironment?.Get(Symbol.This);
624+
}
625+
catch (InvalidOperationException ex) when (ex.Message.StartsWith("ReferenceError:",
626+
StringComparison.Ordinal))
627+
{
628+
// If the caller's `this` binding is uninitialized (e.g., derived ctor before super()),
629+
// fall back to the global object per non-strict this-binding semantics.
630+
boundThis = Symbol.Undefined;
631+
}
622632
if (boundThis is null || ReferenceEquals(boundThis, Symbol.Undefined))
623633
{
624634
boundThis = _realmState.Engine?.GlobalObject;
@@ -694,7 +704,18 @@ public bool TryDefineProperty(string name, PropertyDescriptor descriptor)
694704

695705
if (_homeObject is not null || _superConstructor is not null || prototypeForSuper is not null)
696706
{
697-
var binding = new SuperBinding(_superConstructor, prototypeForSuper, boundThis,
707+
var runtimeSuperConstructor = _superConstructor;
708+
if (_isClassConstructor)
709+
{
710+
var runtimeCtorPrototype =
711+
(this as IPrototypeAccessorProvider)?.PrototypeAccessor ?? Prototype;
712+
if (runtimeCtorPrototype is IJsEnvironmentAwareCallable ctorLike)
713+
{
714+
runtimeSuperConstructor = ctorLike;
715+
}
716+
}
717+
718+
var binding = new SuperBinding(runtimeSuperConstructor, prototypeForSuper, boundThis,
698719
initialThisInitialized);
699720
functionEnvironment.RealmState?.Logger?.LogInformation(
700721
"SuperBinding: define in function env env={Env} isCtor={IsCtor} isDerivedCtor={IsDerivedCtor} protoNull={ProtoNull} thisInit={ThisInit}",

src/Asynkron.JsEngine/EvalHostFunction.cs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -65,23 +65,8 @@ public EvalHostFunction(JsEngine engine)
6565
}
6666
catch (ParseException parseException)
6767
{
68-
var message = parseException.Message;
69-
object? errorObject = message;
70-
if (!environment.TryGet(Symbol.SyntaxErrorIdentifier, out var ctor) ||
71-
ctor is not IJsCallable callable)
72-
{
73-
throw new ThrowSignal(errorObject);
74-
}
75-
76-
try
77-
{
78-
errorObject = callable.Invoke([message], null);
79-
}
80-
catch (ThrowSignal signal)
81-
{
82-
errorObject = signal.ThrownValue;
83-
}
84-
68+
var errorObject =
69+
StandardLibrary.CreateSyntaxError(parseException.Message, CallingContext, environment.RealmState);
8570
throw new ThrowSignal(errorObject);
8671
}
8772

src/Asynkron.JsEngine/JsEngine.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,9 @@ public JsEngine(IJsEngineOptions? options = null)
188188
SetGlobal("AggregateError", StandardLibrary.CreateErrorConstructor(RealmState, "AggregateError"));
189189

190190
// Register Promise constructor
191-
SetGlobal("Promise", StandardLibrary.CreatePromiseConstructor(this));
191+
var promiseConstructor = StandardLibrary.CreatePromiseConstructor(this);
192+
SetGlobal("Promise", promiseConstructor);
193+
RealmState.PromiseConstructor = promiseConstructor as IJsCallable;
192194

193195
// Register Symbol constructor
194196
SetGlobal("Symbol", StandardLibrary.CreateSymbolConstructor(RealmState));

src/Asynkron.JsEngine/Runtime/RealmState.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public sealed class RealmState
5151
public HostFunction? GeneratorFunctionConstructor { get; set; }
5252
public JsObject? AsyncGeneratorFunctionPrototype { get; set; }
5353
public JsObject? AsyncGeneratorPrototype { get; set; }
54+
public IJsCallable? PromiseConstructor { get; set; }
5455

5556
// Internal flags to avoid re-attaching built-in surfaces per instance
5657
public bool StringPrototypeMethodsInitialized { get; set; }

0 commit comments

Comments
 (0)