feat(lang): L4 type checker between parse and emit

selfhost/check/ walks every function, the entry, tests, globals' initializers and
@On listeners with real scopes, and refuses mixed number kinds, text and numbers,
two record types, mismatched slices and fn types, wrong argument counts, wrong
returns and wrong push elements - every mix-up at once, each at its line.
LUDIC_CHECK_REPORT=1 lists them by category. pointer stays untyped (L7's).

What it found is fixed: render3d's HDR scan calling the float-bits extern f_lt
with floats; ludic.shooter's right-stick aim overflowing past half a push;
prof.ludic storing longs in []int; extern arguments now coerced to their
parameters. Text-returning runtime functions say string; Assets.ready says bool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-24 01:34:49 +03:00
parent 0677aee93f
commit 57b66bdf47
52 changed files with 27005 additions and 11844 deletions

View file

@ -1377,13 +1377,24 @@ function emit_call(e: Node) -> Val {
let ext = find_extern(name)
if (ext != null) {
reorder_named(e, param_labels(ext))
# each argument is coerced to its parameter, as a function's are: passed as its own
# type, a float given to an int parameter arrived in a float register the callee
# never reads, and the callee compared whatever the integer register held
let eptys = param_types(ext)
let eargs = new []pointer
let eatys = new []pointer
var ei = 0
while ei < len(e.kids) { let v = emit_expr(e.kids[ei]); push(eargs, v.code); push(eatys, v.ty); ei += 1 }
while ei < len(e.kids) {
let v = emit_expr(e.kids[ei])
var pty = v.ty
if ei < len(eptys) { pty = eptys[ei] }
push(eargs, coerce_code(v, pty))
push(eatys, pty)
ei += 1
}
let erl = llty(ext.ty)
emit(" ")
var erreg = "0"
var erreg: pointer = "0"
if not (erl == "void") { erreg = nreg(); emit(erreg); emit(" = ") }
emit("call "); emit(erl); emit(" @"); emit(ext.a.s); emit("(")
ei = 0
@ -1425,7 +1436,7 @@ function emit_call(e: Node) -> Val {
}
let rl = llty(fn2.ty)
emit(" ")
var rreg = "0"
var rreg: pointer = "0"
if not (rl == "void") { rreg = nreg(); emit(rreg); emit(" = ") }
emit("call "); emit(rl); emit(" @fn_"); emit(cname); emit("(")
i = 0

View file

@ -168,7 +168,7 @@ function lit_as_fixed(v: Val) -> Val {
function emit_fp_bin(op: pointer, a: Val, b: Val) -> Val {
if (a.ty == "fixed") and not (fixed_lit_code(b) == "") { return emit_bin_vals(op, a, lit_as_fixed(b), false) }
if (b.ty == "fixed") and not (fixed_lit_code(a) == "") { return emit_bin_vals(op, lit_as_fixed(a), b, false) }
var t = "float"
var t: pointer = "float"
if is_fp(a.ty) and is_fp(b.ty) { t = fp_result(a.ty, b.ty) }
else { if is_fp(a.ty) { t = a.ty } else { t = b.ty } }
let ac = to_fp(a, t, `the left side of {op}`)
@ -336,7 +336,7 @@ function fp_pick(meth: pointer, t: pointer, x: pointer, y: pointer) -> pointer {
# the float type a Math call works in, given its already-evaluated first
# argument and the static types of the rest; "" when it is not a float call
function fp_math_type(first: Val, e: Node) -> pointer {
var t = ""
var t: pointer = ""
if is_fp(first.ty) { t = first.ty }
var i = 1
while i < len(e.kids) {

View file

@ -79,7 +79,7 @@ function emit_indirect_call(fv: Val, e: Node) -> Val {
}
let rl = llty(ret)
emit(" ")
var rreg = "0"
var rreg: pointer = "0"
if not (rl == "void") {
rreg = nreg()
emit(rreg)

View file

@ -28,8 +28,8 @@ function compound_op(tok: pointer) -> pointer {
function emit_assign(st: Node) -> void {
# resolve the target's address and type
let t = st.a
var addr = "0"
var ty = "int"
var addr: pointer = "0"
var ty: pointer = "int"
if t.kind == E_ID {
let li = loc_find(t.s)
if li >= 0 {
@ -209,7 +209,7 @@ function emit_match_tagged(st: Node, sv: Val, en: Node) -> void {
let arm = st.kids[i]
if arm_is_default(arm) { deflt = arm }
else {
var acc = "0"
var acc: pointer = "0"
var first = true
var bindPat: Node = null
var p = 0
@ -262,7 +262,7 @@ function emit_match(st: Node) -> void {
let arm = st.kids[i]
if arm_is_default(arm) { deflt = arm }
else {
var acc = "0"
var acc: pointer = "0"
var first = true
var p = 0
while p < len(arm.kids) {
@ -303,7 +303,7 @@ function emit_emit(st: Node) -> Val {
var j = 0
while j < len(st.a.kids) { if (st.a.kids[j].s == fd.s) { av = st.a.kids[j].a }; j += 1 }
let lt = llty(fd.ty)
var code = "0"
var code: pointer = "0"
if (lt == "ptr") { code = "null" }
if (av != null) { let v = emit_expr(av); code = v.code }
else { if (fd.a != null) { let dv = emit_expr(fd.a); code = dv.code } } # declared default

View file

@ -30,7 +30,7 @@ function machine_state_value(m: Node, state: Node) -> pointer {
function emit_machine(st: Node) -> void {
let gv = machine_var(st.a)
var s = ""
var s: pointer = ""
if (gv != null) { s = emit_bind(`load i32, ptr @g_{st.a.s}`) }
else {
let regv = emit_expr(st.a)

View file

@ -38,7 +38,7 @@ function emit_query(st: Node) -> void {
while t < len(terms.kids) {
let tm = terms.kids[t]
let ak = find_arch_id(tm.s)
var ok = "0"
var ok: pointer = "0"
if ak > 0 {
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(i1); emit("\n")
let kv = emit_bind(`load i32, ptr {kp}`)