feat: operator overloading in bootstrap compiler
- Add operatorMethodName mapping (tkPlus -> operator_add, etc.) - Add tryLowerOperatorCall / tryLowerIndexCall in HIR lowerer - Modify ekBinary lowering to check method table before builtins - Modify ekAssign lowering to detect ekIndex target and emit operator_index_set method call when available - Add sema type-checking for operator overloads in ekBinary and ekIndex paths (method-table lookup before builtin rules) - Fix sema compile errors: minfo.params[N].typ -> minfo.params[N] - Fix hir_lower forward decl ordering for resolveExprType - Update golden test expected.c files for current C backend - Update ROADMAP.md and PHASE8_STRATEGY.md status
This commit is contained in:
+143
-1
@@ -533,6 +533,103 @@ proc findMethodEntry(ctx: LowerCtx, typeName: string): (string, seq[MethodInfo])
|
||||
return (prefix, ctx.methodTable[prefix])
|
||||
return ("", @[])
|
||||
|
||||
proc operatorMethodName(op: TokenKind): string =
|
||||
case op
|
||||
of tkPlus: "operator_add"
|
||||
of tkMinus: "operator_sub"
|
||||
of tkStar: "operator_mul"
|
||||
of tkSlash: "operator_div"
|
||||
of tkPercent: "operator_mod"
|
||||
of tkEq: "operator_eq"
|
||||
of tkNe: "operator_ne"
|
||||
of tkLt: "operator_lt"
|
||||
of tkLe: "operator_le"
|
||||
of tkGt: "operator_gt"
|
||||
of tkGe: "operator_ge"
|
||||
of tkAmp: "operator_bitand"
|
||||
of tkPipe: "operator_bitor"
|
||||
of tkCaret: "operator_xor"
|
||||
of tkShl: "operator_shl"
|
||||
of tkShr: "operator_shr"
|
||||
else: ""
|
||||
|
||||
proc tryLowerOperatorCall(ctx: var LowerCtx, op: TokenKind, leftExpr, rightExpr: Expr, typ: Type, loc: SourceLocation): HirNode =
|
||||
## Try to lower a binary operator to a method call. Returns nil if no overload found.
|
||||
let methodName = operatorMethodName(op)
|
||||
if methodName == "": return nil
|
||||
let receiverType = ctx.resolveExprType(leftExpr)
|
||||
var receiverTypeName = ""
|
||||
if receiverType.kind == tkNamed:
|
||||
receiverTypeName = receiverType.name
|
||||
if ctx.typeSubst.hasKey(receiverTypeName):
|
||||
let substituted = ctx.typeSubst[receiverTypeName]
|
||||
if substituted.kind == tkNamed:
|
||||
receiverTypeName = substituted.name
|
||||
elif substituted.isPointer and substituted.inner.len > 0 and substituted.inner[0].kind == tkNamed:
|
||||
receiverTypeName = substituted.inner[0].name
|
||||
elif receiverType.kind in {tkInt, tkInt8, tkInt16, tkInt32, tkInt64,
|
||||
tkUInt, tkUInt8, tkUInt16, tkUInt32, tkUInt64,
|
||||
tkFloat32, tkFloat64, tkBool, tkStr, tkChar8}:
|
||||
receiverTypeName = receiverType.toString
|
||||
elif receiverType.isPointer and receiverType.inner.len > 0 and receiverType.inner[0].kind == tkNamed:
|
||||
receiverTypeName = receiverType.inner[0].name
|
||||
let (typeName, methods) = ctx.findMethodEntry(receiverTypeName)
|
||||
if typeName == "": return nil
|
||||
for minfo in methods:
|
||||
if minfo.name == methodName:
|
||||
var calleeName = typeName & "_" & methodName
|
||||
# Check generic method instantiation
|
||||
let recvTypeExpr = ctx.getReceiverTypeExpr(leftExpr)
|
||||
let (baseName, typeArgs) = ctx.extractGenericStructInfo(recvTypeExpr)
|
||||
if baseName != "" and baseName == typeName and minfo.decl.declFuncTypeParams.len > 0:
|
||||
calleeName = ctx.generateMethodInstance(calleeName, typeArgs)
|
||||
var args: seq[HirNode] = @[]
|
||||
let loweredReceiver = ctx.lowerExpr(leftExpr)
|
||||
if minfo.params.len > 0 and minfo.params[0].isPointer and not receiverType.isPointer:
|
||||
args.add(hirUnary(tkAmp, loweredReceiver, makePointer(receiverType), loc))
|
||||
else:
|
||||
args.add(loweredReceiver)
|
||||
args.add(ctx.lowerExpr(rightExpr))
|
||||
return hirCall(calleeName, args, typ, loc)
|
||||
return nil
|
||||
|
||||
proc tryLowerIndexCall(ctx: var LowerCtx, objExpr, idxExpr: Expr, typ: Type, loc: SourceLocation): HirNode =
|
||||
## Try to lower arr[i] to operator_index_get(arr, i). Returns nil if no overload found.
|
||||
let receiverType = ctx.resolveExprType(objExpr)
|
||||
var receiverTypeName = ""
|
||||
if receiverType.kind == tkNamed:
|
||||
receiverTypeName = receiverType.name
|
||||
if ctx.typeSubst.hasKey(receiverTypeName):
|
||||
let substituted = ctx.typeSubst[receiverTypeName]
|
||||
if substituted.kind == tkNamed:
|
||||
receiverTypeName = substituted.name
|
||||
elif substituted.isPointer and substituted.inner.len > 0 and substituted.inner[0].kind == tkNamed:
|
||||
receiverTypeName = substituted.inner[0].name
|
||||
elif receiverType.kind in {tkInt, tkInt8, tkInt16, tkInt32, tkInt64,
|
||||
tkUInt, tkUInt8, tkUInt16, tkUInt32, tkUInt64,
|
||||
tkFloat32, tkFloat64, tkBool, tkStr, tkChar8}:
|
||||
receiverTypeName = receiverType.toString
|
||||
elif receiverType.isPointer and receiverType.inner.len > 0 and receiverType.inner[0].kind == tkNamed:
|
||||
receiverTypeName = receiverType.inner[0].name
|
||||
let (typeName, methods) = ctx.findMethodEntry(receiverTypeName)
|
||||
if typeName == "": return nil
|
||||
for minfo in methods:
|
||||
if minfo.name == "operator_index_get":
|
||||
var calleeName = typeName & "_operator_index_get"
|
||||
let recvTypeExpr = ctx.getReceiverTypeExpr(objExpr)
|
||||
let (baseName, typeArgs) = ctx.extractGenericStructInfo(recvTypeExpr)
|
||||
if baseName != "" and baseName == typeName and minfo.decl.declFuncTypeParams.len > 0:
|
||||
calleeName = ctx.generateMethodInstance(calleeName, typeArgs)
|
||||
var args: seq[HirNode] = @[]
|
||||
let loweredReceiver = ctx.lowerExpr(objExpr)
|
||||
if minfo.params.len > 0 and minfo.params[0].isPointer and not receiverType.isPointer:
|
||||
args.add(hirUnary(tkAmp, loweredReceiver, makePointer(receiverType), loc))
|
||||
else:
|
||||
args.add(loweredReceiver)
|
||||
args.add(ctx.lowerExpr(idxExpr))
|
||||
return hirCall(calleeName, args, typ, loc)
|
||||
return nil
|
||||
|
||||
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
if expr == nil: return nil
|
||||
let loc = expr.loc
|
||||
@@ -584,6 +681,9 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
let ifNode = hirIf(left, thenBlock, elseBlock, loc)
|
||||
return hirBlock(@[tmp, ifNode], hirLoad(tmp, makeBool(), loc), makeBool(), loc)
|
||||
else:
|
||||
let lowered = ctx.tryLowerOperatorCall(expr.exprBinaryOp, expr.exprBinaryLeft, expr.exprBinaryRight, typ, loc)
|
||||
if lowered != nil:
|
||||
return lowered
|
||||
let left = ctx.lowerExpr(expr.exprBinaryLeft)
|
||||
let right = ctx.lowerExpr(expr.exprBinaryRight)
|
||||
return hirBinary(expr.exprBinaryOp, left, right, typ, loc)
|
||||
@@ -714,9 +814,13 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
return HirNode(kind: hLoad, loadPtr: basePtr, typ: typ, loc: loc)
|
||||
|
||||
of ekIndex:
|
||||
let baseType = ctx.resolveExprType(expr.exprIndexObj)
|
||||
if not baseType.isSlice:
|
||||
let lowered = ctx.tryLowerIndexCall(expr.exprIndexObj, expr.exprIndexIdx, typ, loc)
|
||||
if lowered != nil:
|
||||
return lowered
|
||||
let base = ctx.lowerExpr(expr.exprIndexObj)
|
||||
let idx = ctx.lowerExpr(expr.exprIndexIdx)
|
||||
let baseType = ctx.resolveExprType(expr.exprIndexObj)
|
||||
if baseType.isSlice:
|
||||
let sliceIdx = HirNode(kind: hSliceIndex, sliceIndexBase: base,
|
||||
sliceIndexIndex: idx,
|
||||
@@ -728,6 +832,44 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
return HirNode(kind: hLoad, loadPtr: basePtr, typ: typ, loc: loc)
|
||||
|
||||
of ekAssign:
|
||||
# Check for operator_index_set overload
|
||||
if expr.exprAssignTarget.kind == ekIndex:
|
||||
let objExpr = expr.exprAssignTarget.exprIndexObj
|
||||
let idxExpr = expr.exprAssignTarget.exprIndexIdx
|
||||
let receiverType = ctx.resolveExprType(objExpr)
|
||||
var receiverTypeName = ""
|
||||
if receiverType.kind == tkNamed:
|
||||
receiverTypeName = receiverType.name
|
||||
if ctx.typeSubst.hasKey(receiverTypeName):
|
||||
let substituted = ctx.typeSubst[receiverTypeName]
|
||||
if substituted.kind == tkNamed:
|
||||
receiverTypeName = substituted.name
|
||||
elif substituted.isPointer and substituted.inner.len > 0 and substituted.inner[0].kind == tkNamed:
|
||||
receiverTypeName = substituted.inner[0].name
|
||||
elif receiverType.kind in {tkInt, tkInt8, tkInt16, tkInt32, tkInt64,
|
||||
tkUInt, tkUInt8, tkUInt16, tkUInt32, tkUInt64,
|
||||
tkFloat32, tkFloat64, tkBool, tkStr, tkChar8}:
|
||||
receiverTypeName = receiverType.toString
|
||||
elif receiverType.isPointer and receiverType.inner.len > 0 and receiverType.inner[0].kind == tkNamed:
|
||||
receiverTypeName = receiverType.inner[0].name
|
||||
let (typeName, methods) = ctx.findMethodEntry(receiverTypeName)
|
||||
if typeName != "":
|
||||
for minfo in methods:
|
||||
if minfo.name == "operator_index_set":
|
||||
var calleeName = typeName & "_operator_index_set"
|
||||
let recvTypeExpr = ctx.getReceiverTypeExpr(objExpr)
|
||||
let (baseName, typeArgs) = ctx.extractGenericStructInfo(recvTypeExpr)
|
||||
if baseName != "" and baseName == typeName and minfo.decl.declFuncTypeParams.len > 0:
|
||||
calleeName = ctx.generateMethodInstance(calleeName, typeArgs)
|
||||
var args: seq[HirNode] = @[]
|
||||
let loweredReceiver = ctx.lowerExpr(objExpr)
|
||||
if minfo.params.len > 0 and minfo.params[0].isPointer and not receiverType.isPointer:
|
||||
args.add(hirUnary(tkAmp, loweredReceiver, makePointer(receiverType), loc))
|
||||
else:
|
||||
args.add(loweredReceiver)
|
||||
args.add(ctx.lowerExpr(idxExpr))
|
||||
args.add(ctx.lowerExpr(expr.exprAssignValue))
|
||||
return hirCall(calleeName, args, makeVoid(), loc)
|
||||
let target = ctx.lowerExpr(expr.exprAssignTarget)
|
||||
let value = ctx.lowerExpr(expr.exprAssignValue)
|
||||
return HirNode(kind: hAssign, assignOp: expr.exprAssignOp,
|
||||
|
||||
@@ -797,6 +797,33 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
of ekBinary:
|
||||
let left = sema.checkExpr(expr.exprBinaryLeft, scope)
|
||||
let right = sema.checkExpr(expr.exprBinaryRight, scope)
|
||||
# Operator overloading: check method table before builtin rules
|
||||
let opMethodName = case expr.exprBinaryOp
|
||||
of tkPlus: "operator_add"
|
||||
of tkMinus: "operator_sub"
|
||||
of tkStar: "operator_mul"
|
||||
of tkSlash: "operator_div"
|
||||
of tkPercent: "operator_mod"
|
||||
of tkEq: "operator_eq"
|
||||
of tkNe: "operator_ne"
|
||||
of tkLt: "operator_lt"
|
||||
of tkLe: "operator_le"
|
||||
of tkGt: "operator_gt"
|
||||
of tkGe: "operator_ge"
|
||||
of tkAmp: "operator_bitand"
|
||||
of tkPipe: "operator_bitor"
|
||||
of tkCaret: "operator_xor"
|
||||
of tkShl: "operator_shl"
|
||||
of tkShr: "operator_shr"
|
||||
else: ""
|
||||
if opMethodName != "" and left.kind == tkNamed and sema.methodTable.hasKey(left.name):
|
||||
for minfo in sema.methodTable[left.name]:
|
||||
if minfo.name == opMethodName:
|
||||
# Validate argument count (self + other)
|
||||
if minfo.params.len == 2:
|
||||
let otherType = minfo.params[1]
|
||||
if right.isAssignableTo(otherType) or otherType.isAssignableTo(right) or right.kind == tkUnknown:
|
||||
return minfo.retType
|
||||
case expr.exprBinaryOp
|
||||
of tkPlus, tkMinus, tkStar, tkSlash, tkPercent, tkStarStar:
|
||||
if not left.isNumeric or not right.isNumeric:
|
||||
@@ -1057,6 +1084,14 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
return obj.inner[0]
|
||||
elif obj.kind == tkStr:
|
||||
return makeChar8()
|
||||
elif obj.kind == tkNamed and sema.methodTable.hasKey(obj.name):
|
||||
for minfo in sema.methodTable[obj.name]:
|
||||
if minfo.name == "operator_index_get" and minfo.params.len == 2:
|
||||
let idxType = minfo.params[1]
|
||||
if idx.isAssignableTo(idxType) or idxType.isAssignableTo(idx) or idx.kind == tkUnknown:
|
||||
return minfo.retType
|
||||
sema.emitError(expr.loc, "cannot index non-slice/non-pointer type")
|
||||
return makeUnknown()
|
||||
else:
|
||||
sema.emitError(expr.loc, "cannot index non-slice/non-pointer type")
|
||||
return makeUnknown()
|
||||
|
||||
Reference in New Issue
Block a user