registry NAME of RECORD [as PREFIX] is a global table; def NAME key { ... } in
any file is one entry, collected in source order and filled before any code
runs. Each entry gets an index constant (PREFIX_KEY), the table PREFIX_COUNT,
and a record with a key field gets it filled and a NAME_find(key). A record
literal naming a field its record lacks is now an error everywhere; a global's
initializer is lowered as its own file's code (its errors, and what its module
may see, were whichever statement came last).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
139 lines
4 KiB
Text
139 lines
4 KiB
Text
# check_expr.ludic — L4: the type of an expression, and every mix-up inside it. Each form works
|
|
# out its operands' types first, so an error deep in an argument is found as well as one at the top.
|
|
function ck_field(r: Node, name: pointer) -> pointer {
|
|
var i = 0
|
|
while i < len(r.kids) {
|
|
if (r.kids[i].s == name) { return r.kids[i].ty }
|
|
i += 1
|
|
}
|
|
return null
|
|
}
|
|
# anything the checker does not type is still walked for the errors inside it
|
|
function ck_walk(e: Node) -> void {
|
|
if e == null { return }
|
|
ck_any(e.a)
|
|
ck_any(e.b)
|
|
ck_any(e.c)
|
|
var i = 0
|
|
while i < len(e.kids) {
|
|
ck_any(e.kids[i])
|
|
i += 1
|
|
}
|
|
}
|
|
function ck_any(n: Node) -> void {
|
|
if n == null { return }
|
|
if n.kind == N_BLOCK { ck_block(n); return }
|
|
if n.kind == E_FINIT { ck_expr(n.a); return }
|
|
if (n.kind >= S_LET and n.kind <= S_BECOME) or n.kind == S_EMIT or n.kind == S_UNSAFE { ck_stmt(n); return }
|
|
ck_expr(n)
|
|
}
|
|
function ck_expr(e: Node) -> pointer {
|
|
if e == null { return "?" }
|
|
let ex = ck_expect
|
|
ck_expect = null
|
|
let k = e.kind
|
|
if k == E_INT { return "int" }
|
|
if k == E_FLOAT {
|
|
if is_float_file(e.file) { return "float" }
|
|
return "fixed"
|
|
}
|
|
if k == E_STR { return "string" }
|
|
if k == E_SLICE { ck_walk(e); return "string" }
|
|
if k == E_BOOL { return "bool" }
|
|
if k == E_NULL { return "null" }
|
|
if k == E_ID { return ck_id(e) }
|
|
if k == E_FNREF {
|
|
let f = ck_fn(e.s)
|
|
if f != null { return fn_sig_of(f) }
|
|
return "?"
|
|
}
|
|
if k == E_NEW { return ck_new(e) }
|
|
if k == E_LIST { return ck_list(e) }
|
|
if k == E_BIN { return ck_bin(e) }
|
|
if k == E_UN { return ck_un(e) }
|
|
if k == E_MEMBER { return ck_member(e) }
|
|
if k == E_INDEX { return ck_index_of(e) }
|
|
if k == E_CALL {
|
|
ck_call_expect = ex
|
|
return ck_call(e)
|
|
}
|
|
if k == S_EMIT { ck_emit(e); return "?" }
|
|
ck_walk(e)
|
|
return "?"
|
|
}
|
|
function ck_id(e: Node) -> pointer {
|
|
let li = ck_local(e.s)
|
|
if li >= 0 { return ck_tys[li] }
|
|
let g = ck_global(e.s)
|
|
if g != null { return g.ty }
|
|
return "?"
|
|
}
|
|
function ck_new(e: Node) -> pointer {
|
|
let r = ck_record(e.s)
|
|
if e.a != null {
|
|
var i = 0
|
|
while i < len(e.a.kids) {
|
|
let fi = e.a.kids[i]
|
|
let vt = ck_expr(fi.a)
|
|
if r != null and fi.kind == E_FINIT {
|
|
let ft = ck_field(r, fi.s)
|
|
if ft != null { ck_give(ft, vt, fi.a, `field {fi.s} of {e.s}`) }
|
|
if ft == null { ck_err("field", fi.a, `{gen_show(e.s)} has no field {fi.s}`) }
|
|
}
|
|
i += 1
|
|
}
|
|
}
|
|
return e.s
|
|
}
|
|
function ck_list(e: Node) -> pointer {
|
|
var t: pointer = "?"
|
|
var i = 0
|
|
while i < len(e.kids) {
|
|
let et = ck_expr(e.kids[i])
|
|
if ck_unknown(t) { t = et }
|
|
else { ck_give(t, et, e.kids[i], `an element of a []{t}`) }
|
|
i += 1
|
|
}
|
|
if ck_unknown(t) or (t == "null") { return "?" }
|
|
return "[]" + t
|
|
}
|
|
function ck_un(e: Node) -> pointer {
|
|
let t = ck_expr(e.a)
|
|
if (e.s == "not") or (e.s == "!") {
|
|
ck_cond(e.a, t)
|
|
return "bool"
|
|
}
|
|
if (e.s == "-") and (t == "bool") { ck_err("bool", e, "- on a bool") }
|
|
return t
|
|
}
|
|
function ck_member(e: Node) -> pointer {
|
|
let b = e.a
|
|
if b.kind == E_ID and ck_local(b.s) < 0 and ck_global(b.s) == null {
|
|
# a namespace, an enum or a component name: Math.PI, Color.Red, Thing.of
|
|
let en = ck_enum(b.s)
|
|
if en != null and not enum_is_tagged(en) { return b.s }
|
|
return "?"
|
|
}
|
|
let bt = ck_expr(b)
|
|
if ck_unknown(bt) { return "?" }
|
|
let r = ck_record(bt)
|
|
if r != null {
|
|
let ft = ck_field(r, e.s)
|
|
if ft != null { return ft }
|
|
}
|
|
return "?"
|
|
}
|
|
function ck_index_of(e: Node) -> pointer {
|
|
let bt = ck_expr(e.a)
|
|
let it = ck_expr(e.b)
|
|
if ck_is_raw(bt) { ck_raw(e, `indexing {ck_a(bt)}`) }
|
|
if not ck_unknown(it) and not ck_is_int(it) { ck_err("index", e.b, `an index wants an int and this is {ck_a(it)}`) }
|
|
if ck_unknown(bt) { return "?" }
|
|
if is_slice_ty(bt) { return slice_elem(bt) }
|
|
if (bt == "string") or (bt == "words") { return "int" }
|
|
if (bt == "floats") { return "float" }
|
|
if (bt == "doubles") { return "double" }
|
|
if (bt == "fixeds") { return "fixed" }
|
|
if (bt == "pointers") { return "pointer" }
|
|
return "?"
|
|
}
|