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); n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n; 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.kind = hBinary;
n.intValue = expr.intValue; // operator n.intValue = expr.intValue; // operator
n.child1 = Lcx_LowerExpr(ctx, expr.child1); n.child1 = Lcx_LowerExpr(ctx, expr.child1);
@@ -775,6 +832,44 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
return n; 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.kind = hIndexPtr;
n.child1 = Lcx_LowerExpr(ctx, expr.child1); n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.child2 = Lcx_LowerExpr(ctx, expr.child2); 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.kind = hAssign;
n.child1 = Lcx_LowerExpr(ctx, expr.child1); // target n.child1 = Lcx_LowerExpr(ctx, expr.child1); // target
n.child2 = Lcx_LowerExpr(ctx, expr.child2); // value n.child2 = Lcx_LowerExpr(ctx, expr.child2); // value
+104
View File
@@ -491,6 +491,50 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if op == tkAssign || (op >= tkPlusAssign && op <= tkShrAssign) { if op == tkAssign || (op >= tkPlusAssign && op <= tkShrAssign) {
return left; return left;
} }
// Operator overloading: check method table
var opMethodName: String = "";
if op == tkPlus { opMethodName = "operator_add"; }
else if op == tkMinus { opMethodName = "operator_sub"; }
else if op == tkStar { opMethodName = "operator_mul"; }
else if op == tkSlash { opMethodName = "operator_div"; }
else if op == tkPercent { opMethodName = "operator_mod"; }
else if op == tkEq { opMethodName = "operator_eq"; }
else if op == tkNe { opMethodName = "operator_ne"; }
else if op == tkLt { opMethodName = "operator_lt"; }
else if op == tkLe { opMethodName = "operator_le"; }
else if op == tkGt { opMethodName = "operator_gt"; }
else if op == tkGe { opMethodName = "operator_ge"; }
else if op == tkAmp { opMethodName = "operator_bitand"; }
else if op == tkPipe { opMethodName = "operator_bitor"; }
else if op == tkCaret { opMethodName = "operator_xor"; }
else if op == tkShl { opMethodName = "operator_shl"; }
else if op == tkShr { opMethodName = "operator_shr"; }
if !String_Eq(opMethodName, "") && left == tyNamed {
var receiverTypeName: String = "";
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
receiverTypeName = expr.child1.refType.typeName;
}
if !String_Eq(receiverTypeName, "") {
var i: int = 0;
while i < sema.methodCount {
if String_Eq(sema.methodEntries[i].typeName, receiverTypeName) &&
String_Eq(sema.methodEntries[i].methodName, opMethodName) {
let methodDecl: *Decl = sema.methodEntries[i].decl;
if methodDecl != null as *Decl && methodDecl.paramCount == 2 {
if methodDecl.retType != null as *TypeExpr {
expr.refType = methodDecl.retType;
return Sema_ResolveType(sema, methodDecl.retType);
}
return tyUnknown;
}
}
i = i + 1;
}
}
}
// Comparison operators return bool // Comparison operators return bool
if op >= tkEq && op <= tkGe { return tyBool; } if op >= tkEq && op <= tkGe { return tyBool; }
// Logical operators return bool // Logical operators return bool
@@ -645,6 +689,33 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if !Sema_IsNumeric(idx) && idx != tyUnknown { if !Sema_IsNumeric(idx) && idx != tyUnknown {
Sema_EmitError(sema, expr.line, expr.column, "index must be integer"); Sema_EmitError(sema, expr.line, expr.column, "index must be integer");
} }
// Operator overloading: check method table for operator_index_get
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, "") {
var i: int = 0;
while i < sema.methodCount {
if String_Eq(sema.methodEntries[i].typeName, receiverTypeName) &&
String_Eq(sema.methodEntries[i].methodName, "operator_index_get") {
let methodDecl: *Decl = sema.methodEntries[i].decl;
if methodDecl != null as *Decl && methodDecl.paramCount == 2 {
if methodDecl.retType != null as *TypeExpr {
expr.refType = methodDecl.retType;
return Sema_ResolveType(sema, methodDecl.retType);
}
return tyUnknown;
}
}
i = i + 1;
}
}
return tyUnknown; return tyUnknown;
} }
@@ -1174,6 +1245,39 @@ func Sema_CollectGlobals(sema: *Sema) {
decl = decl.childDecl2; decl = decl.childDecl2;
} }
// Pass 3: auto-register func Type_Method(self: Type, ...) as methods
decl = sema.module.firstItem;
while decl != null as *Decl {
if decl.kind == dkFunc && decl.paramCount > 0 {
let selfParam: Param = decl.param0;
if String_Eq(selfParam.name, "self") {
var typeName: String = "";
var i: int = bux_strlen(decl.strValue) as int - 1;
while i > 0 {
if decl.strValue[i] as int == 95 { // '_'
let prefix: String = bux_str_slice(decl.strValue, 0, i as uint);
let typeSym: Symbol = Scope_Lookup(sema.scope, prefix);
if typeSym.kind == skType && typeSym.decl != null as *Decl && typeSym.decl.kind == dkStruct {
typeName = prefix;
break;
}
}
i = i - 1;
}
if !String_Eq(typeName, "") {
let methodName: String = bux_str_slice(decl.strValue, (i + 1) as uint, bux_strlen(decl.strValue) - (i + 1) as uint);
if sema.methodCount < 256 {
sema.methodEntries[sema.methodCount].typeName = typeName;
sema.methodEntries[sema.methodCount].methodName = methodName;
sema.methodEntries[sema.methodCount].decl = decl;
sema.methodCount = sema.methodCount + 1;
}
}
}
}
decl = decl.childDecl2;
}
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------