Fixed closure upvalue scoping

This commit is contained in:
Michael Schimmel
2025-09-30 17:25:28 +02:00
parent 1ac605ee57
commit 1c1bd4cdca
6 changed files with 365 additions and 85 deletions
+46 -5
View File
@@ -10,8 +10,14 @@ uses
Myc.Ast.Nodes,
Myc.Ast.Visitor,
Myc.Ast.Scope,
Myc.Ast.Analyzer,
Myc.Ast;
//TODO Es gibt hier einen fundamentalen Fehler. Der Binder ist ist in einem Durchlauf nicht in der Lage, Upvalues zu erkennen.
// Wenn in TransformVariableDeclaration eine Variable definiert wird, ist sie grundsätzlich lokal, obwohl sie in einer nested lambda als upvalue benutzt werden könnte.
// Deshalb muss eigentlich zunächst ermittelt werden, wleche variablen später zur upvalue werden.
// Dafür ist Myc.Ast.Analyzer möglicherweise nützlich.
type
// The binder is a transformer that enriches the AST with semantic information
// like resolved addresses, scopes, and tail-call annotations.
@@ -36,6 +42,9 @@ type
FNestedLambdaCount: Integer;
FIsTailStack: TStack<Boolean>;
FNextIsTail: Boolean;
// Contains all variable declarations that are captured by a closure.
// This is populated by a pre-pass before the transformation begins.
FBoxedDeclarations: THashSet<IVariableDeclarationNode>;
procedure EnterScope;
procedure ExitScope;
@@ -73,6 +82,15 @@ type
property Address: TResolvedAddress read FAddress;
end;
// A bound variable declaration node that includes whether the variable is captured (boxed).
TBoundVariableDeclarationNode = class(TVariableDeclarationNode)
private
FIsBoxed: Boolean;
public
constructor Create(const AIdentifier: IIdentifierNode; AInitializer: IAstNode; AIsBoxed: Boolean);
property IsBoxed: Boolean read FIsBoxed;
end;
TBoundLambdaExpressionNode = class(TLambdaExpressionNode)
private
FScopeDescriptor: IScopeDescriptor;
@@ -127,6 +145,13 @@ begin
FAddress := AAddress;
end;
{ TBoundVariableDeclarationNode }
constructor TBoundVariableDeclarationNode.Create(const AIdentifier: IIdentifierNode; AInitializer: IAstNode; AIsBoxed: Boolean);
begin
inherited Create(AIdentifier, AInitializer);
FIsBoxed := AIsBoxed;
end;
{ TBoundLambdaExpressionNode }
constructor TBoundLambdaExpressionNode.Create(
const AUnboundNode: ILambdaExpressionNode;
@@ -192,12 +217,14 @@ begin
FNestedLambdaCount := 0;
FIsTailStack := TStack<Boolean>.Create;
FNextIsTail := True;
FBoxedDeclarations := nil;
end;
destructor TAstBinder.Destroy;
begin
FIsTailStack.Free;
FUpvalueStack.Free;
FBoxedDeclarations.Free;
inherited;
end;
@@ -221,12 +248,20 @@ end;
function TAstBinder.Execute(const RootNode: IAstNode; out Descriptor: IScopeDescriptor): IAstNode;
begin
EnterScope;
// First pass: Analyze the entire AST to find all variable declarations
// that are captured by closures (upvalues).
FBoxedDeclarations := TUpvalueAnalyzer.Analyze(RootNode, FCurrentDescriptor.Parent);
try
Result := Accept(RootNode).AsIntf<IAstNode>;
Descriptor := FCurrentDescriptor;
// Second pass: Transform the tree, using the information from the analysis pass.
EnterScope;
try
Result := Accept(RootNode).AsIntf<IAstNode>;
Descriptor := FCurrentDescriptor;
finally
ExitScope;
end;
finally
ExitScope;
// The binder now owns the hash set, which will be freed in the destructor.
end;
end;
@@ -290,11 +325,13 @@ var
slotIndex: Integer;
address: TResolvedAddress;
boundIdentifier: IIdentifierNode;
isBoxed: Boolean;
begin
if not IsValidIdentifier(Node.Identifier.Name) then
raise Exception.CreateFmt('Invalid identifier name: "%s".', [Node.Identifier.Name]);
FNextIsTail := False;
initializer := nil;
if Node.Initializer <> nil then
initializer := Accept(Node.Initializer).AsIntf<IAstNode>;
@@ -303,7 +340,11 @@ begin
boundIdentifier := TBoundIdentifierNode.Create(Node.Identifier, address);
Result := TAst.VarDecl(boundIdentifier, initializer);
// Check if the analysis pass marked this declaration as being captured by a closure.
isBoxed := (FBoxedDeclarations <> nil) and FBoxedDeclarations.Contains(Node);
// Always create a bound declaration node, passing the IsBoxed flag.
Result := TBoundVariableDeclarationNode.Create(boundIdentifier, initializer, isBoxed);
end;
function TAstBinder.TransformAssignment(const Node: IAssignmentNode): IAssignmentNode;