Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
390d2ca
Parse enums in ambient module declarations (Refs #2945)
Nov 7, 2026
9e4f2fc
Use Array reference semantics for string index updates (Refs #3473)
Nov 7, 2026
641fc83
Order interpreted instance index keys numerically (Refs #3478)
Nov 7, 2026
01da09c
Preserve interpreted class accessor creation order (Refs #3480)
Nov 7, 2026
bfd1de0
Declare interpreted parameter properties before fields (Refs #3482)
Nov 7, 2026
616485a
Bind generic lexical super to the closed constructor owner (Refs #3484)
Nov 7, 2026
747715b
Bind lexical super to an intrinsic Array receiver (Refs #3485)
Nov 7, 2026
96043f8
Assert Symbol key identity in the class ordering regression (Refs #3478)
Nov 7, 2026
aa415dc
Implement class-owned ECMAScript private accessors (Refs #3483)
Nov 7, 2026
3de4258
Keep closed-owner selection scoped to generic lexical super (Refs #3484)
Nov 7, 2026
2c98a60
Preserve the decorated accessor admission boundary (Refs #3485)
Nov 7, 2026
a4694e6
Reject explicit this parameters on private accessors (Refs #3483)
Nov 7, 2026
89d29fa
Map sloppy arguments indices to live simple parameters (Refs #3476)
Nov 7, 2026
7e3e189
Deduplicate Array indices backed by property descriptors (Refs #3509)
Nov 7, 2026
f878dd3
Sever mapped arguments when Reflect deletes an index (Refs #3476)
Nov 7, 2026
a310a74
Isolate ambient enum bindings and support quoted members (Refs #2945)
Nov 7, 2026
875b704
Skip synthetic fields after declaring parameter properties (Refs #3482)
Nov 7, 2026
5240808
Verify accessor enumeration in both execution modes (Refs #3480)
Nov 7, 2026
c6b52ba
Filter live enumerable class-instance fields in for-in and revivers (…
Nov 7, 2026
f51b589
Honor live parents and reentrant receiver binding in direct super (Re…
Nov 7, 2026
ed86386
Assert ambient enum types through their checked module surface (Refs …
Nov 7, 2026
c140d4b
Complete private accessor declaration, inference, and setter checks (…
Nov 7, 2026
940085e
Omit redundant declare modifiers inside ambient declaration bodies (R…
Nov 7, 2026
53c2ffd
Complete mapped arguments review fixes and gate emitted support (Refs…
Nov 7, 2026
4acc322
Resolve declared enum members through literal string keys (Refs #2945…
Nov 8, 2026
43e470a
Serialize only enumerable class fields without a replacer list (Refs …
Nov 8, 2026
446814d
Assert descriptor flags after abrupt field initialization (Refs #3482)
Nov 8, 2026
bc00c21
Resolve forward private getter types before publishing signatures (Re…
Nov 8, 2026
ec7e392
Assert direct-check ambient enum errors through their diagnostics (Re…
Nov 8, 2026
dcef603
Read Array callback indices lazily for arguments (Refs #3476, #3520, …
Nov 8, 2026
391581f
Preserve mixed enum member kinds and inherited string reads (Refs #35…
Nov 8, 2026
fd49425
Record declaration names within ambient blocks for the editor (Refs #…
Nov 8, 2026
cdf1c63
Verify JSON serialization of enumerable instance accessors (Refs #3498)
Nov 8, 2026
673aa86
Keep private getter dependencies local to each inference scope (Refs …
Nov 8, 2026
cd0e60a
Keep arguments callback snapshots independent of logical length (Refs…
Nov 8, 2026
a2e61b5
Resolve the receiver binding before specializing enum reads (Refs #35…
Nov 8, 2026
66d78c4
Supply own-property metadata to the object-write fixture (Refs #3476)
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
1 change: 1 addition & 0 deletions src/SharpTS/Compilation/AsyncArrowMoveNextEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ public void EmitMoveNext(List<Stmt> body, CompilationContext ctx, Type returnTyp

// State dispatch switch
EmitStateDispatch();
MappedArgumentsEmitter.EmitStateMachinePrologue(ctx, _builder);

// Follow-up to #838: instantiate this arrow's own function display class once, so a nested sync
// arrow that writes one of the arrow's locals shares the reference cell. Null-guarded: a resume
Expand Down
2 changes: 2 additions & 0 deletions src/SharpTS/Compilation/AsyncArrowStorageAccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public AsyncArrowStorageAccess ThroughOuterStateMachine(AsyncArrowStateMachineBu
public static AsyncArrowStorageAccess OwnDisplayClassField(FieldInfo displayClassField, FieldInfo field)
=> new(null, field, new ReceiverStep(displayClassField));

internal void EmitCellReference(ILGenerator il) => EmitReceiver(il);

public static AsyncArrowStorageAccess CapturedField(
AsyncArrowStateMachineBuilder builder, string name, FieldInfo field)
=> new(null, field, OuterReceiverPath(builder,
Expand Down
2 changes: 2 additions & 0 deletions src/SharpTS/Compilation/AsyncMoveNextEmitter.Operators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ protected override void EmitTaggedTemplateLiteral(Expr.TaggedTemplateLiteral ttl

protected override void EmitGetPrivate(Expr.GetPrivate gp)
{
if (TryEmitPrivateAccessor(gp.Object, gp.Name)) return;
if (TryEmitPrivateMethodValue(gp)) return;
if (TryEmitDefinitionPrivateField(gp.Object, gp.Name)) return;
if (TryEmitGuestStaticPrivateField(gp.Object, gp.Name)) return;
Expand Down Expand Up @@ -139,6 +140,7 @@ protected override void EmitGetPrivate(Expr.GetPrivate gp)

protected override void EmitSetPrivate(Expr.SetPrivate sp)
{
if (TryEmitPrivateAccessor(sp.Object, sp.Name, sp.Value)) return;
if (TryEmitDefinitionPrivateField(sp.Object, sp.Name, sp.Value)) return;
if (TryEmitGuestStaticPrivateField(sp.Object, sp.Name, sp.Value)) return;
if (TryEmitGuestInstancePrivateField(sp.Object, sp.Name, sp.Value)) return;
Expand Down
1 change: 1 addition & 0 deletions src/SharpTS/Compilation/AsyncMoveNextEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ public void EmitMoveNext(List<Stmt>? body, CompilationContext ctx, Type returnTy

// Emit state dispatch switch (includes lock state if needed)
EmitStateSwitch();
MappedArgumentsEmitter.EmitStateMachinePrologue(ctx, _builder);

// For @lock: emit lock acquisition prologue after state switch
// This runs on initial entry (state -1) and handles reentrancy tracking
Expand Down
46 changes: 36 additions & 10 deletions src/SharpTS/Compilation/CallHandlers/SuperConstructorHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ public bool TryHandle(IEmitterContext emitter, Expr.Call call)

var ctx = emitter.Context;
if (TryEmitLexicalReceiverConstruction(emitter, call)) return true;
bool intrinsicArray = ctx.IsRuntimeReceiverInitializer
&& ctx.CurrentClassBuilder?.BaseType == ctx.Runtime!.ArrayStorage.Type
&& ctx.CurrentClassExpr is { } arrayExpression
&& ctx.LexicalSuperElementInitializers?.ContainsKey(arrayExpression) == true;
bool liveParent = ctx.IsRuntimeReceiverInitializer
&& (intrinsicArray || ctx.RuntimeFeatures?.UsesClassPrototypeMutation != false);
if (ctx.ConstructorSuperBound is { } bound)
{
var first = emitter.IL.DefineLabel();
Expand All @@ -43,7 +49,8 @@ public bool TryHandle(IEmitterContext emitter, Expr.Call call)
if (ctx.ClassDefinitionParameterIndex is { } classDefinition)
{
emitter.IL.Emit(OpCodes.Ldarg, classDefinition);
emitter.IL.Emit(OpCodes.Call, ctx.Runtime!.ClassDefinitions.GetParent);
emitter.IL.Emit(OpCodes.Call, liveParent
? ctx.Runtime!.ObjectPrototypes.GetPrototypeOf : ctx.Runtime!.ClassDefinitions.GetParent);
}
else if (ctx.CurrentSuperclassName == "Promise")
{
Expand Down Expand Up @@ -72,7 +79,6 @@ public bool TryHandle(IEmitterContext emitter, Expr.Call call)
}
if (ctx.IsRuntimeReceiverInitializer && ctx.ClassDefinitionParameterIndex is { } definitionParameter)
{
bool liveParent = ctx.RuntimeFeatures?.UsesClassPrototypeMutation != false;
emitter.IL.Emit(OpCodes.Ldarg, definitionParameter);
emitter.IL.Emit(OpCodes.Call, liveParent
? ctx.Runtime!.ObjectPrototypes.GetPrototypeOf : ctx.Runtime!.ClassDefinitions.GetParent);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Expand All @@ -83,11 +89,17 @@ public bool TryHandle(IEmitterContext emitter, Expr.Call call)
else emitter.IL.Emit(OpCodes.Ldsfld, ctx.Runtime.Sentinels.UndefinedInstance);
emitter.IL.Emit(OpCodes.Call, liveParent
? ctx.Runtime.DynamicConstruction.ConstructWithNewTarget : ctx.Runtime.ClassDefinitions.InitializeReceiver);
// Argument evaluation or the parent body can invoke a lexical super
// before this call completes. Keep that earlier receiver binding.
var receiver = emitter.IL.DeclareLocal(ctx.Types.Object);
emitter.IL.Emit(OpCodes.Stloc, receiver);
EmitRequireUnboundThis(emitter.IL, ctx, ctx.ClassConstructorThisCell!);
if (ctx.ConstructorSuperBound is { } completed)
{
emitter.IL.Emit(OpCodes.Ldc_I4_1);
emitter.IL.Emit(OpCodes.Stloc, completed);
}
emitter.IL.Emit(OpCodes.Ldloc, receiver);
emitter.IL.Emit(OpCodes.Stloc, ctx.ClassConstructorReceiver!);
emitter.IL.Emit(OpCodes.Ldloc, ctx.ClassConstructorThisCell!);
emitter.IL.Emit(OpCodes.Ldc_I4_0);
Expand Down Expand Up @@ -224,19 +236,17 @@ private static bool TryEmitLexicalReceiverConstruction(IEmitterContext emitter,
var cell = il.DeclareLocal(ctx.Types.ObjectArray);
il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, owner);
il.Emit(OpCodes.Call, ctx.Runtime!.ObjectPrototypes.GetPrototypeOf);
var closedOwner = elements?.DeclaringType?.IsGenericTypeDefinition == true
? ctx.ClassExprFactories![expression].Template : null;
if (closedOwner != null)
ConstructorReceiverEmitter.CloseNominalSuperTarget(il, ctx, closedOwner.BaseType);
emitter.EmitArgsArrayWithSpread(call.Arguments);
il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, newTarget);
il.Emit(OpCodes.Call, ctx.Runtime.DynamicConstruction.ConstructWithNewTarget);
il.Emit(OpCodes.Stloc, result);
il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, thisCell!);
il.Emit(OpCodes.Castclass, ctx.Types.ObjectArray); il.Emit(OpCodes.Stloc, cell);
il.Emit(OpCodes.Ldloc, cell); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Ldelem_Ref);
il.Emit(OpCodes.Isinst, ctx.Runtime.Sentinels.UndefinedType);
var unbound = il.DefineLabel();
il.Emit(OpCodes.Brtrue, unbound);
GuestErrorEmitter.ThrowError(il, ctx.Runtime.Errors.CreateException,
ctx.Runtime.Errors.ReferenceErrorConstructor, "Super constructor may only be called once");
il.MarkLabel(unbound);
EmitRequireUnboundThis(il, ctx, cell);
il.Emit(OpCodes.Ldloc, cell); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Ldloc, result);
il.Emit(OpCodes.Stelem_Ref);
if (elements != null)
Expand All @@ -259,13 +269,29 @@ private static bool TryEmitLexicalReceiverConstruction(IEmitterContext emitter,
il.Emit(OpCodes.Stelem_Ref);
}
}
il.Emit(OpCodes.Call, elements);
// Class-expression factories execute on the canonical closed CLR
// owner. The arrow's display class has no owner type parameters.
var target = closedOwner == null ? elements : EmitterTypeHelpers.ResolveMethod(closedOwner, elements);
il.Emit(OpCodes.Call, target);
}
il.Emit(OpCodes.Ldloc, result);
emitter.SetStackUnknown();
return true;
}

private static void EmitRequireUnboundThis(ILGenerator il, CompilationContext context, LocalBuilder cell)
{
var unbound = il.DefineLabel();
il.Emit(OpCodes.Ldloc, cell);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ldelem_Ref);
il.Emit(OpCodes.Isinst, context.Runtime!.Sentinels.UndefinedType);
il.Emit(OpCodes.Brtrue, unbound);
GuestErrorEmitter.ThrowError(il, context.Runtime.Errors.CreateException,
context.Runtime.Errors.ReferenceErrorConstructor, "Super constructor may only be called once");
il.MarkLabel(unbound);
}

/// <summary>
/// Emits a super() call that chains to a built-in Error constructor.
/// The emitted $Error/$TypeError etc. constructors take (string? message).
Expand Down
1 change: 1 addition & 0 deletions src/SharpTS/Compilation/ClosureAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ public ClosureAnalyzer(IReadOnlyDictionary<Stmt.Class, Expr.ClassExpr>? runtimeC
private readonly LoopBodyCellAnalyzer _bodyCells = new();

internal bool IsLoopBodyCellDeclaration(Token declaration) => _bodyCells.IsCellDeclaration(declaration);
internal bool IsMappedArgumentParameter(Token declaration) => _bodyCells.IsMappedArgumentParameter(declaration);
internal object? GetLoopBodyCellOwner(Token declaration) => _bodyCells.GetCellOwner(declaration);
internal bool IsOrdinaryBlockShadowDeclaration(Token declaration) => _bodyCells.IsOrdinaryBlockShadowDeclaration(declaration);
internal bool IsFunctionBodyVarDeclaration(Token declaration) => _bodyCells.IsFunctionBodyVarDeclaration(declaration);
Expand Down
4 changes: 3 additions & 1 deletion src/SharpTS/Compilation/CompilationContext.Classes.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Reflection;
using System.Reflection.Emit;
using SharpTS.Parsing;

namespace SharpTS.Compilation;

Expand Down Expand Up @@ -134,7 +135,8 @@ private string ResolvePrivateNameOwner(string fallbackClassName, string name)
private bool DeclaresPrivateName(string owner, string name)
=> ClassRegistry?.GetPrivateElements(owner) is { } elements
&& (elements.FieldNames.Contains(name) || elements.StaticFields.ContainsKey(name)
|| elements.Methods.ContainsKey(name) || elements.StaticMethods.ContainsKey(name));
|| elements.Methods.ContainsKey(name) || elements.StaticMethods.ContainsKey(name)
|| PrivateAccessorNames.Contains(elements.Methods, name) || PrivateAccessorNames.Contains(elements.StaticMethods, name));

// ============================================
// @lock Decorator Support: Thread-safe Method Execution
Expand Down
1 change: 1 addition & 0 deletions src/SharpTS/Compilation/CompilationContext.Closures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ private bool EmitEntryPointDisplayClassLoad(ILGenerator il)
internal HashSet<string>? PublishedCapturedParameterNames { get; set; }

internal Dictionary<string, (Token Declaration, LocalBuilder Cell)> ParameterCaptureCells { get; } = new(StringComparer.Ordinal);
internal Dictionary<Token, AsyncArrowStorageAccess>? AsyncMappedParameterCaptures { get; set; }

// Initialized parameter bindings survive initializer-free var redeclarations,
// including receiver adapters whose parameters live in locals rather than args.
Expand Down
4 changes: 2 additions & 2 deletions src/SharpTS/Compilation/ConstructorReceiverEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ namespace SharpTS.Compilation;

internal static class ConstructorReceiverEmitter
{
internal static void CloseNominalSuperTarget(ILGenerator il, CompilationContext context)
internal static void CloseNominalSuperTarget(ILGenerator il, CompilationContext context, Type? nominalParent = null)
{
// A guest generic constructor keeps its open Type identity. Its factory
// must execute through the checked closed owner, but only while that
// exact nominal constructor is still the selected live parent.
if (context.CurrentClassBuilder?.BaseType is not { IsConstructedGenericType: true } parent
if ((nominalParent ?? context.CurrentClassBuilder?.BaseType) is not { IsConstructedGenericType: true } parent
|| parent.ContainsGenericParameters
|| parent.GetGenericTypeDefinition() is not TypeBuilder definition
|| !context.Classes.Values.Any(owner => ReferenceEquals(owner, definition))) return;
Expand Down
7 changes: 7 additions & 0 deletions src/SharpTS/Compilation/EmittedDescriptorStorageRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ public ConstructorInfo DescriptorConstructor
internal set => Set(ref _descriptorConstructor, value);
}

private PropertyInfo? _descriptorParameterCell;
internal PropertyInfo? DescriptorParameterCell
{
get => _descriptorParameterCell;
set => Set(ref _descriptorParameterCell, value!);
}

private PropertyInfo? _descriptorValue;
public PropertyInfo DescriptorValue
{
Expand Down
68 changes: 68 additions & 0 deletions src/SharpTS/Compilation/ExpressionEmitterBase.PrivateAccessors.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Reflection.Emit;
using SharpTS.Parsing;

namespace SharpTS.Compilation;

public abstract partial class ExpressionEmitterBase
{
private readonly record struct PrivateAccessorReference(string Owner, string Name,
PrivateMethodValue? Getter, PrivateMethodValue? Setter);

private PrivateAccessorReference? FindPrivateAccessor(Token token)
{
if (Ctx.CurrentClassName is not { } current) return null;
string name = token.Lexeme.TrimStart('#');
string owner = Ctx.ResolvePrivateFieldOwner(current, name);
var registry = Ctx.ClassRegistry;
var getter = registry?.GetPrivateMethodValue(owner, PrivateAccessorNames.Getter(name));
var setter = registry?.GetPrivateMethodValue(owner, PrivateAccessorNames.Setter(name));
return getter is null && setter is null ? null : new(owner, name, getter, setter);
}

protected bool TryEmitPrivateAccessor(Expr receiverExpression, Token token, Expr? assignedExpression = null)
{
if (FindPrivateAccessor(token) is not { } accessor) return false;
var receiver = SpillBoxed(receiverExpression);
var assignedValue = assignedExpression is null ? null : SpillBoxed(assignedExpression);
EmitPrivateAccessorFacet(accessor, receiver, assignedValue);
if (assignedValue is not null)
{
IL.Emit(OpCodes.Ldloc, assignedValue);
SetStackUnknown();
}
return true;
}

private void EmitPrivateAccessorFacet(PrivateAccessorReference accessor, LocalBuilder receiver, LocalBuilder? assignedValue = null)
{
bool isWrite = assignedValue is not null;
var facet = isWrite ? accessor.Setter : accessor.Getter;
// A wrong-brand access fails before checking whether the facet exists.
var brand = facet ?? accessor.Getter ?? accessor.Setter!;
EmitPrivateMethodValue(brand, accessor.Owner, brand.Source.Name.Lexeme.TrimStart('#'), receiver, accessor.Name);
if (facet is null)
{
IL.Emit(OpCodes.Pop);
GuestErrorEmitter.ThrowTypeError(IL, Ctx.Runtime!,
$"Private accessor #{accessor.Name} does not have a {(isWrite ? "setter" : "getter")}");
return;
}
if (!isWrite)
{
EmitGuestMethodInvocation(receiver, []);
return;
}

var callable = _helpers.SpillStoreObject();
IL.Emit(OpCodes.Ldloc, receiver);
IL.Emit(OpCodes.Ldloc, callable);
IL.Emit(OpCodes.Ldc_I4_1);
IL.Emit(OpCodes.Newarr, Types.Object);
IL.Emit(OpCodes.Dup);
IL.Emit(OpCodes.Ldc_I4_0);
IL.Emit(OpCodes.Ldloc, assignedValue!);
IL.Emit(OpCodes.Stelem_Ref);
IL.Emit(OpCodes.Call, Ctx.Runtime!.Invocation.Method);
IL.Emit(OpCodes.Pop);
}
}
13 changes: 10 additions & 3 deletions src/SharpTS/Compilation/ExpressionEmitterBase.PrivateCompound.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ namespace SharpTS.Compilation;

public abstract partial class ExpressionEmitterBase
{
private readonly record struct PrivateFieldReference(FieldInfo? StaticField, LocalBuilder? Storage, string Key);
private readonly record struct PrivateFieldReference(FieldInfo? StaticField, LocalBuilder? Storage, string Key,
PrivateAccessorReference? Accessor = null);

protected void EmitCompoundSetPrivate(Expr.CompoundSetPrivate expression)
{
Expand Down Expand Up @@ -44,6 +45,8 @@ protected void EmitPrivateIncrement(Expr.GetPrivate field, TokenType operation,

private PrivateFieldReference ResolvePrivateFieldReference(Token fieldName, LocalBuilder receiver)
{
if (FindPrivateAccessor(fieldName) is { } accessor)
return new(null, receiver, accessor.Name, accessor);
string name = fieldName.Lexeme.TrimStart('#');
string owner = Ctx.CurrentClassName is { } current
? Ctx.ResolvePrivateFieldOwner(current, name)
Expand Down Expand Up @@ -107,7 +110,9 @@ private PrivateFieldReference ResolvePrivateFieldReference(Token fieldName, Loca

private void EmitPrivateFieldReferenceRead(PrivateFieldReference reference)
{
if (reference.StaticField is { } field)
if (reference.Accessor is { } accessor)
EmitPrivateAccessorFacet(accessor, reference.Storage!);
else if (reference.StaticField is { } field)
IL.Emit(OpCodes.Ldsfld, field);
else
{
Expand All @@ -121,7 +126,9 @@ private void EmitPrivateFieldReferenceRead(PrivateFieldReference reference)

private void EmitPrivateFieldReferenceWrite(PrivateFieldReference reference, LocalBuilder value)
{
if (reference.StaticField is { } field)
if (reference.Accessor is { } accessor)
EmitPrivateAccessorFacet(accessor, reference.Storage!, value);
else if (reference.StaticField is { } field)
{
IL.Emit(OpCodes.Ldloc, value);
IL.Emit(OpCodes.Stsfld, field);
Expand Down
Loading
Loading