Generic Visitors

This commit is contained in:
Michael Schimmel
2026-01-03 19:14:18 +01:00
parent db74b83e11
commit 22674b962b
27 changed files with 3573 additions and 3547 deletions
+183 -151
View File
@@ -52,28 +52,31 @@ type
function PrepareBaseType(const BaseNode: IAstNode; out IsOptional: Boolean): IStaticType;
function ApplyOptionality(const AType: IStaticType; IsOptional: Boolean): IStaticType;
protected
function VisitIdentifier(const Node: IIdentifierNode): IAstNode; override;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; override;
function VisitAssignment(const Node: IAssignmentNode): IAstNode; override;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; override;
function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override;
function VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; override;
function VisitIfExpression(const Node: IIfExpressionNode): IAstNode; override;
function VisitMemberAccess(const Node: IMemberAccessNode): IAstNode; override;
function VisitIndexer(const Node: IIndexerNode): IAstNode; override;
function VisitCreateSeries(const Node: ICreateSeriesNode): IAstNode; override;
function VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode; override;
function VisitRecurNode(const Node: IRecurNode): IAstNode; override;
function VisitNop(const Node: INopNode): IAstNode; override;
function VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode; override;
function VisitConstant(const Node: IConstantNode): IAstNode; override;
function VisitKeyword(const Node: IKeywordNode): IAstNode; override;
strict private
// Typed Handlers (non-virtual, IAstNode signature)
function VisitIdentifier(const Node: IAstNode): IAstNode;
function VisitVariableDeclaration(const Node: IAstNode): IAstNode;
function VisitAssignment(const Node: IAstNode): IAstNode;
function VisitLambdaExpression(const Node: IAstNode): IAstNode;
function VisitFunctionCall(const Node: IAstNode): IAstNode;
function VisitBlockExpression(const Node: IAstNode): IAstNode;
function VisitIfExpression(const Node: IAstNode): IAstNode;
function VisitMemberAccess(const Node: IAstNode): IAstNode;
function VisitIndexer(const Node: IAstNode): IAstNode;
function VisitCreateSeries(const Node: IAstNode): IAstNode;
function VisitSeriesLength(const Node: IAstNode): IAstNode;
function VisitRecurNode(const Node: IAstNode): IAstNode;
function VisitNop(const Node: IAstNode): IAstNode;
function VisitRecordLiteral(const Node: IAstNode): IAstNode;
function VisitConstant(const Node: IAstNode): IAstNode;
function VisitKeyword(const Node: IAstNode): IAstNode;
// Pipe Support
function VisitPipeInput(const Node: IPipeInputNode): IAstNode; override;
function VisitPipe(const Node: IPipeNode): IAstNode; override;
function VisitPipeInput(const Node: IAstNode): IAstNode;
function VisitPipe(const Node: IAstNode): IAstNode;
protected
procedure SetupHandlers; override;
public
constructor Create(const RootLayout: IScopeLayout; const RootScope: IExecutionScope; const ALog: ICompilerLog);
@@ -215,6 +218,32 @@ begin
inherited;
end;
procedure TTypeChecker.SetupHandlers;
begin
inherited SetupHandlers; // Load Defaults
Register(akIdentifier, VisitIdentifier);
Register(akVariableDeclaration, VisitVariableDeclaration);
Register(akAssignment, VisitAssignment);
Register(akLambdaExpression, VisitLambdaExpression);
Register(akFunctionCall, VisitFunctionCall);
Register(akBlockExpression, VisitBlockExpression);
Register(akIfExpression, VisitIfExpression);
Register(akMemberAccess, VisitMemberAccess);
Register(akIndexer, VisitIndexer);
Register(akCreateSeries, VisitCreateSeries);
Register(akSeriesLength, VisitSeriesLength);
Register(akRecur, VisitRecurNode);
Register(akNop, VisitNop);
Register(akRecordLiteral, VisitRecordLiteral);
Register(akConstant, VisitConstant);
Register(akKeyword, VisitKeyword);
// Pipe Support
Register(akPipeInput, VisitPipeInput);
Register(akPipe, VisitPipe);
end;
class function TTypeChecker.CheckTypes(
const RootNode: IAstNode;
const Layout: IScopeLayout;
@@ -261,24 +290,27 @@ end;
// --- Visits ---
function TTypeChecker.VisitConstant(const Node: IConstantNode): IAstNode;
function TTypeChecker.VisitConstant(const Node: IAstNode): IAstNode;
begin
// Base implementation already returns Node, but here we explicitly confirm identity for clarity
Result := Node;
end;
function TTypeChecker.VisitKeyword(const Node: IAstNode): IAstNode;
begin
Result := Node;
end;
function TTypeChecker.VisitKeyword(const Node: IKeywordNode): IAstNode;
begin
Result := Node;
end;
function TTypeChecker.VisitIdentifier(const Node: IIdentifierNode): IAstNode;
function TTypeChecker.VisitIdentifier(const Node: IAstNode): IAstNode;
var
I: IIdentifierNode;
typ: IStaticType;
adr: TResolvedAddress;
identity: INamedIdentity;
begin
adr := Node.Address;
identity := Node.Identity.AsNamed;
I := Node.AsIdentifier;
adr := I.Address;
identity := I.Identity.AsNamed;
if adr.Kind = akUnresolved then
begin
@@ -290,40 +322,45 @@ begin
Result := TAst.Identifier(identity, adr, typ);
end;
function TTypeChecker.VisitRecurNode(const Node: IRecurNode): IAstNode;
function TTypeChecker.VisitRecurNode(const Node: IAstNode): IAstNode;
var
newArgs: TArray<IAstNode>;
i: Integer;
R: IRecurNode;
args: IArgumentList;
begin
SetLength(newArgs, Node.Arguments.Count);
for i := 0 to Node.Arguments.Count - 1 do
newArgs[i] := Accept(Node.Arguments[i]);
R := Node.AsRecur;
// Use inherited to transform arguments, then reconstruct with type Void
// Note: inherited VisitRecurNode returns IAstNode which is a RecurNode.
// We can call Accept on arguments list directly to avoid intermediate node creation if desired,
// but relying on inherited logic keeps it consistent.
var argList := TArgumentList.Create(newArgs, Node.Arguments.Identity);
Result := TAst.Recur(Node.Identity, argList, TTypes.Void);
// Efficient approach: Accept the arguments list directly (it's a child).
args := Accept(R.Arguments).AsArgumentList;
Result := TAst.Recur(Node.Identity, args, TTypes.Void);
end;
function TTypeChecker.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
function TTypeChecker.VisitVariableDeclaration(const Node: IAstNode): IAstNode;
var
V: IVariableDeclarationNode;
initType: IStaticType;
newInitializer, newIdent: IAstNode;
adr: TResolvedAddress;
identNode: IIdentifierNode;
begin
identNode := Node.Target.AsIdentifier;
V := Node.AsVariableDeclaration;
identNode := V.Target.AsIdentifier;
adr := identNode.Address;
initType := TTypes.Unknown;
if adr.Kind = akUnresolved then
begin
if Assigned(Node.Initializer) then
Accept(Node.Initializer);
if Assigned(V.Initializer) then
Accept(V.Initializer);
Result := Node;
Exit;
end;
if Assigned(Node.Initializer) then
newInitializer := Accept(Node.Initializer)
if Assigned(V.Initializer) then
newInitializer := Accept(V.Initializer)
else
newInitializer := nil;
@@ -334,29 +371,31 @@ begin
FCurrentContext.SetType(adr.SlotIndex, initType);
newIdent := TAst.Identifier(identNode.Identity.AsNamed, adr, initType);
Result := TAst.VarDecl(Node.Identity, newIdent, newInitializer, initType, Node.IsBoxed);
Result := TAst.VarDecl(Node.Identity, newIdent, newInitializer, initType, V.IsBoxed);
end;
function TTypeChecker.VisitAssignment(const Node: IAssignmentNode): IAstNode;
function TTypeChecker.VisitAssignment(const Node: IAstNode): IAstNode;
var
A: IAssignmentNode;
targetType, sourceType: IStaticType;
newIdent, newValue: IAstNode;
adr: TResolvedAddress;
identNode: IIdentifierNode;
begin
identNode := Node.Target.AsIdentifier;
newIdent := Accept(Node.Target);
A := Node.AsAssignment;
identNode := A.Target.AsIdentifier;
newIdent := Accept(A.Target);
targetType := newIdent.AsTypedNode.StaticType;
adr := identNode.Address;
if adr.Kind = akUnresolved then
begin
Accept(Node.Value);
Accept(A.Value);
Result := Node;
Exit;
end;
newValue := Accept(Node.Value);
newValue := Accept(A.Value);
sourceType := newValue.AsTypedNode.StaticType;
if (targetType.Kind <> stUnknown) and (sourceType.Kind <> stUnknown) then
@@ -378,27 +417,28 @@ begin
Result := TAst.Assign(Node.Identity, newIdent, newValue, targetType);
end;
function TTypeChecker.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
function TTypeChecker.VisitBlockExpression(const Node: IAstNode): IAstNode;
var
newBlock: IBlockExpressionNode;
blockType: IStaticType;
newExprs: TArray<IAstNode>;
i: Integer;
exprs: IExpressionList;
begin
SetLength(newExprs, Node.Expressions.Count);
for i := 0 to Node.Expressions.Count - 1 do
newExprs[i] := Accept(Node.Expressions[i]);
// Inherited logic transforms all expressions in the list
newBlock := inherited VisitBlockExpression(Node).AsBlockExpression;
exprs := newBlock.Expressions;
if Length(newExprs) > 0 then
blockType := newExprs[High(newExprs)].AsTypedNode.StaticType
if exprs.Count > 0 then
blockType := exprs[exprs.Count - 1].AsTypedNode.StaticType
else
blockType := TTypes.Void;
var exprList := TExpressionList.Create(newExprs, Node.Expressions.Identity);
Result := TAst.Block(Node.Identity, exprList, blockType);
// Return new block with calculated type
Result := TAst.Block(Node.Identity, exprs, blockType);
end;
function TTypeChecker.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
function TTypeChecker.VisitLambdaExpression(const Node: IAstNode): IAstNode;
var
L: ILambdaExpressionNode;
newParams: TArray<IIdentifierNode>;
newBody: IAstNode;
bodyType, methodType: IStaticType;
@@ -409,8 +449,10 @@ var
paramIdent: IIdentifierNode;
injectedType: IStaticType;
begin
L := Node.AsLambdaExpression;
// 1. Resolve Upvalue Types
var upvalueAddrs := Node.Upvalues;
var upvalueAddrs := L.Upvalues;
SetLength(upvalueTypes, Length(upvalueAddrs));
for i := 0 to High(upvalueAddrs) do
@@ -425,14 +467,14 @@ begin
end;
// 2. Enter New Scope
FCurrentContext := TTypeContext.Create(FCurrentContext, Node.Layout, upvalueTypes, nil);
FCurrentContext := TTypeContext.Create(FCurrentContext, L.Layout, upvalueTypes, nil);
try
SetLength(newParams, Node.Parameters.Count);
SetLength(paramTypes, Node.Parameters.Count);
SetLength(newParams, L.Parameters.Count);
SetLength(paramTypes, L.Parameters.Count);
for i := 0 to Node.Parameters.Count - 1 do
for i := 0 to L.Parameters.Count - 1 do
begin
paramIdent := Node.Parameters[i];
paramIdent := L.Parameters[i];
// Check if there is already a type assigned (e.g. injected by Pipe Visitor)
injectedType := paramIdent.AsTypedNode.StaticType;
@@ -448,52 +490,45 @@ begin
newParams[i] := TAst.Identifier(paramIdent.Identity.AsNamed, paramIdent.Address, paramTypes[i]);
end;
newBody := Accept(Node.Body);
newBody := Accept(L.Body);
bodyType := newBody.AsTypedNode.StaticType;
methodType := TTypes.CreateMethod(paramTypes, bodyType);
finalDescriptor := TScope.CreateDescriptor(Node.Layout, FCurrentContext.Types);
finalDescriptor := TScope.CreateDescriptor(L.Layout, FCurrentContext.Types);
finally
var temp := FCurrentContext;
FCurrentContext := FCurrentContext.FParent;
temp.Free;
end;
var paramList := TParameterList.Create(newParams, Node.Parameters.Identity);
var paramList := TParameterList.Create(newParams, L.Parameters.Identity);
Result :=
TAst.LambdaExpr(
Node.Identity,
paramList,
newBody,
Node.Layout,
finalDescriptor,
Node.Upvalues,
Node.HasNestedLambdas,
Node.IsPure,
methodType
);
TAst.LambdaExpr(Node.Identity, paramList, newBody, L.Layout, finalDescriptor, L.Upvalues, L.HasNestedLambdas, L.IsPure, methodType);
end;
function TTypeChecker.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
function TTypeChecker.VisitFunctionCall(const Node: IAstNode): IAstNode;
var
newCall: IFunctionCallNode;
calleeType, retType: IStaticType;
i, j: Integer;
newCallee: IAstNode;
newArgs: TArray<IAstNode>;
argTypes: TArray<IStaticType>;
hasUnknownArgs: Boolean;
bestSig: IMethodSignature;
match: Boolean;
begin
newCallee := Accept(Node.Callee);
SetLength(newArgs, Node.Arguments.Count);
SetLength(argTypes, Node.Arguments.Count);
// Use inherited to visit Callee and Arguments first
newCall := inherited VisitFunctionCall(Node).AsFunctionCall;
// Now analyze types on the transformed children
var newCallee := newCall.Callee;
var newArgs := newCall.Arguments;
SetLength(argTypes, newArgs.Count);
hasUnknownArgs := False;
for i := 0 to Node.Arguments.Count - 1 do
for i := 0 to newArgs.Count - 1 do
begin
newArgs[i] := Accept(Node.Arguments[i]);
argTypes[i] := newArgs[i].AsTypedNode.StaticType;
if argTypes[i].Kind = stUnknown then
hasUnknownArgs := True;
@@ -535,36 +570,35 @@ begin
if Assigned(FLog) then
FLog.AddError(Format('Cannot invoke type %s as a function.', [calleeType.ToString]), Node);
var argList := TArgumentList.Create(newArgs, Node.Arguments.Identity);
Result := TAst.FunctionCall(Node.Identity, newCallee, argList, retType, Node.IsTailCall, nil, False);
// Return new node with Types
Result := TAst.FunctionCall(Node.Identity, newCallee, newArgs, retType, newCall.IsTailCall, nil, False);
end;
function TTypeChecker.VisitIfExpression(const Node: IIfExpressionNode): IAstNode;
function TTypeChecker.VisitIfExpression(const Node: IAstNode): IAstNode;
var
newCond, newThen, newElse: IAstNode;
newIf: IIfExpressionNode;
resType: IStaticType;
begin
newCond := Accept(Node.Condition);
newThen := Accept(Node.ThenBranch);
newElse := Accept(Node.ElseBranch);
newIf := inherited VisitIfExpression(Node).AsIfExpression;
// Simple promotion logic
if (newElse <> nil) then
resType := TTypeRules.Promote(newThen.AsTypedNode.StaticType, newElse.AsTypedNode.StaticType)
if (newIf.ElseBranch <> nil) then
resType := TTypeRules.Promote(newIf.ThenBranch.AsTypedNode.StaticType, newIf.ElseBranch.AsTypedNode.StaticType)
else
resType := TTypes.MakeOptional(newThen.AsTypedNode.StaticType);
resType := TTypes.MakeOptional(newIf.ThenBranch.AsTypedNode.StaticType);
Result := TAst.IfExpr(Node.Identity, newCond, newThen, newElse, resType);
Result := TAst.IfExpr(Node.Identity, newIf.Condition, newIf.ThenBranch, newIf.ElseBranch, resType);
end;
function TTypeChecker.VisitIndexer(const Node: IIndexerNode): IAstNode;
function TTypeChecker.VisitIndexer(const Node: IAstNode): IAstNode;
var
I: IIndexerNode;
newBase, newIndex: IAstNode;
baseType, elemType: IStaticType;
isOpt: Boolean;
begin
newBase := Accept(Node.Base);
newIndex := Accept(Node.Index);
I := Node.AsIndexer;
newBase := Accept(I.Base);
newIndex := Accept(I.Index);
baseType := PrepareBaseType(newBase, isOpt);
elemType := TTypes.Unknown;
@@ -577,20 +611,22 @@ begin
Result := TAst.Indexer(Node.Identity, newBase, newIndex, elemType);
end;
function TTypeChecker.VisitMemberAccess(const Node: IMemberAccessNode): IAstNode;
function TTypeChecker.VisitMemberAccess(const Node: IAstNode): IAstNode;
var
M: IMemberAccessNode;
newBase: IAstNode;
baseType, resType: IStaticType;
idx: Integer;
isOpt: Boolean;
begin
newBase := Accept(Node.Base);
M := Node.AsMemberAccess;
newBase := Accept(M.Base);
baseType := PrepareBaseType(newBase, isOpt);
resType := TTypes.Unknown;
if (baseType.Kind = stRecord) or (baseType.Kind = stRecordSeries) then
begin
idx := baseType.Definition.IndexOf(Node.Member.Value);
idx := baseType.Definition.IndexOf(M.Member.Value);
if idx >= 0 then
begin
var fieldType := TTypes.FromScalarKind(baseType.Definition.Items[idx].Value);
@@ -604,45 +640,40 @@ begin
end;
resType := ApplyOptionality(resType, isOpt);
Result := TAst.MemberAccess(Node.Identity, newBase, Node.Member, resType);
Result := TAst.MemberAccess(Node.Identity, newBase, M.Member, resType);
end;
function TTypeChecker.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode;
function TTypeChecker.VisitRecordLiteral(const Node: IAstNode): IAstNode;
var
R: IRecordLiteralNode;
i: Integer;
newFields: TArray<IRecordFieldNode>;
fieldTypes: TArray<TPair<IKeyword, IStaticType>>;
scalarFieldTypes: TArray<TPair<IKeyword, TScalar.TKind>>;
isScalar: Boolean;
valType: IStaticType;
key: IKeyword;
visitedValue: IAstNode;
newFieldList: IRecordFieldList;
begin
SetLength(newFields, Node.Fields.Count);
SetLength(fieldTypes, Node.Fields.Count);
SetLength(scalarFieldTypes, Node.Fields.Count);
R := Node.AsRecordLiteral;
// Transform fields using inherited recursion
newFieldList := inherited VisitRecordFieldList(R.Fields).AsRecordFieldList;
// Now analyze the transformed fields
var count := newFieldList.Count;
SetLength(fieldTypes, count);
SetLength(scalarFieldTypes, count);
isScalar := True;
for i := 0 to Node.Fields.Count - 1 do
for i := 0 to count - 1 do
begin
var oldField := Node.Fields[i];
visitedValue := Accept(oldField.Value);
// Keys are guaranteed to be keywords by parser
key := oldField.Key.Value;
// Recreate the field node with the typed value
newFields[i] := TAst.RecordField(oldField.Identity, oldField.Key, visitedValue);
// Analyze Type
valType := visitedValue.AsTypedNode.StaticType;
var field := newFieldList[i];
key := field.Key.Value;
valType := field.Value.AsTypedNode.StaticType;
// Check if strict scalar (no optionals allowed in packed ScalarRecord)
if (valType.Kind in [stOrdinal, stFloat, stBoolean, stDateTime, stKeyword]) and (not valType.IsOptional) then
begin
var kind: TScalar.TKind;
// Map StaticType Kind to Scalar Kind
case valType.Kind of
stOrdinal: kind := TScalar.TKind.Ordinal;
stFloat: kind := TScalar.TKind.Float;
@@ -650,7 +681,7 @@ begin
stDateTime: kind := TScalar.TKind.DateTime;
stKeyword: kind := TScalar.TKind.Keyword;
else
kind := TScalar.TKind.Ordinal; // Should not happen given check above
kind := TScalar.TKind.Ordinal;
end;
scalarFieldTypes[i] := TPair<IKeyword, TScalar.TKind>.Create(key, kind);
end
@@ -667,7 +698,7 @@ begin
var genericDef: IGenericRecordDefinition := nil;
var resultType: IStaticType;
if isScalar and (Length(newFields) > 0) then
if isScalar and (count > 0) then
begin
scalarDef := TKeywordMappingRegistry<TScalar.TKind>.Intern(scalarFieldTypes);
resultType := TTypes.CreateRecord(scalarDef);
@@ -678,16 +709,17 @@ begin
resultType := TTypes.CreateGenericRecord(genericDef);
end;
newFieldList := TRecordFieldList.Create(newFields, Node.Fields.Identity);
Result := TAst.RecordLiteral(Node.Identity, newFieldList, scalarDef, genericDef, resultType);
end;
function TTypeChecker.VisitCreateSeries(const Node: ICreateSeriesNode): IAstNode;
function TTypeChecker.VisitCreateSeries(const Node: IAstNode): IAstNode;
var
C: ICreateSeriesNode;
elemType: IStaticType;
def: string;
begin
def := Node.Definition;
C := Node.AsCreateSeries;
def := C.Definition;
// Simple heuristic for type
if def.StartsWith('[') then
elemType := TTypes.CreateRecord(nil) // Placeholder, normally parses JSON
@@ -697,12 +729,12 @@ begin
Result := TAst.CreateSeries(Node.Identity.AsDefinition, TTypes.CreateSeries(elemType));
end;
function TTypeChecker.VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode;
function TTypeChecker.VisitSeriesLength(const Node: IAstNode): IAstNode;
begin
Result := TAst.SeriesLength(Node.Identity, Accept(Node.Series).AsIdentifier, TTypes.Ordinal);
Result := TAst.SeriesLength(Node.Identity, Accept(Node.AsSeriesLength.Series).AsIdentifier, TTypes.Ordinal);
end;
function TTypeChecker.VisitNop(const Node: INopNode): IAstNode;
function TTypeChecker.VisitNop(const Node: IAstNode): IAstNode;
begin
Result := TAst.Nop(Node.Identity, TTypes.Void);
end;
@@ -711,14 +743,15 @@ end;
// PIPE IMPLEMENTATION (Type Checking)
// =============================================================================
function TTypeChecker.VisitPipeInput(const Node: IPipeInputNode): IAstNode;
function TTypeChecker.VisitPipeInput(const Node: IAstNode): IAstNode;
var
P: IPipeInputNode;
newSource: IIdentifierNode;
sourceType: IStaticType;
newSelectors: TArray<IKeywordNode>;
i: Integer;
begin
newSource := Accept(Node.StreamSource).AsIdentifier;
P := Node.AsPipeInput;
// Transform source identifier (resolves type)
newSource := Accept(P.StreamSource).AsIdentifier;
sourceType := newSource.AsTypedNode.StaticType;
// 1. Verify Source is a Series-compatible type
@@ -736,7 +769,7 @@ begin
begin
// 2. Verify Selectors exist in Record Definition
var def := sourceType.Definition;
for var sel in Node.Selectors do
for var sel in P.Selectors do
begin
if def.IndexOf(sel.Value) < 0 then
begin
@@ -747,16 +780,13 @@ begin
end;
end;
// Reconstruct list simply to pass through
SetLength(newSelectors, Node.Selectors.Count);
for i := 0 to Node.Selectors.Count - 1 do
newSelectors[i] := Node.Selectors[i];
Result := TAst.PipeInput(newSource, TAst.PipeSelectorList(newSelectors, Node.Selectors.Identity.Location), Node.Identity.Location);
// Reuse selectors (they are just keywords, no type checking needed)
Result := TAst.PipeInput(newSource, P.Selectors, Node.Identity.Location);
end;
function TTypeChecker.VisitPipe(const Node: IPipeNode): IAstNode;
function TTypeChecker.VisitPipe(const Node: IAstNode): IAstNode;
var
P: IPipeNode;
i, k: Integer;
inputNode: IPipeInputNode;
newInputs: TArray<IPipeInputNode>;
@@ -766,13 +796,15 @@ var
newParams: TArray<IIdentifierNode>;
inferredType: IStaticType;
begin
SetLength(newInputs, Node.Inputs.Count);
P := Node.AsPipe;
SetLength(newInputs, P.Inputs.Count);
paramTypes := TList<IStaticType>.Create;
try
// 1. Visit Inputs and collect types for Lambda parameters
for i := 0 to Node.Inputs.Count - 1 do
for i := 0 to P.Inputs.Count - 1 do
begin
inputNode := Accept(Node.Inputs[i]).AsPipeInput;
// Recurse on inputs to resolve their sources
inputNode := Accept(P.Inputs[i]).AsPipeInput;
newInputs[i] := inputNode;
streamType := inputNode.StreamSource.AsTypedNode.StaticType;
@@ -803,7 +835,7 @@ begin
end;
// 2. Prepare Lambda with Inferred Types
lambda := Node.Transformation;
lambda := P.Transformation;
if lambda.Parameters.Count <> paramTypes.Count then
begin
@@ -871,7 +903,7 @@ begin
pipeType := TTypes.Unknown;
end;
Result := TAst.Pipe(Node.Identity, TPipeInputList.Create(newInputs, Node.Inputs.Identity), typedLambda, pipeType);
Result := TAst.Pipe(Node.Identity, TPipeInputList.Create(newInputs, P.Inputs.Identity), typedLambda, pipeType);
finally
paramTypes.Free;
end;