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 @@ -109,7 +109,21 @@ protected override void VisitTryCatch(Stmt.TryCatch stmt)

// Don't traverse into nested declarations - they don't affect our analysis
protected override void VisitFunction(Stmt.Function stmt) { }
protected override void VisitClass(Stmt.Class stmt) { }
protected override void VisitClass(Stmt.Class stmt)
{
string name = StorageName(stmt, stmt.Name.Lexeme);
_declaredVariables.Add(name);
if (!_seenSuspension)
_variablesDeclaredBeforeSuspension.Add(name);
foreach (var expression in ClassDefinitionExpressions.Enumerate(stmt))
Visit(expression);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
protected override void VisitClassExpr(Expr.ClassExpr expr)
{
foreach (var expression in ClassDefinitionExpressions.Enumerate(expr))
Visit(expression);
}

protected override void VisitInterface(Stmt.Interface stmt) { }
protected override void VisitTypeAlias(Stmt.TypeAlias stmt) { }
protected override void VisitEnum(Stmt.Enum stmt) { }
Expand Down
15 changes: 14 additions & 1 deletion src/SharpTS/Compilation/AsyncStateAnalyzer.Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,20 @@ protected override void VisitTryCatch(Stmt.TryCatch stmt)

// Don't traverse into nested declarations - they don't affect our analysis
protected override void VisitFunction(Stmt.Function stmt) { }
protected override void VisitClass(Stmt.Class stmt) { }
protected override void VisitClass(Stmt.Class stmt)
{
// The class body has its own scope, but its binding belongs to this
// function and must survive a suspension when subsequently referenced.
_declaredVariables.Add(StorageName(stmt, stmt.Name.Lexeme));
foreach (var expression in ClassDefinitionExpressions.Enumerate(stmt))
Visit(expression);
}
protected override void VisitClassExpr(Expr.ClassExpr expr)
{
foreach (var expression in ClassDefinitionExpressions.Enumerate(expr))
Visit(expression);
}

protected override void VisitInterface(Stmt.Interface stmt) { }
protected override void VisitTypeAlias(Stmt.TypeAlias stmt) { }
protected override void VisitEnum(Stmt.Enum stmt) { }
Expand Down
31 changes: 31 additions & 0 deletions src/SharpTS/Compilation/ClassDefinitionExpressions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using SharpTS.Parsing;

namespace SharpTS.Compilation;

/// <summary>Expressions evaluated in the enclosing scope when defining a class.</summary>
internal static class ClassDefinitionExpressions
{
internal static IEnumerable<Expr> Enumerate(Stmt.Class declaration) =>
Enumerate(declaration.SuperclassExpr, declaration.Fields, declaration.Methods, declaration.Accessors);

internal static IEnumerable<Expr> Enumerate(Expr.ClassExpr expression) =>
Enumerate(expression.SuperclassExpr, expression.Fields, expression.Methods, expression.Accessors);

private static IEnumerable<Expr> Enumerate(Expr? superclass, List<Stmt.Field> fields,
List<Stmt.Function> methods, List<Stmt.Accessor>? accessors)
{
if (superclass != null)
yield return superclass;
foreach (var key in MemberKeys(fields, methods, accessors))
yield return key;
}

internal static IEnumerable<Expr> MemberKeys(IEnumerable<Stmt.Field> fields,
IEnumerable<Stmt.Function> methods, IEnumerable<Stmt.Accessor>? accessors) =>
fields.Select(field => (field.Name.Start, field.ComputedKey))
.Concat(methods.Select(method => (method.Name.Start, method.ComputedKey)))
.Concat((accessors ?? []).Select(accessor => (accessor.Name.Start, accessor.ComputedKey)))
.Where(member => member.ComputedKey != null)
.OrderBy(member => member.Start)
.Select(member => member.ComputedKey!);
}
4 changes: 4 additions & 0 deletions src/SharpTS/Compilation/ClosureAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,8 @@ protected override void VisitFunction(Stmt.Function stmt)
protected override void VisitClass(Stmt.Class stmt)
{
DeclareVariable(stmt.Name.Lexeme);
foreach (var expression in ClassDefinitionExpressions.Enumerate(stmt))
Visit(expression);
foreach (var method in stmt.Methods)
{
// Skip overload signatures (no body)
Expand All @@ -672,6 +674,8 @@ protected override void VisitClass(Stmt.Class stmt)

protected override void VisitClassExpr(Expr.ClassExpr expr)
{
foreach (var expression in ClassDefinitionExpressions.Enumerate(expr))
Visit(expression);
// Class expressions don't declare the class name in the outer scope
// (unlike class declarations), but we still need to analyze all bodies

Expand Down
6 changes: 4 additions & 2 deletions src/SharpTS/Compilation/ExpressionEmitterBase.CallHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1333,8 +1333,9 @@ protected static bool AnyContainsSuspension(IEnumerable<Expr> exprs)
Expr.Satisfies sa => ExprContainsSuspension(sa.Expression),
Expr.NonNullAssertion nn => ExprContainsSuspension(nn.Expression),
Expr.DynamicImport di => ExprContainsSuspension(di.PathExpression),
// Leaves (Literal/Variable/This/Super/ImportMeta/RegexLiteral) and lambda/class
// boundaries (ArrowFunction/ClassExpr) cannot surface a suspension to the current frame.
Expr.ClassExpr ce => AnyContainsSuspension(ClassDefinitionExpressions.Enumerate(ce)),
// Leaves and nested function bodies cannot suspend the current frame.
// Class heritage and computed names execute in the current frame.
_ => false
};

Expand Down Expand Up @@ -1365,6 +1366,7 @@ protected static bool AnyStmtContainsSuspension(IEnumerable<Stmt> statements)
/// </summary>
protected static bool StmtContainsSuspension(Stmt stmt) => stmt switch
{
Stmt.Class c => AnyContainsSuspension(ClassDefinitionExpressions.Enumerate(c)),
Stmt.Expression e => ExprContainsSuspension(e.Expr),
Stmt.Var v => v.Initializer != null && ExprContainsSuspension(v.Initializer),
Stmt.Const c => ExprContainsSuspension(c.Initializer),
Expand Down
20 changes: 16 additions & 4 deletions src/SharpTS/Compilation/GeneratorBlockScopeRenamer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ internal sealed record BlockScopeRenameResult(
}

/// <summary>
/// Computes per-binding storage names for block-scoped (<c>let</c>/<c>const</c>) declarations inside a
/// Computes per-binding storage names for block-scoped (<c>let</c>/<c>const</c>/class) declarations inside a
/// suspension state-machine body that <em>shadow</em> an enclosing binding of the same name.
/// </summary>
/// <remarks>
Expand Down Expand Up @@ -73,7 +73,7 @@ internal sealed record BlockScopeRenameResult(
///
/// Restrictions that keep the rewrite sound:
/// <list type="bullet">
/// <item>Only <c>let</c>/<c>const</c> (<see cref="Stmt.Const"/>, <see cref="Stmt.Var"/> with
/// <item>Only class and <c>let</c>/<c>const</c> (<see cref="Stmt.Const"/>, <see cref="Stmt.Var"/> with
/// <c>IsVar == false</c>) declarations are renamed. <c>for</c>/<c>for-of</c>/<c>for-in</c>/<c>catch</c>
/// introduce scopes (so shadowing is detected accurately) but their loop-variable / catch-parameter
/// bindings are left alone.</item>
Expand Down Expand Up @@ -391,8 +391,20 @@ protected override void VisitArrowFunction(Expr.ArrowFunction expr)
// capture pivot does not cover, so a binding they read is already OFF-LIMITS (CaptureClassifier).
// Their interiors contribute no renames or pivots; do not descend.
protected override void VisitFunction(Stmt.Function stmt) { }
protected override void VisitClass(Stmt.Class stmt) { }
protected override void VisitClassExpr(Expr.ClassExpr expr) { }
protected override void VisitClass(Stmt.Class stmt)
{
if (_arrowDepth == 0)
DeclareBlockScoped(stmt, stmt.Name.Lexeme);
else
CurrentScope[stmt.Name.Lexeme] = stmt.Name.Lexeme;
foreach (var expression in ClassDefinitionExpressions.Enumerate(stmt))
Visit(expression);
}
protected override void VisitClassExpr(Expr.ClassExpr expr)
{
foreach (var expression in ClassDefinitionExpressions.Enumerate(expr))
Visit(expression);
}

#endregion

Expand Down
33 changes: 7 additions & 26 deletions src/SharpTS/Compilation/GeneratorStateAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,40 +275,21 @@ protected override void VisitTryCatch(Stmt.TryCatch stmt)
protected override void VisitFunction(Stmt.Function stmt) { }
protected override void VisitClass(Stmt.Class stmt)
{
_declaredVariables.Add(stmt.Name.Lexeme);
string name = StorageName(stmt, stmt.Name.Lexeme);
_declaredVariables.Add(name);
if (!_seenYield)
_variablesDeclaredBeforeYield.Add(stmt.Name.Lexeme);
_variablesDeclaredBeforeYield.Add(name);

// Class bodies are nested callable/initializer scopes, but heritage and
// computed element names are evaluated eagerly in the enclosing generator.
if (stmt.SuperclassExpr != null)
Visit(stmt.SuperclassExpr);
foreach (var field in stmt.Fields)
if (field.ComputedKey != null)
Visit(field.ComputedKey);
foreach (var method in stmt.Methods)
if (method.ComputedKey != null)
Visit(method.ComputedKey);
if (stmt.Accessors != null)
foreach (var accessor in stmt.Accessors)
if (accessor.ComputedKey != null)
Visit(accessor.ComputedKey);
foreach (var expression in ClassDefinitionExpressions.Enumerate(stmt))
Visit(expression);
}

protected override void VisitClassExpr(Expr.ClassExpr expr)
{
if (expr.SuperclassExpr != null)
Visit(expr.SuperclassExpr);
foreach (var field in expr.Fields)
if (field.ComputedKey != null)
Visit(field.ComputedKey);
foreach (var method in expr.Methods)
if (method.ComputedKey != null)
Visit(method.ComputedKey);
if (expr.Accessors != null)
foreach (var accessor in expr.Accessors)
if (accessor.ComputedKey != null)
Visit(accessor.ComputedKey);
foreach (var expression in ClassDefinitionExpressions.Enumerate(expr))
Visit(expression);
}
protected override void VisitInterface(Stmt.Interface stmt) { }
protected override void VisitTypeAlias(Stmt.TypeAlias stmt) { }
Expand Down
6 changes: 4 additions & 2 deletions src/SharpTS/Compilation/ILCompiler.ArrowFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,8 @@ private void CollectArrowsFromStmt(Stmt stmt)
}
break;
case Stmt.Class c:
foreach (var expression in ClassDefinitionExpressions.Enumerate(c))
CollectArrowsFromExpr(expression);
var previousClassName = _currentCollectClassName;
_currentCollectClassName = c.Name.Lexeme;
foreach (var method in c.Methods)
Expand All @@ -604,8 +606,6 @@ private void CollectArrowsFromStmt(Stmt stmt)
// get MethodBuilders instead of EmitArrowFunction's null fallback.
foreach (var field in c.Fields)
{
if (field.ComputedKey != null)
CollectArrowsFromExpr(field.ComputedKey);
if (field.Initializer != null)
CollectArrowsFromExpr(field.Initializer);
}
Expand Down Expand Up @@ -1080,6 +1080,8 @@ or Stmt.Class
CollectArrowsFromExpr(sp2.Value);
break;
case Expr.ClassExpr ce:
foreach (var expression in ClassDefinitionExpressions.Enumerate(ce))
CollectArrowsFromExpr(expression);
// Collect the class expression for later definition
CollectClassExpression(ce);
var previousClassNameCE = _currentCollectClassName;
Expand Down
21 changes: 19 additions & 2 deletions src/SharpTS/Compilation/ILCompiler.Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,16 @@ private void AnalyzeArrowStmtForAwaits(Stmt stmt, ref int awaitCount, ref bool s
{
switch (stmt)
{
case Stmt.Class classStmt:
{
var name = ArrowStorageName(classStmt, classStmt.Name.Lexeme);
declaredVariables.Add(name);
if (!seenAwait)
declaredBeforeAwait.Add(name);
foreach (var expression in ClassDefinitionExpressions.Enumerate(classStmt))
AnalyzeArrowExprForAwaits(expression, ref awaitCount, ref seenAwait, declaredVariables, usedAfterAwait, declaredBeforeAwait);
break;
}
case Stmt.Var v:
{
var name = ArrowStorageName(v, v.Name.Lexeme); // #766: per-binding storage name
Expand Down Expand Up @@ -972,6 +982,10 @@ private void AnalyzeArrowExprForAwaits(Expr expr, ref int awaitCount, ref bool s
{
switch (expr)
{
case Expr.ClassExpr classExpr:
foreach (var expression in ClassDefinitionExpressions.Enumerate(classExpr))
AnalyzeArrowExprForAwaits(expression, ref awaitCount, ref seenAwait, declaredVariables, usedAfterAwait, declaredBeforeAwait);
break;
case Expr.Await a:
awaitCount++;
seenAwait = true;
Expand Down Expand Up @@ -1009,6 +1023,10 @@ private void AnalyzeArrowExprForAwaits(Expr expr, ref int awaitCount, ref bool s
case Expr.Grouping g:
AnalyzeArrowExprForAwaits(g.Expression, ref awaitCount, ref seenAwait, declaredVariables, usedAfterAwait, declaredBeforeAwait);
break;
case Expr.Comma comma:
AnalyzeArrowExprForAwaits(comma.Left, ref awaitCount, ref seenAwait, declaredVariables, usedAfterAwait, declaredBeforeAwait);
AnalyzeArrowExprForAwaits(comma.Right, ref awaitCount, ref seenAwait, declaredVariables, usedAfterAwait, declaredBeforeAwait);
break;
case Expr.Call c:
AnalyzeArrowExprForAwaits(c.Callee, ref awaitCount, ref seenAwait, declaredVariables, usedAfterAwait, declaredBeforeAwait);
foreach (var arg in c.Arguments)
Expand Down Expand Up @@ -1242,8 +1260,7 @@ private void EmitSuspensionFreePrimitiveAsyncFunctionBodies()
coreParameters[index].ParameterType);
}
var coreEmitter = new SuspensionFreeAsyncCoreEmitter(coreContext);
foreach (Stmt statement in function.Body!)
coreEmitter.EmitStatement(statement);
coreEmitter.EmitStatements(function.Body!);

MethodBuilder stubMethod = _functions.Builders[functionName];
ILGenerator il = stubMethod.GetILGenerator();
Expand Down
49 changes: 42 additions & 7 deletions src/SharpTS/Compilation/ILCompiler.Classes.ClassExpressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -428,9 +428,13 @@ private void DefineClassExpressionMethodSignatures(Expr.ClassExpr classExpr)
// by this class's generated name. It is registered in the class-
// expression .cctor and dispatched through the $Runtime symbol-
// accessor registry, mirroring the class-declaration path (#266).
if (accessor.ComputedKey != null)
if (accessor.ComputedKey != null || accessor.IsStatic)
{
DefineSymbolAccessorMethod(typeBuilder, accessor);
// Static accessors use the runtime constructor registry rather
// than the instance CLR property table, including literal names.
DefineSymbolAccessorMethod(typeBuilder, accessor.ComputedKey != null
? accessor
: accessor with { ComputedKey = new Expr.Literal(accessor.Name.Lexeme) });
continue;
}
string accessorName = accessor.Name.Lexeme;
Expand Down Expand Up @@ -461,6 +465,9 @@ private void DefineClassExpressionMethodSignatures(Expr.ClassExpr classExpr)
_classExprs.Setters[classExpr][pascalName] = methodBuilder;
}
}

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

/// <summary>
Expand Down Expand Up @@ -521,7 +528,7 @@ private void EmitClassExpressionBody(Expr.ClassExpr classExpr)
{
// Symbol-keyed accessors are emitted below from _classes.SymbolAccessors
// (their synthetic methods aren't in _classExprs.Getters/Setters).
if (!accessor.IsAbstract && accessor.ComputedKey == null)
if (!accessor.IsAbstract && accessor.ComputedKey == null && !accessor.IsStatic)
{
EmitClassExpressionAccessor(classExpr, typeBuilder, accessor, fieldsField);
}
Expand Down Expand Up @@ -621,13 +628,27 @@ private void EmitClassExpressionStaticConstructor(Expr.ClassExpr classExpr, Type
EmitClassPrototypeRegistration(
il, typeBuilder, GetClassConstructorLength(classExpr.Methods));

if (_classes.DeferredClassDefinitions.TryGetValue(typeBuilder.Name, out var deferredDefinition))
{
il.Emit(OpCodes.Ret);
il = deferredDefinition.Initializer.GetILGenerator();
ctx = CreateClassExpressionContext(il, classExpr, typeBuilder, null, deferredDefinition.Initializer);
ctx.IsStaticConstructorContext = true;
emitter = new ILEmitter(ctx);
}

// Process StaticInitializers if available (preserves declaration order)
if (hasStaticInitializers)
{
foreach (var initializer in classExpr.StaticInitializers!)
{
switch (initializer)
{
case Stmt.Field field when field.IsStatic && field.ComputedKey != null:
_classes.ComputedFieldKeys.TryGetValue(field, out var computedKey);
EmitComputedStaticFieldInitializer(emitter, il, typeBuilder, field, computedKey);
break;

case Stmt.Field field when field.IsStatic && field.Initializer != null:
var staticField = _classExprs.StaticFields[classExpr][field.Name.Lexeme];
emitter.EmitExpression(field.Initializer);
Expand Down Expand Up @@ -819,8 +840,24 @@ private void EmitClassExpressionConstructor(Expr.ClassExpr classExpr, TypeBuilde

void EmitInstanceFieldInitializers()
{
foreach (var field in classExpr.Fields.Where(f => !f.IsStatic && f.Initializer != null))
foreach (var field in classExpr.Fields.Where(f => !f.IsStatic && !f.IsDeclare && (f.Initializer != null || f.ComputedKey != null)))
{
if (field.ComputedKey != null)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldsfld, _classes.ComputedFieldKeys[field]);
if (field.Initializer != null)
{
emitter.EmitExpression(field.Initializer);
emitter.EmitBoxIfNeeded(field.Initializer);
}
else
il.Emit(OpCodes.Ldsfld, _runtime.Sentinels.UndefinedInstance);
il.Emit(OpCodes.Call, _runtime.ObjectWrite.Index);
continue;
}
if (field.Initializer == null)
continue;
string fieldName = field.Name.Lexeme;
string pascalName = NamingConventions.ToPascalCase(fieldName);

Expand Down Expand Up @@ -1177,7 +1214,7 @@ private void DefineClassExpressionSymbolMethods(Expr.ClassExpr classExpr, TypeBu
// Unique, deterministic name so multiple computed methods don't collide and the synthetic
// `<computed>` lexeme (not a dispatchable name) is replaced by a real IL name.
string uniqueName = $"$symmethod_{i}";
var renamed = method with { Name = new Token(TokenType.IDENTIFIER, uniqueName, null, method.Name.Line) };
var renamed = method with { Name = new Token(TokenType.IDENTIFIER, uniqueName, null, method.Name.Line, method.Name.Start) };

// Class-expression methods use all-object parameter slots (computed iterator methods are
// typically parameterless anyway). Async generator FIRST since it sets both flags.
Expand All @@ -1203,8 +1240,6 @@ private void DefineClassExpressionSymbolMethods(Expr.ClassExpr classExpr, TypeBu
list.Add((renamed, method.ComputedKey!, mb));
}
_classes.SymbolMethods[className] = list;
if (DefineDeferredComputedMethodKeyRegistrar(typeBuilder) is { } deferred)
_classExprs.DeferredComputedKeys[classExpr] = deferred;
}

/// <summary>
Expand Down
Loading
Loading