wip(0.S1, 0.S2): state records, mut and read-only state parameters, entry injection, module var refused; ludic migrate state
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
63a1fa1378
commit
1e8b5b0523
35 changed files with 1227 additions and 30 deletions
|
|
@ -13,7 +13,9 @@ function ck_params(f: Node, labels: []pointer, tys: []pointer) -> void {
|
|||
}
|
||||
}
|
||||
function ck_call_fn(e: Node, name: pointer, f: Node) -> pointer {
|
||||
mg_call(e, f)
|
||||
call_fill_defaults(e, f)
|
||||
ck_state_args(e, f)
|
||||
let labels = new []pointer
|
||||
let tys = new []pointer
|
||||
ck_params(f, labels, tys)
|
||||
|
|
@ -22,6 +24,7 @@ function ck_call_fn(e: Node, name: pointer, f: Node) -> pointer {
|
|||
return f.ty
|
||||
}
|
||||
function ck_call_alias(e: Node, name: pointer, al: int, f: Node) -> pointer {
|
||||
state_inject(e, f)
|
||||
if g_al_labels[al] == null { call_fill_defaults(e, f) } else { call_mixed_to_named(e, ns_alias_labels(al)) }
|
||||
let labels = new []pointer
|
||||
let tys = new []pointer
|
||||
|
|
@ -141,6 +144,7 @@ function ck_builtin(e: Node, name: pointer) -> pointer {
|
|||
return "void"
|
||||
}
|
||||
if (name == "push") and len(e.kids) == 2 {
|
||||
ck_write_check(e.kids[0], "push")
|
||||
let st = ck_expr(e.kids[0])
|
||||
let vt = ck_expr(e.kids[1])
|
||||
if not ck_unknown(st) and is_slice_ty(st) { ck_give(slice_elem(st), vt, e.kids[1], `push onto {ck_a(st)}`) }
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ function ck_bind(name: pointer, ty: pointer) -> void {
|
|||
push(ck_tys, t)
|
||||
}
|
||||
ck_top += 1
|
||||
ck_set_ro(false)
|
||||
}
|
||||
function ck_mark() -> int { return ck_top }
|
||||
function ck_pop(m: int) -> void { ck_top = m }
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ function ck_id(e: Node) -> pointer {
|
|||
if li >= 0 { return ck_tys[li] }
|
||||
let g = ck_global(e.s)
|
||||
if g != null {
|
||||
mg_ref(e, g)
|
||||
ck_vis(g, e.s, e)
|
||||
return g.ty
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,9 @@ function gen_clone(n: Node, tps: []pointer, args: []pointer) -> Node {
|
|||
c.line = n.line
|
||||
c.file = n.file
|
||||
c.vis = n.vis
|
||||
c.uns = n.uns
|
||||
c.pos = n.pos
|
||||
c.pos2 = n.pos2
|
||||
c.a = gen_clone(n.a, tps, args)
|
||||
c.b = gen_clone(n.b, tps, args)
|
||||
c.c = gen_clone(n.c, tps, args)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ function gen_fn_instance(t: Node, iname: pointer, binds: []pointer) -> Node {
|
|||
if have != null { return have }
|
||||
let f = gen_clone(t, gen_split(t.tps), binds)
|
||||
f.s = iname
|
||||
if g_migrate { f.mg = mg_unit(t, 0) } # an instance's needs are its generic's
|
||||
push(g_gen_out, f)
|
||||
push(g_gen_work, f)
|
||||
ck_tab_put(ck_fn_k, ck_fn_v, iname, f)
|
||||
|
|
@ -44,6 +45,7 @@ function gen_arg_nodes(e: Node, labels: []pointer) -> []Node {
|
|||
return out
|
||||
}
|
||||
function gen_call(e: Node, name: pointer, t: Node) -> pointer {
|
||||
mg_call(e, t)
|
||||
let expect = ck_call_expect
|
||||
call_fill_defaults(e, t)
|
||||
let tps = gen_split(t.tps)
|
||||
|
|
|
|||
79
selfhost/check/check_state.ludic
Normal file
79
selfhost/check/check_state.ludic
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# check_state.ludic — 0.S: what a state parameter allows. `h: mut Hiker` may change the state;
|
||||
# `h: Hiker` may only read it - an assignment whose target starts at it (`h.hips = 1`,
|
||||
# `h.list[i] = x`), a push onto something in it, or passing it where a `mut Hiker` is wanted is
|
||||
# refused. A module-level `let` (and a const, a registry, a state's own instance) is immutable all
|
||||
# the way down: the same writes starting at one are refused. What this does not follow: a reference
|
||||
# read out of a read-only state into a local (`let l = h.list`) and changed there.
|
||||
var ck_ro: []int = new []int # parallel to ck_names: 1 for a read-only state
|
||||
|
||||
function ck_set_ro(ro: bool) -> void {
|
||||
let k = ck_top - 1
|
||||
while len(ck_ro) < len(ck_names) { push(ck_ro, 0) }
|
||||
if ro { ck_ro[k] = 1 } else { ck_ro[k] = 0 }
|
||||
}
|
||||
function ck_is_ro(li: int) -> bool {
|
||||
if li < 0 or li >= len(ck_ro) { return false }
|
||||
return ck_ro[li] == 1
|
||||
}
|
||||
# the name an assignment's target starts at: `a` in `a.b[c].d`
|
||||
function ck_chain_root(t: Node) -> Node {
|
||||
var n = t
|
||||
while n != null and (n.kind == E_INDEX or n.kind == E_MEMBER) { n = n.a }
|
||||
if n == null or n.kind != E_ID { return null }
|
||||
return n
|
||||
}
|
||||
function ck_module_let(g: Node) -> bool {
|
||||
if g == null { return false }
|
||||
if g.kind == N_CONST { return true }
|
||||
if g.kind != N_VAR { return false }
|
||||
if g.uns == 1 { return true }
|
||||
return reg_find(g.s) >= 0
|
||||
}
|
||||
# a write into `t`: refused when it starts at a read-only state or at a module-level binding
|
||||
function ck_write_check(t: Node, what: pointer) -> void {
|
||||
let r = ck_chain_root(t)
|
||||
if r == null { return }
|
||||
let li = ck_local(r.s)
|
||||
if li >= 0 {
|
||||
if ck_is_ro(li) {
|
||||
ck_err("state", t, `{what}: {r.s} is read-only here ({r.s}: {ck_tys[li]}); take it as {r.s}: mut {ck_tys[li]} to change it`)
|
||||
}
|
||||
return
|
||||
}
|
||||
let g = ck_global(r.s)
|
||||
mg_write(t, g)
|
||||
if ck_module_let(g) {
|
||||
var name = r.s
|
||||
if is_state_ty(g.ty) and g.uns == 1 { name = `the state {g.ty}` }
|
||||
ck_err("state", t, `{what}: {name} is module-level and immutable all the way down; changing data belongs in a state, passed as a mut parameter`)
|
||||
}
|
||||
}
|
||||
# a function's parameters: `mut` is for a state, and a state without it is read-only
|
||||
function ck_bind_param(p: Node) -> void {
|
||||
ck_bind(p.s, p.ty)
|
||||
if p.uns == 1 and not is_state_ty(p.ty) {
|
||||
ck_err("state", p, `{p.s}: mut {p.ty} - mut is for a state parameter, and {p.ty} is not a state`)
|
||||
}
|
||||
ck_set_ro(is_state_ty(p.ty) and p.uns == 0)
|
||||
}
|
||||
# a call's arguments against the function's state parameters: a read-only state is not a mut one
|
||||
function ck_state_args(e: Node, f: Node) -> void {
|
||||
var k = 0
|
||||
var i = 0
|
||||
while i < len(f.kids) {
|
||||
let p = f.kids[i]
|
||||
if p.kind == N_PARAM {
|
||||
if p.uns == 1 and is_state_ty(p.ty) and k < len(e.kids) {
|
||||
let a = e.kids[k]
|
||||
if a.kind == E_ID {
|
||||
let li = ck_local(a.s)
|
||||
if li >= 0 and ck_is_ro(li) {
|
||||
ck_err("state", a, `{f.s} changes {p.ty} ({p.s}: mut {p.ty}), and {a.s} is read-only here`)
|
||||
}
|
||||
}
|
||||
}
|
||||
k += 1
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
|
@ -28,11 +28,21 @@ function ck_let(s: Node) -> void {
|
|||
if s.ty != null {
|
||||
if s.a != null { ck_give(s.ty, t, s.a, s.s) }
|
||||
ck_bind(s.s, s.ty)
|
||||
if s.tps != null and (s.tps == "state") { # an entry point's state (state.ludic)
|
||||
if not is_state_ty(s.ty) { ck_err("state", s, `an entry point takes states, and {s.s}: {s.ty} is not one`) }
|
||||
ck_set_ro(s.uns == 0)
|
||||
}
|
||||
return
|
||||
}
|
||||
ck_bind(s.s, t)
|
||||
# a local bound to a read-only state is read-only too
|
||||
if s.a != null and s.a.kind == E_ID {
|
||||
let li = ck_local(s.a.s)
|
||||
if li >= 0 and li < ck_top - 1 and ck_is_ro(li) { ck_set_ro(true) }
|
||||
}
|
||||
}
|
||||
function ck_assign(s: Node) -> void {
|
||||
ck_write_check(s.a, "this assignment")
|
||||
let lt = ck_target(s.a)
|
||||
if (s.s == "=") { ck_expect = lt }
|
||||
let rt = ck_expr(s.b)
|
||||
|
|
@ -116,10 +126,11 @@ function ck_stmt(s: Node) -> void {
|
|||
}
|
||||
}
|
||||
function ck_fn_body(d: Node) -> void {
|
||||
mg_enter(d, 0)
|
||||
let m = ck_mark()
|
||||
var i = 0
|
||||
while i < len(d.kids) {
|
||||
if d.kids[i].kind == N_PARAM { ck_bind(d.kids[i].s, d.kids[i].ty) }
|
||||
if d.kids[i].kind == N_PARAM { ck_bind_param(d.kids[i]) }
|
||||
i += 1
|
||||
}
|
||||
ck_ret = d.ty
|
||||
|
|
@ -132,8 +143,10 @@ function ck_fn_body(d: Node) -> void {
|
|||
if d.uns == 1 { ck_unsafe -= 1 }
|
||||
ck_ret = "void"
|
||||
ck_pop(m)
|
||||
mg_leave()
|
||||
}
|
||||
function ck_listener(l: Node) -> void {
|
||||
mg_enter(l, 4)
|
||||
let ev = find_event(l.s)
|
||||
ck_vis(ev, l.s, l)
|
||||
let m = ck_mark()
|
||||
|
|
@ -147,6 +160,7 @@ function ck_listener(l: Node) -> void {
|
|||
ck_ret = "void"
|
||||
ck_block(l.a)
|
||||
ck_pop(m)
|
||||
mg_leave()
|
||||
}
|
||||
# a program's function named like one of the runtime's takes every call the runtime makes to its
|
||||
# own - the runtime's ui_measure started answering to a package's - so the name is refused; one
|
||||
|
|
@ -177,7 +191,16 @@ function check_program() -> void {
|
|||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_FN { ck_fn_body(d) }
|
||||
if d.kind == N_MAIN or d.kind == N_TEST { ck_block(d.a) }
|
||||
if d.kind == N_MAIN or d.kind == N_TEST {
|
||||
mg_enter(d, 1)
|
||||
ck_block(d.a)
|
||||
mg_leave()
|
||||
}
|
||||
if d.kind == N_SYS and g_migrate { # a handler's body: only the migration reads it here
|
||||
mg_enter(d, 2)
|
||||
ck_block(d.a)
|
||||
mg_leave()
|
||||
}
|
||||
if (d.kind == N_VAR or d.kind == N_CONST) and d.a != null {
|
||||
ck_vis_file = d.file
|
||||
ck_vis_skip = d.kind == N_VAR and is_port_var(d)
|
||||
|
|
@ -195,11 +218,13 @@ function check_program() -> void {
|
|||
# a test block is checked like `entry`: that is also what makes its generic calls real
|
||||
j = 0
|
||||
while j < len(g_tests) {
|
||||
mg_enter(g_tests[j], 3)
|
||||
ck_block(g_tests[j].a)
|
||||
mg_leave()
|
||||
j += 1
|
||||
}
|
||||
gen_finish()
|
||||
if ck_errors > 0 and not ck_reporting() {
|
||||
if ck_errors > 0 and not ck_reporting() and not g_migrate {
|
||||
let m = `{itoa(ck_errors)} type error(s)\n`
|
||||
file_write(file_stderr(), m, len(m))
|
||||
exit(1)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue