Static specialization WIP

This commit is contained in:
Michael Schimmel
2025-11-19 14:38:40 +01:00
parent c129c1a3ae
commit 138e7ac454
26 changed files with 2349 additions and 1110 deletions
+141 -77
View File
@@ -36,8 +36,6 @@ type
function VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; override;
function VisitIfExpression(const Node: IIfExpressionNode): IAstNode; override;
function VisitTernaryExpression(const Node: ITernaryExpressionNode): IAstNode; override;
function VisitBinaryExpression(const Node: IBinaryExpressionNode): IAstNode; override;
function VisitUnaryExpression(const Node: IUnaryExpressionNode): IAstNode; override;
function VisitMemberAccess(const Node: IMemberAccessNode): IAstNode; override;
function VisitIndexer(const Node: IIndexerNode): IAstNode; override;
function VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode; override;
@@ -137,36 +135,60 @@ var
initType: IStaticType;
newInitializer, newIdent: IAstNode;
adr: TResolvedAddress;
lambdaNode: ILambdaExpressionNode;
placeholderType: IStaticType;
i: Integer;
begin
// 1. Visit Initializer first (if it exists)
// 1. Get the address from the bound identifier (Binder did this)
adr := Node.Identifier.Address;
initType := TTypes.Unknown; // Default
// 2. Check for recursive lambda and bootstrap the type
placeholderType := nil;
if (Node.Initializer <> nil) and (Node.Initializer.Kind = akLambdaExpression) then
begin
lambdaNode := Node.Initializer.AsLambdaExpression;
// Create a placeholder method type based on the *unvisited* lambda's parameter *count*.
var paramTypes: TArray<IStaticType>;
SetLength(paramTypes, Length(lambdaNode.Parameters));
for i := 0 to High(paramTypes) do
paramTypes[i] := TTypes.Unknown;
// Create the placeholder (Return type is also Unknown for now)
placeholderType := TTypes.CreateMethod(paramTypes, TTypes.Unknown);
// 3. *Update the descriptor* with the placeholder *before* visiting the initializer
FCurrentDescriptor.UpdateType(adr.SlotIndex, placeholderType);
initType := placeholderType; // Store this
end;
// 4. Visit Initializer (if it exists)
if Assigned(Node.Initializer) then
newInitializer := Accept(Node.Initializer)
else
newInitializer := nil;
// 2. Get initializer type
// 5. Get the *final* inferred initializer type
if Assigned(newInitializer) then
initType := newInitializer.AsTypedNode.StaticType
else
initType := TTypes.Unknown; // (def fib)
else if not Assigned(placeholderType) then // only if not already set
initType := TTypes.Unknown; // (def f) - no initializer
// 3. Get the address from the bound identifier
adr := Node.Identifier.Address;
// 4. Update the type in the scope descriptor (which was set to Unknown by the binder).
// 6. *Re-update* the type in the scope descriptor with the final, inferred type.
if initType.Kind <> stUnknown then
FCurrentDescriptor.UpdateType(adr.SlotIndex, initType);
// 5. Create the new (typed) identifier node
// 7. Create the new (typed) identifier node
newIdent := TAst.Identifier(Node.Identifier.Name, adr, initType);
// 6. Create the new VariableDeclaration node using the factory
// 8. Create the new VariableDeclaration node using the factory
Result :=
TAst.VarDecl(
newIdent.AsIdentifier,
newInitializer,
initType,
Node.IsBoxed // 7. Copy runtime flags (IsBoxed) via interface
Node.IsBoxed // 9. Copy runtime flags
);
end;
@@ -175,32 +197,65 @@ var
targetType, sourceType: IStaticType;
newIdent, newValue: IAstNode;
adr: TResolvedAddress;
lambdaNode: ILambdaExpressionNode;
placeholderType: IStaticType;
i: Integer;
begin
// 1. Visit children first (Identifier, Value)
newValue := Accept(Node.Value);
// 1. Visit Identifier *first* to get its address and current type
newIdent := Accept(Node.Identifier);
// 2. Get types
targetType := newIdent.AsTypedNode.StaticType;
adr := newIdent.AsIdentifier.Address;
// 2. Check for recursive lambda assignment
placeholderType := nil;
if (Node.Value <> nil) and (Node.Value.Kind = akLambdaExpression) then
begin
lambdaNode := Node.Value.AsLambdaExpression;
// Create a placeholder (only if the target is not already a method type)
if (targetType.Kind <> stMethod) then
begin
var paramTypes: TArray<IStaticType>;
SetLength(paramTypes, Length(lambdaNode.Parameters));
for i := 0 to High(paramTypes) do
paramTypes[i] := TTypes.Unknown; // We infer param types later
placeholderType := TTypes.CreateMethod(paramTypes, TTypes.Unknown);
// 3. *Update the descriptor* with the placeholder *before* visiting the value
FCurrentDescriptor.UpdateType(adr.SlotIndex, placeholderType);
targetType := placeholderType;
end;
end;
// 4. Visit Value
newValue := Accept(Node.Value);
sourceType := newValue.AsTypedNode.StaticType;
// 3. Check assignment
// 5. Check assignment
if not TTypeRules.CanAssign(targetType, sourceType) then
raise ETypeException.CreateFmt('Cannot assign type %s to %s', [sourceType.ToString, targetType.ToString]);
// 4. If the target was 'Unknown' (from 'def'), update the descriptor
// with the new, inferred type. This enables recursion.
if (targetType.Kind = stUnknown) and (sourceType.Kind <> stUnknown) then
begin
adr := newIdent.AsIdentifier.Address;
FCurrentDescriptor.UpdateType(adr.SlotIndex, sourceType);
// If target was unknown, try promoting
if (targetType.Kind = stUnknown) then
begin
if not TTypeRules.CanAssign(sourceType, targetType) then // Check reverse
raise ETypeException.CreateFmt('Cannot assign type %s to %s', [sourceType.ToString, targetType.ToString]);
end
else
raise ETypeException.CreateFmt('Cannot assign type %s to %s', [sourceType.ToString, targetType.ToString]);
end;
// 6. If the target was 'Unknown' or a 'Placeholder', update the descriptor
// with the new, final inferred type.
if ((targetType.Kind = stUnknown) or Assigned(placeholderType)) and (sourceType.Kind <> stUnknown) then
begin
FCurrentDescriptor.UpdateType(adr.SlotIndex, sourceType);
// Re-create the identifier node *with the new type*
newIdent := TAst.Identifier(newIdent.AsIdentifier.Name, adr, sourceType);
targetType := sourceType;
end;
// 5. Create the new Assignment node
// 7. Create the new Assignment node
Result := TAst.Assign(newIdent.AsIdentifier, newValue, targetType);
end;
@@ -255,15 +310,28 @@ end;
function TTypeChecker.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
var
calleeType, retType: IStaticType;
i: Integer;
i, j: Integer;
newCallee: IAstNode;
newArgs: TArray<IAstNode>;
argTypes: TArray<IStaticType>;
hasUnknownArgs: Boolean;
bestSig: IMethodSignature;
sig: IMethodSignature;
match: Boolean;
begin
// 1. Visit children first (bottom-up)
newCallee := Accept(Node.Callee);
SetLength(newArgs, Length(Node.Arguments));
SetLength(argTypes, Length(Node.Arguments));
hasUnknownArgs := False;
for i := 0 to High(Node.Arguments) do
begin
newArgs[i] := Accept(Node.Arguments[i]);
argTypes[i] := newArgs[i].AsTypedNode.StaticType;
if argTypes[i].Kind = stUnknown then
hasUnknownArgs := True;
end;
// 2. Get callee type (now inferred)
calleeType := newCallee.AsTypedNode.StaticType;
@@ -272,24 +340,58 @@ begin
// 3. Perform type checking
if calleeType.Kind = TStaticTypeKind.stMethod then
begin
var signature := calleeType.Signature;
if Length(newArgs) <> Length(signature.ParamTypes) then
raise ETypeException.CreateFmt('Function expects %d arguments, but got %d', [Length(signature.ParamTypes), Length(newArgs)]);
retType := signature.ReturnType;
// Check argument types
for i := 0 to High(newArgs) do
// If any argument is Unknown, we cannot resolve overloads.
// The return type remains Unknown.
if not hasUnknownArgs then
begin
var argType := newArgs[i].AsTypedNode.StaticType;
var paramType := signature.ParamTypes[i];
if not TTypeRules.CanAssign(paramType, argType) then
bestSig := nil;
for sig in calleeType.Signatures do
begin
// Check 1: Argument count
if Length(sig.ParamTypes) <> Length(argTypes) then
continue;
// Check 2: Argument types (CanAssign)
match := True;
for j := 0 to High(argTypes) do
begin
if not TTypeRules.CanAssign(sig.ParamTypes[j], argTypes[j]) then
begin
match := False;
break; // This signature doesn't match
end;
end;
// Check 3: Found first match
if match then
begin
// This is the "dumb" checker logic: first match wins.
// A "smarter" checker would find the *best* match.
bestSig := sig;
break;
end;
end; // for sig
// Check 4: Handle results
if Assigned(bestSig) then
begin
retType := bestSig.ReturnType;
end
else
begin
// No signature matched, even with known types. This is an error.
var argsStr: string := '';
for i := 0 to High(argTypes) do
argsStr := argsStr + argTypes[i].ToString + ' ';
raise ETypeException
.CreateFmt('Cannot assign argument %d (type %s) to parameter (type %s)', [i, argType.ToString, paramType.ToString]);
.CreateFmt('No matching signature for call with args (%s) found on method %s', [argsStr, calleeType.ToString]);
end;
end;
// else: hasUnknownArgs is True, so retType remains Unknown (as set in step 2)
end
else if calleeType.Kind <> TStaticTypeKind.stUnknown then
raise ETypeException.CreateFmt('Cannot invoke type %s as a function.', [calleeType.ToString]);
// else: calleeType is Unknown (e.g. recursive call or unbound symbol), retType remains Unknown.
// 4. Create the new (typed) call node using the factory
Result :=
@@ -374,44 +476,6 @@ begin
Result := TAst.TernaryExpr(newCond, newThen, newElse, resultType);
end;
function TTypeChecker.VisitBinaryExpression(const Node: IBinaryExpressionNode): IAstNode;
var
leftType, rightType, resultType: IStaticType;
newLeft, newRight: IAstNode;
begin
// 1. Visit children
newLeft := Accept(Node.Left);
newRight := Accept(Node.Right);
// 2. Get types
leftType := newLeft.AsTypedNode.StaticType;
rightType := newRight.AsTypedNode.StaticType;
// 3. Resolve
resultType := TTypeRules.ResolveBinaryOp(Node.Operator, leftType, rightType);
// 4. Create new node
Result := TAst.BinaryExpr(newLeft, Node.Operator, newRight, resultType);
end;
function TTypeChecker.VisitUnaryExpression(const Node: IUnaryExpressionNode): IAstNode;
var
rightType, resultType: IStaticType;
newRight: IAstNode;
begin
// 1. Visit children
newRight := Accept(Node.Right);
// 2. Get types
rightType := newRight.AsTypedNode.StaticType;
// 3. Resolve
resultType := TTypeRules.ResolveUnaryOp(Node.Operator, rightType);
// 4. Create new node
Result := TAst.UnaryExpr(Node.Operator, newRight, resultType);
end;
function TTypeChecker.VisitMemberAccess(const Node: IMemberAccessNode): IAstNode;
var
baseType, elemType: IStaticType;