unit Myc.Ast.Types; interface uses System.SysUtils, System.Generics.Collections, Myc.Data.Scalar, Myc.Data.Keyword; type IStaticType = interface; // Defines the categories of types available in the language. TStaticTypeKind = ( stUnknown, // Used during inference before a type is known stVoid, stOrdinal, // Int64 stFloat, // Double stBoolean, stDateTime, stText, stKeyword, stMethod, stSeries, stRecord, stRecordSeries, stGenericRecord ); TStaticTypeKindHelper = record helper for TStaticTypeKind public function ToString: string; end; // Defines the signature of a method IMethodSignature = interface {$region 'private'} function GetParamTypes: TArray; function GetReturnType: IStaticType; {$endregion} property ParamTypes: TArray read GetParamTypes; property ReturnType: IStaticType read GetReturnType; function GetHashCode: Integer; end; // Defines a mapping for generic record fields (Key -> StaticType) IGenericRecordDefinition = IKeywordMapping; TGenericRecordRegistry = TKeywordMappingRegistry; // Represents a complete static type definition. IStaticType = interface {$region 'private'} function GetKind: TStaticTypeKind; function GetElementType: IStaticType; function GetSignatures: TArray; function GetDefinition: IScalarRecordDefinition; function GetGenericDefinition: IGenericRecordDefinition; {$endregion} // The kind of type (e.g., Ordinal, Series, etc.) property Kind: TStaticTypeKind read GetKind; // The element type (if Kind = stSeries) property ElementType: IStaticType read GetElementType; // The signatures (if Kind = stMethod). // This array contains one entry for simple methods, // and multiple entries for overloaded functions (like RTL). property Signatures: TArray read GetSignatures; // The definition (if Kind = stRecord or stRecordSeries) property Definition: IScalarRecordDefinition read GetDefinition; // The definition (if Kind = stGenericRecord) property GenericDefinition: IGenericRecordDefinition read GetGenericDefinition; // Checks for type equality function IsEqual(const Other: IStaticType): Boolean; function ToString: string; function GetHashCode: Integer; end; ETypeException = class(Exception); // Factory and Flyweight access for static types. // Use this record to create or access all IStaticType instances. TTypes = record private class var FUnknown: IStaticType; class var FVoid: IStaticType; class var FOrdinal: IStaticType; class var FFloat: IStaticType; class var FBoolean: IStaticType; class var FDateTime: IStaticType; class var FText: IStaticType; class var FKeyword: IStaticType; class constructor Create; public // Flyweight accessors for simple types class property Unknown: IStaticType read FUnknown; class property Void: IStaticType read FVoid; class property Ordinal: IStaticType read FOrdinal; class property Float: IStaticType read FFloat; class property Boolean: IStaticType read FBoolean; class property DateTime: IStaticType read FDateTime; class property Text: IStaticType read FText; class property Keyword: IStaticType read FKeyword; // Factory functions for complex types class function CreateSeries(const AElementType: IStaticType): IStaticType; static; class function CreateMethod(const AParamTypes: TArray; const AReturnType: IStaticType): IStaticType; static; class function CreateMethodSet(const ASignatures: TArray): IStaticType; static; class function CreateRecord(const ADef: IScalarRecordDefinition): IStaticType; static; class function CreateRecordSeries(const ADef: IScalarRecordDefinition): IStaticType; static; class function CreateGenericRecord(const ADef: IGenericRecordDefinition): IStaticType; static; class function FromScalarKind(AKind: TScalar.TKind): IStaticType; static; end; // Defines the rules of the type system. TTypeRules = record public class function Promote(const A, B: IStaticType): IStaticType; static; // Checks if a value of type 'Source' can be assigned to 'Target'. class function CanAssign(const Target, Source: IStaticType): Boolean; static; // Determines the result type of a binary operation. // Raises ETypeException on failure. class function ResolveBinaryOp(Op: TScalar.TBinaryOp; const Left, Right: IStaticType): IStaticType; static; // Determines the result type of a unary operation. class function ResolveUnaryOp(Op: TScalar.TUnaryOp; const Right: IStaticType): IStaticType; static; end; TMethodSignature = class(TInterfacedObject, IMethodSignature) private FParamTypes: TArray; FReturnType: IStaticType; function GetParamTypes: TArray; function GetReturnType: IStaticType; public constructor Create(const AParamTypes: TArray; const AReturnType: IStaticType); function GetHashCode: Integer; override; end; implementation uses System.Generics.Defaults, System.Hash; { TStaticTypeKindHelper } function TStaticTypeKindHelper.ToString: string; begin case Self of stUnknown: Result := 'Unknown'; stVoid: Result := 'Void'; stOrdinal: Result := 'Ordinal'; stFloat: Result := 'Float'; stBoolean: Result := 'Boolean'; stDateTime: Result := 'DateTime'; stText: Result := 'Text'; stKeyword: Result := 'Keyword'; stMethod: Result := 'Method'; stSeries: Result := 'Series'; stRecord: Result := 'Record'; stRecordSeries: Result := 'RecordSeries'; stGenericRecord: Result := 'GenericRecord'; else Result := 'ErrorType'; end; end; // --- Abstract Base Implementation --- type TAbstractStaticType = class(TInterfacedObject, IStaticType) protected // IStaticType (default implementations for non-applicable properties) function GetKind: TStaticTypeKind; virtual; abstract; function GetElementType: IStaticType; virtual; function GetSignatures: TArray; virtual; function GetDefinition: IScalarRecordDefinition; virtual; function GetGenericDefinition: IGenericRecordDefinition; virtual; function IsEqual(const Other: IStaticType): Boolean; virtual; function GetHashCode: Integer; override; abstract; function ToString: string; override; end; function TAbstractStaticType.GetElementType: IStaticType; begin Result := nil; end; function TAbstractStaticType.GetSignatures: TArray; begin Result := nil; end; function TAbstractStaticType.GetDefinition: IScalarRecordDefinition; begin // Return an empty/invalid definition Result := Default(IScalarRecordDefinition); end; function TAbstractStaticType.GetGenericDefinition: IGenericRecordDefinition; begin Result := nil; end; function TAbstractStaticType.IsEqual(const Other: IStaticType): Boolean; begin // Default implementation: types are equal if their kinds are equal. // Complex types (Series, Record, Method) MUST override this. if not Assigned(Other) then exit(False); Result := (GetKind = Other.Kind); end; function TAbstractStaticType.ToString: string; begin Result := GetKind.ToString; end; // --- Simple (Flyweight) Type Implementations --- type TSimpleStaticType = class(TAbstractStaticType) private FKind: TStaticTypeKind; public constructor Create(AKind: TStaticTypeKind); function GetKind: TStaticTypeKind; override; function GetHashCode: Integer; override; end; constructor TSimpleStaticType.Create(AKind: TStaticTypeKind); begin inherited Create; FKind := AKind; end; function TSimpleStaticType.GetKind: TStaticTypeKind; begin Result := FKind; end; function TSimpleStaticType.GetHashCode: Integer; begin // Simple types only hash their kind Result := Ord(FKind); end; // --- Complex Type Implementations --- type TSeriesType = class(TAbstractStaticType) private FElementType: IStaticType; public constructor Create(AElementType: IStaticType); function GetKind: TStaticTypeKind; override; function GetElementType: IStaticType; override; function IsEqual(const Other: IStaticType): Boolean; override; function GetHashCode: Integer; override; function ToString: string; override; end; constructor TSeriesType.Create(AElementType: IStaticType); begin inherited Create; FElementType := AElementType; end; function TSeriesType.GetKind: TStaticTypeKind; begin Result := stSeries; end; function TSeriesType.GetElementType: IStaticType; begin Result := FElementType; end; function TSeriesType.IsEqual(const Other: IStaticType): Boolean; begin Result := (Assigned(Other)) and (Other.Kind = stSeries) and (Self.FElementType.IsEqual(Other.ElementType)); end; function TSeriesType.GetHashCode: Integer; begin // Consistent with IsEqual Result := Ord(stSeries); if Assigned(FElementType) then begin // Combine hash of stSeries with hash of element type var hash := FElementType.GetHashCode; Result := THashBobJenkins.GetHashValue(Hash, SizeOf(Integer), Result); end; end; function TSeriesType.ToString: string; begin Result := 'Series<' + FElementType.ToString + '>'; end; { TMethodSignature } constructor TMethodSignature.Create(const AParamTypes: TArray; const AReturnType: IStaticType); begin inherited Create; FParamTypes := AParamTypes; FReturnType := AReturnType; end; function TMethodSignature.GetParamTypes: TArray; begin Result := FParamTypes; end; function TMethodSignature.GetReturnType: IStaticType; begin Result := FReturnType; end; function TMethodSignature.GetHashCode: Integer; var i: Integer; begin // Consistent with TMethodType.IsEqual Result := 0; if Assigned(FReturnType) then Result := FReturnType.GetHashCode; for i := 0 to High(FParamTypes) do begin if Assigned(FParamTypes[i]) then begin var hash := FParamTypes[i].GetHashCode; Result := THashBobJenkins.GetHashValue(hash, SizeOf(Integer), Result); end; end; end; // --- type TMethodType = class(TAbstractStaticType) private FSignatures: TArray; function SignatureToString(const Sig: IMethodSignature): string; public constructor Create(const ASignatures: TArray); function GetKind: TStaticTypeKind; override; function GetSignatures: TArray; override; function IsEqual(const Other: IStaticType): Boolean; override; function GetHashCode: Integer; override; function ToString: string; override; end; constructor TMethodType.Create(const ASignatures: TArray); begin inherited Create; Assert(Length(ASignatures) > 0, 'Cannot create a method type with zero signatures'); FSignatures := ASignatures; end; function TMethodType.GetKind: TStaticTypeKind; begin Result := stMethod; end; function TMethodType.GetSignatures: TArray; begin Result := FSignatures; end; function TMethodType.SignatureToString(const Sig: IMethodSignature): string; var i: Integer; paramStr: string; begin paramStr := ''; for i := 0 to High(Sig.ParamTypes) do begin paramStr := paramStr + Sig.ParamTypes[i].ToString; if i < High(Sig.ParamTypes) then paramStr := paramStr + ', '; end; Result := Format('Method(%s): %s', [paramStr, Sig.ReturnType.ToString]); end; function TMethodType.IsEqual(const Other: IStaticType): Boolean; var i, j: Integer; begin if (not Assigned(Other)) or (Other.Kind <> stMethod) then exit(False); var otherSigs := Other.Signatures; if Length(Self.FSignatures) <> Length(otherSigs) then exit(False); // This is complex. For now, assume equality means identical sets, // which requires comparing O(N^2) signatures if order doesn't matter. // Let's assume order *does* matter for equality to keep this simple (O(N)). // Note: A better IsEqual would compare hashes of signatures. for i := 0 to High(Self.FSignatures) do begin var sig1 := Self.FSignatures[i]; var sig2 := otherSigs[i]; if not sig1.ReturnType.IsEqual(sig2.ReturnType) then exit(False); if Length(sig1.ParamTypes) <> Length(sig2.ParamTypes) then exit(False); for j := 0 to High(sig1.ParamTypes) do begin if not sig1.ParamTypes[j].IsEqual(sig2.ParamTypes[j]) then exit(False); end; end; Result := True; end; function TMethodType.GetHashCode: Integer; var sig: IMethodSignature; begin // Consistent with IsEqual Result := Ord(stMethod); for sig in FSignatures do begin if Assigned(sig) then begin var hash := sig.GetHashCode; Result := THashBobJenkins.GetHashValue(hash, SizeOf(Integer), Result); end; end; end; function TMethodType.ToString: string; var sb: TStringBuilder; sig: IMethodSignature; begin if Length(FSignatures) = 1 then begin Result := SignatureToString(FSignatures[0]); end else begin // Handle overloaded methods sb := TStringBuilder.Create; try sb.Append('Method{'); for sig in FSignatures do begin sb.Append('('); sb.Append(SignatureToString(sig)); sb.Append('), '); end; sb.Remove(sb.Length - 2, 2); // Remove last ', ' sb.Append('}'); Result := sb.ToString; finally sb.Free; end; end; end; // --- type // Represents stRecord and stRecordSeries (Scalar Records) TRecordType = class(TAbstractStaticType) private FKind: TStaticTypeKind; FDefinition: IScalarRecordDefinition; public constructor Create(AKind: TStaticTypeKind; ADef: IScalarRecordDefinition); function GetKind: TStaticTypeKind; override; function GetDefinition: IScalarRecordDefinition; override; function IsEqual(const Other: IStaticType): Boolean; override; function GetHashCode: Integer; override; function ToString: string; override; end; constructor TRecordType.Create(AKind: TStaticTypeKind; ADef: IScalarRecordDefinition); begin inherited Create; Assert(AKind in [stRecord, stRecordSeries]); FKind := AKind; FDefinition := ADef; end; function TRecordType.GetKind: TStaticTypeKind; begin Result := FKind; end; function TRecordType.GetDefinition: IScalarRecordDefinition; begin Result := FDefinition; end; function TRecordType.IsEqual(const Other: IStaticType): Boolean; var i: Integer; begin if (not Assigned(Other)) or (Other.Kind <> GetKind) then exit(False); var otherDef := Other.Definition; if (not Assigned(Self.FDefinition)) or (not Assigned(otherDef)) then exit(False); // Should not happen if Kind is equal, but defensive check if Length(Self.FDefinition.Fields) <> Length(otherDef.Fields) then exit(False); for i := 0 to High(Self.FDefinition.Fields) do begin // Use fast interface comparison for Keys if (Self.FDefinition.Fields[i].Key <> otherDef.Fields[i].Key) or (Self.FDefinition.Fields[i].Value <> otherDef.Fields[i].Value) then exit(False); end; Result := True; end; function TRecordType.GetHashCode: Integer; var field: TPair; begin // Consistent with IsEqual Result := Ord(GetKind); if Assigned(FDefinition) then begin for field in FDefinition.Fields do begin // Hash the Keyword pointer (fast, from flyweight) var hash := TEqualityComparer.Default.GetHashCode(field.Key); Result := THashBobJenkins.GetHashValue(hash, SizeOf(Pointer), Result); // Hash the TScalar.TKind value var data := Ord(field.Value); Result := THashBobJenkins.GetHashValue(data, SizeOf(Byte), Result); end; end; end; function TRecordType.ToString: string; var i: Integer; begin Result := GetKind.ToString + '{'; if Assigned(FDefinition) then begin for i := 0 to High(FDefinition.Fields) do begin Result := Result + FDefinition.Fields[i].Key.Name + ': ' + FDefinition.Fields[i].Value.ToString; if i < High(FDefinition.Fields) then Result := Result + ', '; end; end; Result := Result + '}'; end; // --- type TGenericRecordType = class(TAbstractStaticType) private FDefinition: IGenericRecordDefinition; public constructor Create(const ADef: IGenericRecordDefinition); function GetKind: TStaticTypeKind; override; function GetGenericDefinition: IGenericRecordDefinition; override; function IsEqual(const Other: IStaticType): Boolean; override; function GetHashCode: Integer; override; function ToString: string; override; end; constructor TGenericRecordType.Create(const ADef: IGenericRecordDefinition); begin inherited Create; FDefinition := ADef; end; function TGenericRecordType.GetKind: TStaticTypeKind; begin Result := stGenericRecord; end; function TGenericRecordType.GetGenericDefinition: IGenericRecordDefinition; begin Result := FDefinition; end; function TGenericRecordType.IsEqual(const Other: IStaticType): Boolean; var i: Integer; begin if (not Assigned(Other)) or (Other.Kind <> stGenericRecord) then exit(False); var otherDef := Other.GenericDefinition; if (not Assigned(Self.FDefinition)) or (not Assigned(otherDef)) then exit(False); if Length(Self.FDefinition.Fields) <> Length(otherDef.Fields) then exit(False); for i := 0 to High(Self.FDefinition.Fields) do begin // Compare keys (fast) and static types (must use IsEqual) if (Self.FDefinition.Fields[i].Key <> otherDef.Fields[i].Key) or (not Self.FDefinition.Fields[i].Value.IsEqual(otherDef.Fields[i].Value)) then exit(False); end; Result := True; end; function TGenericRecordType.GetHashCode: Integer; var field: TPair; begin // Consistent with IsEqual Result := Ord(stGenericRecord); if Assigned(FDefinition) then begin for field in FDefinition.Fields do begin // Hash the Keyword pointer (fast, from flyweight) var data := TEqualityComparer.Default.GetHashCode(field.Key); Result := THashBobJenkins.GetHashValue(data, SizeOf(Pointer), Result); // Hash the IStaticType value if Assigned(field.Value) then begin var hash := field.Value.GetHashCode; Result := THashBobJenkins.GetHashValue(hash, SizeOf(Integer), Result); end; end; end; end; function TGenericRecordType.ToString: string; var i: Integer; begin Result := GetKind.ToString + '{'; if Assigned(FDefinition) then begin for i := 0 to High(FDefinition.Fields) do begin Result := Result + FDefinition.Fields[i].Key.Name + ': ' + FDefinition.Fields[i].Value.ToString; if i < High(FDefinition.Fields) then Result := Result + ', '; end; end; Result := Result + '}'; end; { TTypes (Factory) } class constructor TTypes.Create; begin // Create the flyweight singletons FUnknown := TSimpleStaticType.Create(stUnknown); FVoid := TSimpleStaticType.Create(stVoid); FOrdinal := TSimpleStaticType.Create(stOrdinal); FFloat := TSimpleStaticType.Create(stFloat); FBoolean := TSimpleStaticType.Create(stBoolean); FDateTime := TSimpleStaticType.Create(stDateTime); FText := TSimpleStaticType.Create(stText); FKeyword := TSimpleStaticType.Create(stKeyword); end; class function TTypes.CreateMethod(const AParamTypes: TArray; const AReturnType: IStaticType): IStaticType; var sig: IMethodSignature; sigs: TArray; begin // This is now a convenience helper for CreateMethodSet sig := TMethodSignature.Create(AParamTypes, AReturnType); SetLength(sigs, 1); sigs[0] := sig; Result := TMethodType.Create(sigs); end; class function TTypes.CreateMethodSet(const ASignatures: TArray): IStaticType; begin // This is the new primary factory for method types Result := TMethodType.Create(ASignatures); end; class function TTypes.CreateRecord(const ADef: IScalarRecordDefinition): IStaticType; begin Result := TRecordType.Create(stRecord, ADef); end; class function TTypes.CreateRecordSeries(const ADef: IScalarRecordDefinition): IStaticType; begin Result := TRecordType.Create(stRecordSeries, ADef); end; class function TTypes.CreateGenericRecord(const ADef: IGenericRecordDefinition): IStaticType; begin Result := TGenericRecordType.Create(ADef); end; class function TTypes.CreateSeries(const AElementType: IStaticType): IStaticType; begin Result := TSeriesType.Create(AElementType); end; class function TTypes.FromScalarKind(AKind: TScalar.TKind): IStaticType; begin case AKind of TScalar.TKind.Ordinal: Result := FOrdinal; TScalar.TKind.Float: Result := FFloat; TScalar.TKind.Keyword: Result := FKeyword; TScalar.TKind.Boolean: Result := FBoolean; TScalar.TKind.DateTime: Result := FDateTime; else raise ETypeException.Create('Cannot convert invalid TScalar.TKind to TStaticType.'); end; end; { TTypeRules } class function TTypeRules.CanAssign(const Target, Source: IStaticType): Boolean; begin // Basic nil-check for safety. if (not Assigned(Target)) or (not Assigned(Source)) then exit(False); // During inference, allow assignment involving Unknown if (Target.Kind = stUnknown) or (Source.Kind = stUnknown) then exit(True); // Types are always assignable to themselves (identity). if Target.IsEqual(Source) then exit(True); // Allow assigning an integer (Ordinal) to a float variable. if (Target.Kind = stFloat) and (Source.Kind = stOrdinal) then exit(True); // Allow assigning Boolean to Ordinal (0/1) if (Target.Kind = stOrdinal) and (Source.Kind = stBoolean) then exit(True); // Allow assigning DateTime to Float (TDateTime is Double) if (Target.Kind = stFloat) and (Source.Kind = stDateTime) then exit(True); // Allow discarding the return value of a method (e.g., in a 'do' block). if (Target.Kind = stVoid) and (Source.Kind = stMethod) then exit(True); // Allow implicit conversion from Keyword (internally an index) to Ordinal. if (Target.Kind = stOrdinal) and (Source.Kind = stKeyword) then exit(True); // Default: Assignment is not allowed if no specific rule matches. Result := False; end; class function TTypeRules.Promote(const A, B: IStaticType): IStaticType; begin // Handle Unknown: If one is Unknown, the result is the other type. if A.Kind = stUnknown then exit(B); if B.Kind = stUnknown then exit(A); // all operations involving void result void if (A.Kind = stVoid) or (B.Kind = stVoid) then exit(TTypes.Void); // Standard Numeric promotion rule: Float wins if (A.Kind = stFloat) and (B.Kind = stFloat) then exit(TTypes.Float); if (A.Kind = stFloat) and (B.Kind = stOrdinal) then exit(TTypes.Float); if (A.Kind = stOrdinal) and (B.Kind = stFloat) then exit(TTypes.Float); if (A.Kind = stOrdinal) and (B.Kind = stOrdinal) then exit(TTypes.Ordinal); // Implicit DateTime/Boolean logic via Ordinal/Float mapping? // If types are identical (incl. Keyword, Text, Boolean, DateTime), return that type. if A.IsEqual(B) then exit(A); // Cannot promote stGenericRecord or stRecord with anything other than itself/unknown if (A.Kind in [stRecord, stGenericRecord]) or (B.Kind in [stRecord, stGenericRecord]) then raise ETypeException.CreateFmt('Cannot promote types %s and %s', [A.ToString, B.ToString]); // Cannot promote other combinations raise ETypeException.CreateFmt('Cannot promote types %s and %s', [A.ToString, B.ToString]); end; class function TTypeRules.ResolveBinaryOp(Op: TScalar.TBinaryOp; const Left, Right: IStaticType): IStaticType; var promotedType: IStaticType; promotedKind: TStaticTypeKind; begin // 1. Determine the effective type after promotion, handling Unknown. try promotedType := Promote(Left, Right); except // If promotion fails (e.g., Text and Ordinal), the operation is invalid. on E: ETypeException do raise ETypeException.CreateFmt( 'Operator %s cannot be applied to %s and %s (promotion failed: %s)', [Op.ToString, Left.ToString, Right.ToString, E.Message]); end; // If promotion results in Unknown, the result type is also Unknown for now. if promotedType.Kind = stUnknown then exit(TTypes.Unknown); // 2. Check if the operator is valid for the promoted type. promotedKind := promotedType.Kind; case Op of TScalar.TBinaryOp.Add, TScalar.TBinaryOp.Subtract, TScalar.TBinaryOp.Multiply: begin // Numeric operations require Ordinal, Float, or DateTime(as Float) if not (promotedKind in [stOrdinal, stFloat, stDateTime]) then raise ETypeException .CreateFmt('Operator %s requires Ordinal or Float, but got %s after promotion', [Op.ToString, promotedType.ToString]); Result := promotedType; // Result has the promoted type end; TScalar.TBinaryOp.Divide: begin // Division requires Ordinal or Float, but *always* results in Float if not (promotedKind in [stOrdinal, stFloat, stDateTime]) then raise ETypeException .CreateFmt('Operator %s requires Ordinal or Float, but got %s after promotion', [Op.ToString, promotedType.ToString]); Result := TTypes.Float; end; TScalar.TBinaryOp.Equal, TScalar.TBinaryOp.NotEqual: begin // Allow equality checks for Ordinal, Float, Keyword, Boolean, DateTime if not (promotedKind in [stOrdinal, stFloat, stKeyword, stBoolean, stDateTime]) then raise ETypeException.CreateFmt( 'Comparison operator %s requires scalar type, but got %s after promotion', [Op.ToString, promotedType.ToString]); // Result is always Boolean Result := TTypes.Boolean; end; TScalar.TBinaryOp.Less, TScalar.TBinaryOp.Greater, TScalar.TBinaryOp.LessOrEqual, TScalar.TBinaryOp.GreaterOrEqual: begin // Comparison requires Ordinal or Float (Keywords are not ordered) if not (promotedKind in [stOrdinal, stFloat, stDateTime]) then raise ETypeException.CreateFmt( 'Comparison operator %s requires ordered type (Ordinal, Float, DateTime), but got %s after promotion', [Op.ToString, promotedType.ToString]); Result := TTypes.Boolean; end; else raise ETypeException.Create('Unknown binary operator for type resolution.'); end; end; class function TTypeRules.ResolveUnaryOp(Op: TScalar.TUnaryOp; const Right: IStaticType): IStaticType; var rightKind: TStaticTypeKind; begin rightKind := Right.Kind; // If operand is Unknown, result is Unknown. if rightKind = stUnknown then exit(TTypes.Unknown); case Op of TScalar.TUnaryOp.Negate: begin // Negation requires Ordinal or Float if not (rightKind in [stOrdinal, stFloat]) then raise ETypeException.CreateFmt('Unary negation cannot be applied to %s', [Right.ToString]); Result := Right; // Negation preserves type end; TScalar.TUnaryOp.Not: begin // Logical not only applies to Boolean (or Ordinal if treated as bool) if not (rightKind in [stOrdinal, stBoolean]) then raise ETypeException.CreateFmt('Logical not cannot be applied to %s', [Right.ToString]); Result := TTypes.Boolean; end; else raise ETypeException.Create('Unknown unary operator for type resolution.'); end; end; end.