From 497e01d537ae72a0e65fa58a0c1f574949ea11a0 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Tue, 28 Jul 2026 21:36:47 +0300 Subject: [PATCH] feat(selfhost-sema): check call argument types against param types --- _test_call_args/bux.toml | 4 ++ _test_call_args/src/Main.bux | 9 +++ src/sema.bux | 103 ++++++++++++++++++++++++++--------- 3 files changed, 90 insertions(+), 26 deletions(-) create mode 100644 _test_call_args/bux.toml create mode 100644 _test_call_args/src/Main.bux diff --git a/_test_call_args/bux.toml b/_test_call_args/bux.toml new file mode 100644 index 0000000..21b12b8 --- /dev/null +++ b/_test_call_args/bux.toml @@ -0,0 +1,4 @@ +[Package] +Name = "call_args" +Version = "0.1.0" +Type = "bin" diff --git a/_test_call_args/src/Main.bux b/_test_call_args/src/Main.bux new file mode 100644 index 0000000..0b0d51e --- /dev/null +++ b/_test_call_args/src/Main.bux @@ -0,0 +1,9 @@ +extern func PrintInt(n: int); +extern func PrintLine(s: String); + +func Main() -> int { + PrintInt("hello"); + PrintLine(42); + PrintInt(true); + return 0; +} diff --git a/src/sema.bux b/src/sema.bux index cbcd71a..a0cdfff 100644 --- a/src/sema.bux +++ b/src/sema.bux @@ -210,6 +210,9 @@ module Sema { // Display-name helper for assignment diagnostics. func Sema_TypeNameForDiag(te: *TypeExpr, kind: int) -> String { if te != null as *TypeExpr { + // tekFunc TypeExprs have no typeName set (parser leaves it unset) — + // never touch te.typeName for them. + if te.kind == tekFunc { return "func"; } if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr { return String_Concat(te.pointerPointee.typeName, "*"); } @@ -225,6 +228,25 @@ module Sema { return "?"; } + // Assignability for diagnostics: strict numeric widening, bool family, + // exact pointer kind, named types by name (best-effort when refType available). + func Sema_CanAssignTo(sema: *Sema, fromKind: int, fromTe: *TypeExpr, toTe: *TypeExpr) -> bool { + if fromKind == tyUnknown { return true; } + if toTe == null as *TypeExpr { return true; } + let toKind: int = Sema_ResolveType(sema, toTe); + if toKind == tyUnknown { return true; } + if fromKind == tyNamed && toKind == tyNamed { + if fromTe != null as *TypeExpr && !String_Eq(fromTe.typeName, "") { + return String_Eq(fromTe.typeName, toTe.typeName); + } + return true; + } + if fromKind == toKind { return true; } + if Sema_IsStrictNumeric(fromKind) && Sema_IsStrictNumeric(toKind) { return true; } + if Sema_IsBool(fromKind) && Sema_IsBool(toKind) { return true; } + return false; + } + // --------------------------------------------------------------------------- // Block checking helper // --------------------------------------------------------------------------- @@ -1035,9 +1057,57 @@ module Sema { if kind == ekCall { let calleeType: int = Sema_CheckExpr(sema, expr.child1); Sema_ResolveCallArgs(sema, expr); + // Resolve callee decl for call-argument checking: direct calls to + // non-generic named functions only (inference handles generics). + var callDecl: *Decl = null as *Decl; + if expr.child1 != null as *Expr && expr.child1.kind == ekIdent { + let callSym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue); + if callSym.kind == skFunc && callSym.decl != null as *Decl { + if callSym.decl.typeParamCount == 0 { + callDecl = callSym.decl; + } + } + } + // Arity check (runs after Sema_ResolveCallArgs so defaults/named args align) + if callDecl != null as *Decl && expr.callArgCount != callDecl.paramCount { + Sema_EmitError(sema, expr.line, expr.column, + String_Concat("expected ", String_Concat(bux_int_to_str(callDecl.paramCount as int64), + String_Concat(" arguments, got ", bux_int_to_str(expr.callArgCount as int64))))); + callDecl = null as *Decl; + } + var argIdx: int = 0; var arg: *ExprList = expr.callArgs; while arg != null as *ExprList { - discard Sema_CheckExpr(sema, arg.expr); + let argKind: int = Sema_CheckExpr(sema, arg.expr); + // Per-argument type check against the parameter type + if callDecl != null as *Decl && argIdx < callDecl.paramCount { + let cp: *Param = Sema_DeclParam(callDecl, argIdx); + if cp != null as *Param && cp.refParamType != null as *TypeExpr { + // Skip conditions matching the bootstrap: unknown/named/typeparam + // arg kinds, and *char8 -> String (C string interop). + var argSkip: bool = false; + if argKind == tyUnknown || argKind == tyNamed || argKind == tyTypeParam { argSkip = true; } + // Function-typed params: selfhost cannot reliably type function + // values (e.g. '&Double' is tyPointer) — defer like the bootstrap. + if Sema_ResolveType(sema, cp.refParamType) == tyFunc { argSkip = true; } + if !argSkip && argKind == tyPointer && Sema_ResolveType(sema, cp.refParamType) == tyStr { + if arg.expr != null as *Expr && arg.expr.refType != null as *TypeExpr { + if arg.expr.refType.kind == tekPointer && arg.expr.refType.pointerPointee != null as *TypeExpr { + if String_Eq(arg.expr.refType.pointerPointee.typeName, "char8") { argSkip = true; } + } + } + } + if !argSkip && arg.expr != null as *Expr && !Sema_CanAssignTo(sema, argKind, arg.expr.refType, cp.refParamType) { + let wantName: String = Sema_TypeNameForDiag(cp.refParamType, Sema_ResolveType(sema, cp.refParamType)); + let gotName: String = Sema_TypeNameForDiag(arg.expr.refType, argKind); + Sema_EmitError(sema, arg.expr.line, arg.expr.column, + String_Concat("argument ", String_Concat(bux_int_to_str((argIdx + 1) as int64), + String_Concat(": expected ", String_Concat(wantName, + String_Concat(", got ", gotName)))))); + } + } + } + argIdx = argIdx + 1; arg = arg.next; } // Borrow check: reject double mutable borrow in @[Checked] functions @@ -1495,31 +1565,12 @@ module Sema { } // Assignment check: annotation vs initializer (skip when either side is unknown) if stmt.refStmtType != null as *TypeExpr && initType != tyUnknown { - let annotKind: int = Sema_ResolveType(sema, stmt.refStmtType); - if annotKind != tyUnknown { - var mismatch: bool = false; - if initType == tyNamed && annotKind == tyNamed { - // Both named: kinds are equal, compare type names (when available). - if stmt.child1 != null as *Expr && stmt.child1.refType != null as *TypeExpr { - if !String_Eq(stmt.child1.refType.typeName, "") && - !String_Eq(stmt.child1.refType.typeName, stmt.refStmtType.typeName) { - mismatch = true; - } - } - } else if initType != annotKind { - let numericOk: bool = Sema_IsStrictNumeric(initType) && Sema_IsStrictNumeric(annotKind); - let boolOk: bool = Sema_IsBool(initType) && Sema_IsBool(annotKind); - if !numericOk && !boolOk { - mismatch = true; - } - } - if mismatch { - let gotName: String = Sema_TypeNameForDiag(stmt.child1.refType, initType); - let wantName: String = Sema_TypeNameForDiag(stmt.refStmtType, annotKind); - let msg: String = String_Concat("cannot assign ", - String_Concat(gotName, String_Concat(" to ", wantName))); - Sema_EmitError(sema, stmt.line, stmt.column, msg); - } + if !Sema_CanAssignTo(sema, initType, stmt.child1.refType, stmt.refStmtType) { + let gotName: String = Sema_TypeNameForDiag(stmt.child1.refType, initType); + let wantName: String = Sema_TypeNameForDiag(stmt.refStmtType, Sema_ResolveType(sema, stmt.refStmtType)); + let msg: String = String_Concat("cannot assign ", + String_Concat(gotName, String_Concat(" to ", wantName))); + Sema_EmitError(sema, stmt.line, stmt.column, msg); } } sym.isMutable = stmt.boolValue;