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
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,5 @@ public partial class CompilationContext
// Variable name to class expression mapping (for static member access)
public Dictionary<string, Expr.ClassExpr>? VarToClassExpr { get; set; }

public IReadOnlyDictionary<Stmt.Class, (MethodBuilder Method, IReadOnlyList<Expr> Keys)>? DeferredComputedClassKeys { get; set; }
public IReadOnlyDictionary<Expr.ClassExpr, (MethodBuilder Method, IReadOnlyList<Expr> Keys)>? DeferredComputedClassExprKeys { get; set; }
public DeferredClassDefinitionRegistry? DeferredClassDefinitions { get; set; }
}
113 changes: 113 additions & 0 deletions src/SharpTS/Compilation/DeferredClassDefinitionRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection.Emit;
using SharpTS.Parsing;

namespace SharpTS.Compilation;

/// <summary>
/// Owns deferred class initializers and captured computed keys for one compilation.
/// Declarations remain readable while bodies are emitted, including nested classes;
/// completion closes registration and checks every forward-declared initializer.
/// </summary>
public sealed class DeferredClassDefinitionRegistry
{
private readonly Dictionary<TypeBuilder, DeferredClassDefinition> _byType = new(ReferenceEqualityComparer.Instance);
private readonly Dictionary<object, DeferredClassDefinition> _bySource = new(ReferenceEqualityComparer.Instance);
private readonly Dictionary<Stmt.Field, FieldBuilder> _fieldKeys = new(ReferenceEqualityComparer.Instance);
private readonly HashSet<DeferredClassDefinition> _emitted = new(ReferenceEqualityComparer.Instance);

public bool IsComplete { get; private set; }

public bool TryGet(TypeBuilder owner, [NotNullWhen(true)] out DeferredClassDefinition? definition)
=> _byType.TryGetValue(owner, out definition);

public bool TryGet(object source, [NotNullWhen(true)] out DeferredClassDefinition? definition)
=> _bySource.TryGetValue(source, out definition);

internal FieldBuilder RequireFieldKey(Stmt.Field field)
=> _fieldKeys.TryGetValue(field, out var key) ? key
: throw new InvalidOperationException("The computed field key has not been declared.");

internal bool TryGetFieldKey(Stmt.Field field, [NotNullWhen(true)] out FieldBuilder? key)
=> _fieldKeys.TryGetValue(field, out key);

internal DeferredClassDefinition Declare(object source, TypeBuilder owner, MethodBuilder initializer,
MethodBuilder registrar, IEnumerable<Expr> keys, IReadOnlyDictionary<Stmt.Field, FieldBuilder> fieldKeys)
{
EnsureMutable();
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(owner);
ArgumentNullException.ThrowIfNull(initializer);
ArgumentNullException.ThrowIfNull(registrar);
ArgumentNullException.ThrowIfNull(keys);
ArgumentNullException.ThrowIfNull(fieldKeys);
var snapshot = keys.ToArray();
if (source is not (Stmt.Class or Expr.ClassExpr)
|| _bySource.ContainsKey(source) || _byType.ContainsKey(owner)
|| initializer.DeclaringType != owner || !initializer.IsStatic || !registrar.IsStatic
|| (!owner.IsGenericTypeDefinition && registrar.DeclaringType != owner)
|| registrar.Module != owner.Module || ReferenceEquals(initializer, registrar)
|| snapshot.Length == 0 || snapshot.Any(key => key is null))
throw new InvalidOperationException("Invalid or duplicate deferred class declaration.");
var sourceFields = source is Stmt.Class declaration ? declaration.Fields : ((Expr.ClassExpr)source).Fields;
var expected = sourceFields.Where(field => field.ComputedKey != null && !field.IsDeclare).ToArray();
if (fieldKeys.Count != expected.Length || expected.Any(field => !fieldKeys.ContainsKey(field)))
throw new InvalidOperationException("Deferred class keys must include every computed field.");
foreach (var (field, key) in fieldKeys)
{
if (key is null || _fieldKeys.ContainsKey(field) || !key.IsStatic
|| !expected.Any(expectedField => ReferenceEquals(expectedField, field))
|| key.DeclaringType != registrar.DeclaringType
|| !snapshot.Any(expression => ReferenceEquals(expression, field.ComputedKey)))
throw new InvalidOperationException("Invalid or duplicate computed field key declaration.");
}
var definition = new DeferredClassDefinition(owner, initializer, registrar, Array.AsReadOnly(snapshot));
_byType.Add(owner, definition);
_bySource.Add(source, definition);
foreach (var (field, key) in fieldKeys)
_fieldKeys.Add(field, key);
return definition;
}

internal void MarkInitializerEmitted(DeferredClassDefinition definition)
{
EnsureMutable();
ArgumentNullException.ThrowIfNull(definition);
if (!_byType.TryGetValue(definition.Owner, out var declared) || !ReferenceEquals(declared, definition)
|| definition.Initializer.GetILGenerator().ILOffset == 0
|| definition.Registrar.GetILGenerator().ILOffset == 0 || !_emitted.Add(definition))
throw new InvalidOperationException("The deferred class initializer is foreign, empty, or already emitted.");
}

internal void CompleteEmission()
{
EnsureMutable();
if (_emitted.Count != _byType.Count)
throw new InvalidOperationException("Not all deferred class initializers have been emitted.");
IsComplete = true;
}

private void EnsureMutable()
{
if (IsComplete)
throw new InvalidOperationException("Deferred class metadata is complete.");
}
}

/// <summary>Immutable forward references shared by declaration and expression emitters.</summary>
public sealed class DeferredClassDefinition
{
internal DeferredClassDefinition(TypeBuilder owner, MethodBuilder initializer, MethodBuilder registrar,
IReadOnlyList<Expr> keys)
{
Owner = owner;
Initializer = initializer;
Registrar = registrar;
Keys = keys;
}

public TypeBuilder Owner { get; }
public MethodBuilder Initializer { get; }
public MethodBuilder Registrar { get; }
public IReadOnlyList<Expr> Keys { get; }
}
11 changes: 6 additions & 5 deletions src/SharpTS/Compilation/ILCompiler.Classes.ClassExpressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -466,8 +466,7 @@ private void DefineClassExpressionMethodSignatures(Expr.ClassExpr classExpr)
}
}

if (DefineDeferredComputedMethodKeyRegistrar(typeBuilder, classExpr.Fields) is { } deferred)
_classExprs.DeferredComputedKeys[classExpr] = deferred;
DefineDeferredComputedMethodKeyRegistrar(classExpr, typeBuilder, classExpr.Fields);
}

/// <summary>
Expand Down Expand Up @@ -628,7 +627,7 @@ private void EmitClassExpressionStaticConstructor(Expr.ClassExpr classExpr, Type
EmitClassPrototypeRegistration(
il, typeBuilder, GetClassConstructorLength(classExpr.Methods));

if (_classes.DeferredClassDefinitions.TryGetValue(typeBuilder.Name, out var deferredDefinition))
if (_classes.DeferredDefinitions.TryGet(typeBuilder, out var deferredDefinition))
{
il.Emit(OpCodes.Ret);
il = deferredDefinition.Initializer.GetILGenerator();
Expand All @@ -645,7 +644,7 @@ private void EmitClassExpressionStaticConstructor(Expr.ClassExpr classExpr, Type
switch (initializer)
{
case Stmt.Field field when field.IsStatic && field.ComputedKey != null:
_classes.ComputedFieldKeys.TryGetValue(field, out var computedKey);
_classes.DeferredDefinitions.TryGetFieldKey(field, out var computedKey);
EmitComputedStaticFieldInitializer(emitter, il, typeBuilder, field, computedKey);
break;

Expand Down Expand Up @@ -683,6 +682,8 @@ private void EmitClassExpressionStaticConstructor(Expr.ClassExpr classExpr, Type
EmitSymbolMethodRegistrations(emitter, il, typeBuilder);

il.Emit(OpCodes.Ret);
if (deferredDefinition != null)
_classes.DeferredDefinitions.MarkInitializerEmitted(deferredDefinition);
}

/// <summary>
Expand Down Expand Up @@ -845,7 +846,7 @@ void EmitInstanceFieldInitializers()
if (field.ComputedKey != null)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldsfld, _classes.ComputedFieldKeys[field]);
il.Emit(OpCodes.Ldsfld, _classes.DeferredDefinitions.RequireFieldKey(field));
if (field.Initializer != null)
{
emitter.EmitExpression(field.Initializer);
Expand Down
2 changes: 1 addition & 1 deletion src/SharpTS/Compilation/ILCompiler.Classes.Constructors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ private void EmitConstructor(TypeBuilder typeBuilder, Stmt.Class classStmt, Fiel
// Stack: this
il.Emit(OpCodes.Ldarg_0);
// Load the key captured when the class definition was evaluated.
il.Emit(OpCodes.Ldsfld, _classes.ComputedFieldKeys[field]);
il.Emit(OpCodes.Ldsfld, _classes.DeferredDefinitions.RequireFieldKey(field));
// Emit initializer value; a field with no initializer is still an own
// property whose value is undefined.
if (field.Initializer != null)
Expand Down
3 changes: 1 addition & 2 deletions src/SharpTS/Compilation/ILCompiler.Classes.Methods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,7 @@ private void DefineClassMethodsOnly(Stmt.Class classStmt)
CreateExplicitAccessorProperties(typeBuilder, className);
}

if (DefineDeferredComputedMethodKeyRegistrar(typeBuilder, classStmt.Fields) is { } deferred)
_classes.DeferredComputedClassKeys[classStmt] = deferred;
DefineDeferredComputedMethodKeyRegistrar(classStmt, typeBuilder, classStmt.Fields);
}

private bool TryResolveTypedPrimitiveMethodCoreReturnType(
Expand Down
28 changes: 17 additions & 11 deletions src/SharpTS/Compilation/ILCompiler.Classes.Static.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ private void EmitStaticConstructor(TypeBuilder typeBuilder, Stmt.Class classStmt
il.Emit(OpCodes.Stsfld, privateFieldStorage);
}

if (_classes.DeferredClassDefinitions.TryGetValue(typeBuilder.Name, out var deferredDefinition))
if (_classes.DeferredDefinitions.TryGet(typeBuilder, out var deferredDefinition))
{
il.Emit(OpCodes.Ret);
il = deferredDefinition.Initializer.GetILGenerator();
Expand Down Expand Up @@ -151,7 +151,7 @@ private void EmitStaticConstructor(TypeBuilder typeBuilder, Stmt.Class classStmt
case Stmt.Field field when field.IsStatic:
if (field.ComputedKey != null)
{
_classes.ComputedFieldKeys.TryGetValue(field, out var computedKey);
_classes.DeferredDefinitions.TryGetFieldKey(field, out var computedKey);
EmitComputedStaticFieldInitializer(emitter, il, typeBuilder, field, computedKey);
break;
}
Expand Down Expand Up @@ -236,6 +236,8 @@ private void EmitStaticConstructor(TypeBuilder typeBuilder, Stmt.Class classStmt
EmitSymbolMethodRegistrations(emitter, il, typeBuilder);

il.Emit(OpCodes.Ret);
if (deferredDefinition != null)
_classes.DeferredDefinitions.MarkInitializerEmitted(deferredDefinition);
}

/// <summary>
Expand All @@ -251,7 +253,7 @@ private void EmitSymbolAccessorRegistrations(ILEmitter emitter, ILGenerator il,

foreach (var (accessor, method) in list)
{
if (_classes.DeferredClassDefinitions.ContainsKey(typeBuilder.Name))
if (_classes.DeferredDefinitions.TryGet(typeBuilder, out _))
continue;
bool isGetter = accessor.Kind.Type == TokenType.GET;

Expand Down Expand Up @@ -296,7 +298,7 @@ private void EmitSymbolMethodRegistrations(ILEmitter emitter, ILGenerator il, Ty

foreach (var (method, key, builder) in list)
{
if (_classes.DeferredClassDefinitions.ContainsKey(typeBuilder.Name))
if (_classes.DeferredDefinitions.TryGet(typeBuilder, out _))
continue;
// owner: typeof(ThisClass)
il.Emit(OpCodes.Ldtoken, typeBuilder);
Expand All @@ -316,10 +318,14 @@ private void EmitSymbolMethodRegistrations(ILEmitter emitter, ILGenerator il, Ty
}
}

private (MethodBuilder Method, IReadOnlyList<Expr> Keys)? DefineDeferredComputedMethodKeyRegistrar(TypeBuilder typeBuilder, IReadOnlyList<Stmt.Field> fields)
private void DefineDeferredComputedMethodKeyRegistrar(object source, TypeBuilder typeBuilder, IReadOnlyList<Stmt.Field> fields)
{
if (_classes.DeferredClassDefinitions.TryGetValue(typeBuilder.Name, out var existing))
return (existing.Registrar, existing.Keys);
if (_classes.DeferredDefinitions.TryGet(typeBuilder, out var existing))
{
if (!_classes.DeferredDefinitions.TryGet(source, out var bySource) || !ReferenceEquals(existing, bySource))
throw new InvalidOperationException("The deferred class type belongs to a different declaration.");
return;
}
var deferred = new List<(Expr Key, MethodBuilder? Builder, bool IsStatic, bool? IsGetter, int Position, Stmt.Field? Field)>();
if (_classes.SymbolMethods.TryGetValue(typeBuilder.Name, out var methods))
foreach (var (method, key, builder) in methods)
Expand All @@ -331,7 +337,7 @@ private void EmitSymbolMethodRegistrations(ILEmitter emitter, ILGenerator il, Ty
deferred.Add((field.ComputedKey!, null, field.IsStatic, null, field.Name.Start, field));
// Field names are evaluated with the definition, even when no key suspends.
if (!deferred.Any(entry => entry.Field != null || ExpressionContainsSuspension(entry.Key)))
return null;
return;

deferred = deferred.OrderBy(entry => entry.Position).ToList();
var initializer = typeBuilder.DefineMethod("$initializeDeferredClass",
Expand All @@ -345,6 +351,7 @@ private void EmitSymbolMethodRegistrations(ILEmitter emitter, ILGenerator il, Ty
MethodAttributes.Assembly | MethodAttributes.Static,
_types.Void,
[_types.ObjectArray]);
var fieldKeys = new Dictionary<Stmt.Field, FieldBuilder>(ReferenceEqualityComparer.Instance);
var il = registrar.GetILGenerator();
var getTypeFromHandle = _types.GetMethod(_types.Type, "GetTypeFromHandle", _types.RuntimeTypeHandle);

Expand All @@ -355,7 +362,7 @@ private void EmitSymbolMethodRegistrations(ILEmitter emitter, ILGenerator il, Ty
{
var keyField = keyOwner.DefineField($"$computedFieldKey_{typeBuilder.Name}_{i}", _types.Object,
FieldAttributes.Assembly | FieldAttributes.Static);
_classes.ComputedFieldKeys.Add(field, keyField);
fieldKeys.Add(field, keyField);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldc_I4, i);
il.Emit(OpCodes.Ldelem_Ref);
Expand Down Expand Up @@ -384,8 +391,7 @@ private void EmitSymbolMethodRegistrations(ILEmitter emitter, ILGenerator il, Ty
: initializer);
il.Emit(OpCodes.Ret);
var keys = deferred.Select(entry => entry.Key).ToArray();
_classes.DeferredClassDefinitions.Add(typeBuilder.Name, (initializer, registrar, keys));
return (registrar, keys);
_classes.DeferredDefinitions.Declare(source, typeBuilder, initializer, registrar, keys, fieldKeys);
}

private void EmitComputedStaticFieldInitializer(ILEmitter emitter, ILGenerator il,
Expand Down
6 changes: 2 additions & 4 deletions src/SharpTS/Compilation/ILCompiler.ContextFactories.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ private CompilationContext CreateBaseCompilationContext(ILGenerator il, MethodBa
ClassExprBuilders = _classExprs.Builders,
ClassExprStaticMethods = _classExprs.StaticMethods,
ClassExprCaptureFields = _classExprs.CaptureFields,
DeferredComputedClassKeys = _classes.DeferredComputedClassKeys,
DeferredComputedClassExprKeys = _classExprs.DeferredComputedKeys,
DeferredClassDefinitions = _classes.DeferredDefinitions,
BlockScopedClassBuilders = _classes.BlockScopedBuilders,
ClassRegistry = GetClassRegistry(),
DotNetNamespace = _modules.CurrentDotNetNamespace,
Expand Down Expand Up @@ -268,8 +267,7 @@ private CompilationContext CreateNestedAsyncArrowContext(ILGenerator il, Compila
ClassExprBuilders = parentCtx.ClassExprBuilders,
ClassExprCaptureFields = parentCtx.ClassExprCaptureFields,
BlockScopedClassBuilders = parentCtx.BlockScopedClassBuilders,
DeferredComputedClassKeys = parentCtx.DeferredComputedClassKeys,
DeferredComputedClassExprKeys = parentCtx.DeferredComputedClassExprKeys,
DeferredClassDefinitions = parentCtx.DeferredClassDefinitions,
IsStrictMode = parentCtx.IsStrictMode,
// ES2022 Private Class Elements support - inherit from parent context
CurrentClassName = parentCtx.CurrentClassName,
Expand Down
5 changes: 1 addition & 4 deletions src/SharpTS/Compilation/ILCompiler.State.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public partial class ILCompiler
/// </summary>
private sealed class ClassCompilationState
{
public DeferredClassDefinitionRegistry DeferredDefinitions { get; } = new();
public HashSet<Stmt.Class> Declarations { get; } = new(ReferenceEqualityComparer.Instance);
public HashSet<Stmt.Class> EmittedMethodBodies { get; } = new(ReferenceEqualityComparer.Instance);
public Dictionary<string, TypeBuilder> Builders { get; } = [];
Expand Down Expand Up @@ -74,9 +75,6 @@ private sealed class ClassCompilationState
// machinery (incl. generator/async state machines); the key drives the .cctor
// RegisterSymbolMethod call.
public Dictionary<string, List<(Parsing.Stmt.Function Method, Parsing.Expr Key, MethodBuilder Builder)>> SymbolMethods { get; } = [];
public Dictionary<Stmt.Class, (MethodBuilder Method, IReadOnlyList<Expr> Keys)> DeferredComputedClassKeys { get; } = new(ReferenceEqualityComparer.Instance);
public Dictionary<string, (MethodBuilder Initializer, MethodBuilder Registrar, IReadOnlyList<Expr> Keys)> DeferredClassDefinitions { get; } = [];
public Dictionary<Stmt.Field, FieldBuilder> ComputedFieldKeys { get; } = new(ReferenceEqualityComparer.Instance);
public Dictionary<string, FieldBuilder> InstanceFieldsField { get; } = [];
public HashSet<Stmt.Class> CompactStorageClasses { get; } =
new(ReferenceEqualityComparer.Instance);
Expand Down Expand Up @@ -442,7 +440,6 @@ private sealed class ClassExpressionCompilationState
public Dictionary<Expr.ClassExpr, string?> Superclass { get; } = new(ReferenceEqualityComparer.Instance);
public Dictionary<Expr.ClassExpr, string> EnclosingClass { get; } = new(ReferenceEqualityComparer.Instance);
public Dictionary<Expr.ClassExpr, Dictionary<string, FieldBuilder>> CaptureFields { get; } = new(ReferenceEqualityComparer.Instance);
public Dictionary<Expr.ClassExpr, (MethodBuilder Method, IReadOnlyList<Expr> Keys)> DeferredComputedKeys { get; } = new(ReferenceEqualityComparer.Instance);
}

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions src/SharpTS/Compilation/ILCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,7 @@ private void DefineHoistedRegexFields(List<Stmt> statements)
/// </summary>
private void Phase9_FinalizeTypes()
{
_classes.DeferredDefinitions.CompleteEmission();
_unionGenerator?.FinalizeAllUnionTypes();

// Finalize generated object-literal shape structs (#862) before any type that uses them.
Expand Down Expand Up @@ -1652,6 +1653,7 @@ private void ModulePhase10_EmitEntryPoint(List<ParsedModule> modules)
/// </summary>
private void ModulePhase11_FinalizeTypes()
{
_classes.DeferredDefinitions.CompleteEmission();
_unionGenerator?.FinalizeAllUnionTypes();

// Finalize generated object-literal shape structs (#862) before any type that uses them.
Expand Down
Loading
Loading