wip(0.S2): a migration makes a declared read-only state mut where it is now changed; net_sync builds --unsafe

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 15:02:45 +03:00
parent e7f8ee6b91
commit d490d4f9f1
7 changed files with 48213 additions and 46785 deletions

View file

@ -6,13 +6,23 @@
# local (`let l = h.list`, `let r = TABLE[0]`) carries the same rule: a write through it is refused.
var ck_ro: []int = new []int # parallel to ck_names: 1 for a read-only state
var ck_alias: []Node = new []Node # parallel to ck_names: the global a local's reference came from
var ck_ro_st: []pointer = new []pointer # parallel to ck_names: the state that makes a local read-only
function ck_set_ro(ro: bool) -> void {
let k = ck_top - 1
while len(ck_ro) < len(ck_names) { push(ck_ro, 0) }
while len(ck_alias) < len(ck_names) { push(ck_alias, null) }
while len(ck_ro_st) < len(ck_names) { push(ck_ro_st, null) }
if ro { ck_ro[k] = 1 } else { ck_ro[k] = 0 }
ck_alias[k] = null
ck_ro_st[k] = null
if ro { ck_ro_st[k] = ck_tys[k] }
}
# the local just bound is read-only for the same state as local `li`
function ck_ro_like(li: int) -> void {
let k = ck_top - 1
ck_ro[k] = 1
ck_ro_st[k] = ck_ro_st[li]
}
# a value, not a reference: nothing can be written through it
function ck_scalar(t: pointer) -> bool {
@ -30,7 +40,7 @@ function ck_let_from(e: Node, ty: pointer) -> void {
let k = ck_top - 1
let li = ck_local(r.s)
if li >= 0 and li < k {
if ck_is_ro(li) { ck_ro[k] = 1 }
if ck_is_ro(li) { ck_ro_like(li) }
if li < len(ck_alias) { ck_alias[k] = ck_alias[li] }
return
}
@ -67,6 +77,10 @@ function ck_write_check(t: Node, what: pointer) -> void {
mg_write(t, ag)
if ck_module_let(ag) { ck_err("state", t, `{what}: {r.s} holds part of {mg_src_name(ag.s)}, which is module-level and immutable all the way down`) }
}
if ck_is_ro(li) and g_migrate and g_mg_cur >= 0 and ck_ro_st[li] != null {
mg_need(g_mg_cur, mg_state_named(ck_ro_st[li]), true) # 0.S2: it becomes `mut`
return
}
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`)
}

View file

@ -38,7 +38,7 @@ function ck_let(s: Node) -> void {
# a local bound to a read-only state, or to a reference out of one, 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) }
if li >= 0 and li < ck_top - 1 and ck_is_ro(li) { ck_ro_like(li) }
}
ck_let_from(s.a, t)
}

View file

@ -358,10 +358,34 @@ function mg_nparams(n: Node) -> int {
}
return k
}
# a state declared read-only (a run before) that the unit, or something it calls, now changes
function mg_mut_upgrades(u: int, n: Node) -> void {
var ps = n.kids
if g_mu_kind[u] != 0 {
ps = new []Node
var body = n.a
if g_mu_kind[u] == 5 { body = n }
var j = 0
while body != null and j < len(body.kids) and body.kids[j].tps != null and (body.kids[j].tps == "state") {
push(ps, body.kids[j])
j += 1
}
}
var i = 0
while i < len(ps) {
let p = ps[i]
if (p.kind == N_PARAM or p.kind == S_LET) and is_state_ty(p.ty) and p.uns == 0 and p.pos2 >= 0 and p.file != null {
let s = mg_state_named(p.ty)
if has_sub(g_mu_mut[u], `,{itoa(s)},`) { mg_emit(`INS {p.file} {itoa(p.pos2)} {mg_esc("mut ")}`, "") }
}
i += 1
}
}
function mg_units() -> void {
var u = 0
while u < len(g_mu_node) {
let n = g_mu_node[u]
mg_mut_upgrades(u, n)
let need = mg_list(g_mu_need[u])
if len(need) > 0 {
var text = ""

View file

@ -76,6 +76,7 @@ function parse_state() -> void {
}
# `name: mut T` in a parameter list: the `mut`, when it is there
function parse_param_mut(p: Node) -> void {
p.pos2 = toks[pi].pos # where `mut ` goes, when a migration adds it
if is_id("mut") and toks[pi + 1].kind == TK_ID {
pi += 1
p.uns = 1
@ -167,6 +168,7 @@ function entry_bind(body: Node, ps: []Node) -> void {
l.ival = 0
l.uns = p.uns # 1: a `mut` state; 0: read-only
l.tps = "state" # an entry point's state: checked to be one
l.pos2 = p.pos2
let id = node(E_ID)
id.s = state_global(p.ty)
id.file = p.file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -856,8 +856,8 @@ function cmd_dev_test() -> int {
g_case_unsafe = true # the transport ABI takes raw bytes
net_case("networking/net_echo", "4 10 20 30 42")
net_case("networking/net_snapshot", "50 7 50")
g_case_unsafe = false
net_case("networking/net_sync", "12 3 4 50 999")
g_case_unsafe = false
net_case("networking/net_owner", "-1 7 0 1")
net_case("networking/net_rpc", "0 8")
net_case("networking/net_roles", "1 102")