feat(selfhost): add operator overloading support (+,-,==,[], etc.)

- Auto-register func Type_Method(self: T, ...) as methods in sema.
- Add method table lookup for binary operators in sema.bux ekBinary.
- Add method table lookup for operator_index_get in sema.bux ekIndex.
- Add HIR lowering for binary operators to method calls in hir_lower.bux.
- Add HIR lowering for operator_index_get/set in hir_lower.bux.
- Set expr.refType for overloaded operators so chained ops work.
This commit is contained in:
2026-06-10 13:11:46 +03:00
parent a4e410fb30
commit a2236de69a
2 changed files with 246 additions and 0 deletions
+142
View File
@@ -483,6 +483,63 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n;
}
// Operator overloading: try method call
var opMethodName: String = "";
if expr.intValue == tkPlus { opMethodName = "operator_add"; }
else if expr.intValue == tkMinus { opMethodName = "operator_sub"; }
else if expr.intValue == tkStar { opMethodName = "operator_mul"; }
else if expr.intValue == tkSlash { opMethodName = "operator_div"; }
else if expr.intValue == tkPercent { opMethodName = "operator_mod"; }
else if expr.intValue == tkEq { opMethodName = "operator_eq"; }
else if expr.intValue == tkNe { opMethodName = "operator_ne"; }
else if expr.intValue == tkLt { opMethodName = "operator_lt"; }
else if expr.intValue == tkLe { opMethodName = "operator_le"; }
else if expr.intValue == tkGt { opMethodName = "operator_gt"; }
else if expr.intValue == tkGe { opMethodName = "operator_ge"; }
else if expr.intValue == tkAmp { opMethodName = "operator_bitand"; }
else if expr.intValue == tkPipe { opMethodName = "operator_bitor"; }
else if expr.intValue == tkCaret { opMethodName = "operator_xor"; }
else if expr.intValue == tkShl { opMethodName = "operator_shl"; }
else if expr.intValue == tkShr { opMethodName = "operator_shr"; }
if !String_Eq(opMethodName, "") {
var receiverTypeName: String = "";
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
let refTe: *TypeExpr = expr.child1.refType;
if refTe.kind == tekNamed {
receiverTypeName = refTe.typeName;
} else if refTe.kind == tekPointer && refTe.pointerPointee != null as *TypeExpr && refTe.pointerPointee.kind == tekNamed {
receiverTypeName = refTe.pointerPointee.typeName;
}
}
if !String_Eq(receiverTypeName, "") {
let funcName: String = String_Concat(String_Concat(receiverTypeName, "_"), opMethodName);
let sym: Symbol = Scope_Lookup(ctx.scope, funcName);
if sym.kind == skFunc && sym.decl != null as *Decl {
n.kind = hCall;
n.strValue = funcName;
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
// If method expects pointer but receiver is not pointer, add &
if sym.decl.paramCount > 0 && sym.decl.param0.refParamType != null as *TypeExpr && sym.decl.param0.refParamType.kind == tekPointer {
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary;
addrNode.intValue = tkAmp;
addrNode.child1 = recv;
n.child1 = addrNode;
} else {
n.child1 = recv;
}
} else {
n.child1 = recv;
}
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n;
}
}
}
n.kind = hBinary;
n.intValue = expr.intValue; // operator
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
@@ -775,6 +832,44 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
return n;
}
// Operator overloading: try operator_index_get
if expr.child1 != null as *Expr {
var receiverTypeName: String = "";
if expr.child1.refType != null as *TypeExpr {
let refTe: *TypeExpr = expr.child1.refType;
if refTe.kind == tekNamed {
receiverTypeName = refTe.typeName;
} else if refTe.kind == tekPointer && refTe.pointerPointee != null as *TypeExpr && refTe.pointerPointee.kind == tekNamed {
receiverTypeName = refTe.pointerPointee.typeName;
}
}
if !String_Eq(receiverTypeName, "") {
let funcName: String = String_Concat(String_Concat(receiverTypeName, "_"), "operator_index_get");
let sym: Symbol = Scope_Lookup(ctx.scope, funcName);
if sym.kind == skFunc && sym.decl != null as *Decl {
n.kind = hCall;
n.strValue = funcName;
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
// If method expects pointer but receiver is not pointer, add &
if sym.decl.paramCount > 0 && sym.decl.param0.refParamType != null as *TypeExpr && sym.decl.param0.refParamType.kind == tekPointer {
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary;
addrNode.intValue = tkAmp;
addrNode.child1 = recv;
n.child1 = addrNode;
} else {
n.child1 = recv;
}
} else {
n.child1 = recv;
}
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n;
}
}
}
n.kind = hIndexPtr;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
@@ -829,6 +924,53 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
}
}
// Operator overloading: try operator_index_set
if expr.intValue == tkAssign && expr.child1 != null as *Expr && expr.child1.kind == ekIndex && expr.child1.child1 != null as *Expr {
var receiverTypeName: String = "";
let objExpr: *Expr = expr.child1.child1;
if objExpr.refType != null as *TypeExpr {
let refTe: *TypeExpr = objExpr.refType;
if refTe.kind == tekNamed {
receiverTypeName = refTe.typeName;
} else if refTe.kind == tekPointer && refTe.pointerPointee != null as *TypeExpr && refTe.pointerPointee.kind == tekNamed {
receiverTypeName = refTe.pointerPointee.typeName;
}
}
if !String_Eq(receiverTypeName, "") {
let funcName: String = String_Concat(String_Concat(receiverTypeName, "_"), "operator_index_set");
let sym: Symbol = Scope_Lookup(ctx.scope, funcName);
if sym.kind == skFunc && sym.decl != null as *Decl {
let callNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
callNode.kind = hCall;
callNode.strValue = funcName;
callNode.line = line;
callNode.column = col;
let recv: *HirNode = Lcx_LowerExpr(ctx, objExpr);
// If method expects pointer but receiver is not pointer, add &
if sym.decl.paramCount > 0 && sym.decl.param0.refParamType != null as *TypeExpr && sym.decl.param0.refParamType.kind == tekPointer {
if objExpr.refType != null as *TypeExpr && objExpr.refType.kind != tekPointer {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary;
addrNode.intValue = tkAmp;
addrNode.child1 = recv;
callNode.child1 = addrNode;
} else {
callNode.child1 = recv;
}
} else {
callNode.child1 = recv;
}
callNode.child2 = Lcx_LowerExpr(ctx, expr.child1.child2);
let extra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
extra.node = Lcx_LowerExpr(ctx, expr.child2);
extra.next = null as *HirArgList;
callNode.extraData = extra as *void;
callNode.extraCount = 1;
return callNode;
}
}
}
n.kind = hAssign;
n.child1 = Lcx_LowerExpr(ctx, expr.child1); // target
n.child2 = Lcx_LowerExpr(ctx, expr.child2); // value