fix(migrate): 0.R4 - --prune keeps a state declared after a plain parameter; a parameter named twice is refused
On Maroon Lake the prune gave home_keep_records(base_app_st, home_st, r: RunRecords, save_app_st) a second save_app_st at the front, and every call a second argument: a state declared after a plain parameter was not counted as declared. It is now, and a call to such a function is never given the state again. `ludic build --check` let the duplicate through and clang refused it; the checker now refuses a function that names two parameters alike (`add names two parameters n`). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
71735b10a2
commit
c35481f344
8 changed files with 47291 additions and 46599 deletions
|
|
@ -318,7 +318,8 @@ migrate: 1804 vars into 126 states, 64 into lets; 23498 edits in 460 files
|
||||||
function nor anything it calls uses, and the argument that fills it at every call. An argument for a
|
function nor anything it calls uses, and the argument that fills it at every call. An argument for a
|
||||||
parameter the callee no longer has goes too - a package's verb that dropped a state leaves its callers
|
parameter the callee no longer has goes too - a package's verb that dropped a state leaves its callers
|
||||||
passing one too many, and `ludic migrate state --prune <program>` puts them right. A reducer keeps its
|
passing one too many, and `ludic migrate state --prune <program>` puts them right. A reducer keeps its
|
||||||
state. When ludic.base's queues began keeping their own counts, every package verb lost its
|
state, and a state declared after a plain parameter (`home_keep(r: Records, save_st: mut Save)`) is
|
||||||
|
kept where it is. When ludic.base's queues began keeping their own counts, every package verb lost its
|
||||||
`base_st` this way (`wallet_earn(wallet_st, n)`, not `wallet_earn(base_st, wallet_st, n)`):
|
`base_st` this way (`wallet_earn(wallet_st, n)`, not `wallet_earn(base_st, wallet_st, n)`):
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -399,7 +400,8 @@ What it holds apart: `int`, `float`, `fixed` and `bool` (a float or a fixed into
|
||||||
`int(x)`; a float and a fixed never meet but by a literal, which takes whichever kind its slot is);
|
`int(x)`; a float and a fixed never meet but by a literal, which takes whichever kind its slot is);
|
||||||
text and numbers (`string(n)` or a template); one record type and another; slices of different
|
text and numbers (`string(n)` or a template); one record type and another; slices of different
|
||||||
elements; functions of different types. A call gives exactly as many arguments as there are
|
elements; functions of different types. A call gives exactly as many arguments as there are
|
||||||
parameters, a `return` gives the declared result, and `push` gives the slice's own element.
|
parameters, a `return` gives the declared result, and `push` gives the slice's own element. A
|
||||||
|
function names each parameter once (`add names two parameters n`).
|
||||||
|
|
||||||
`pointer` is untyped, as `void *` is in C: it goes wherever a reference is wanted and takes any
|
`pointer` is untyped, as `void *` is in C: it goes wherever a reference is wanted and takes any
|
||||||
reference, and a `[]pointer` any slice of references. Restricting what a raw pointer may reach is
|
reference, and a `[]pointer` any slice of references. Restricting what a raw pointer may reach is
|
||||||
|
|
|
||||||
8
changes/prune-late-states.md
Normal file
8
changes/prune-late-states.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
bump: patch
|
||||||
|
type: fix
|
||||||
|
**`ludic migrate state --prune` keeps a state declared after a plain parameter, and a parameter named twice is refused.**
|
||||||
|
A function like `home_keep(r: Records, save_st: mut Save)` was taken to lack `save_st`, and the
|
||||||
|
prune gave it a second one at the front (`home_keep(save_st: mut Save, r: Records, save_st: mut
|
||||||
|
Save)`) - which `ludic build --check` let through and clang then refused. The migration now counts a
|
||||||
|
state anywhere in the list as declared, and the checker refuses a function that names two parameters
|
||||||
|
alike (`add names two parameters n`).
|
||||||
5
examples/rejected/param_twice.ludic
Normal file
5
examples/rejected/param_twice.ludic
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# a parameter named twice is refused where it is written
|
||||||
|
program ParamTwice {
|
||||||
|
function add(n: int, k: int, n: int) -> int { return n + k }
|
||||||
|
entry { print(`{add(1, 2, 3)}`) }
|
||||||
|
}
|
||||||
|
|
@ -154,12 +154,26 @@ function ck_stmt(s: Node) -> void {
|
||||||
}
|
}
|
||||||
ck_walk(s)
|
ck_walk(s)
|
||||||
}
|
}
|
||||||
|
# a name given to two parameters: the second would shadow the first, and nothing could reach it
|
||||||
|
function ck_param_twice(d: Node, i: int) -> void {
|
||||||
|
var j = 0
|
||||||
|
while j < i {
|
||||||
|
if d.kids[j].kind == N_PARAM and (d.kids[j].s == d.kids[i].s) {
|
||||||
|
ck_err("arity", d.kids[i], `{d.s} names two parameters {d.kids[i].s}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
j += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
function ck_fn_body(d: Node) -> void {
|
function ck_fn_body(d: Node) -> void {
|
||||||
mg_enter(d, 0)
|
mg_enter(d, 0)
|
||||||
let m = ck_mark()
|
let m = ck_mark()
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(d.kids) {
|
while i < len(d.kids) {
|
||||||
if d.kids[i].kind == N_PARAM { ck_bind_param(d.kids[i]) }
|
if d.kids[i].kind == N_PARAM {
|
||||||
|
ck_param_twice(d, i)
|
||||||
|
ck_bind_param(d.kids[i])
|
||||||
|
}
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
ck_ret = d.ty
|
ck_ret = d.ty
|
||||||
|
|
|
||||||
|
|
@ -559,8 +559,22 @@ function mg_list(set: pointer) -> []int {
|
||||||
function mg_declared(n: Node, s: int) -> bool {
|
function mg_declared(n: Node, s: int) -> bool {
|
||||||
if n.kind != N_FN { return false }
|
if n.kind != N_FN { return false }
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(n.kids) and n.kids[i].kind == N_PARAM and is_state_ty(n.kids[i].ty) {
|
while i < len(n.kids) { # anywhere in the list: a state after a plain parameter is declared too
|
||||||
if (n.kids[i].ty == g_ms_name[s]) { return true }
|
if n.kids[i].kind == N_PARAM and (n.kids[i].ty == g_ms_name[s]) { return true }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
# a state declared after a plain parameter: every call already names it where it stands
|
||||||
|
function mg_declared_late(n: Node, s: int) -> bool {
|
||||||
|
if n.kind != N_FN { return false }
|
||||||
|
var plain = false
|
||||||
|
var i = 0
|
||||||
|
while i < len(n.kids) {
|
||||||
|
if n.kids[i].kind == N_PARAM {
|
||||||
|
if not is_state_ty(n.kids[i].ty) { plain = true }
|
||||||
|
else if plain and (n.kids[i].ty == g_ms_name[s]) { return true }
|
||||||
|
}
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
@ -972,7 +986,7 @@ function mg_calls() -> void {
|
||||||
let passed = g_mc_passed[c] == 1
|
let passed = g_mc_passed[c] == 1
|
||||||
var k = 0
|
var k = 0
|
||||||
while k < len(need) {
|
while k < len(need) {
|
||||||
if not (mg_declared(callee, need[k]) and passed) {
|
if not (mg_declared(callee, need[k]) and (passed or mg_declared_late(callee, need[k]))) {
|
||||||
if len(text) > 0 { text = text + ", " }
|
if len(text) > 0 { text = text + ", " }
|
||||||
text = text + g_ms_param[need[k]]
|
text = text + g_ms_param[need[k]]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46913
selfhost/ludicc.seed.ll
46913
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -539,7 +539,8 @@ function migrate_case() -> void {
|
||||||
|
|
||||||
# ludic migrate state --prune: a state nothing uses leaves the functions that take it and the calls
|
# ludic migrate state --prune: a state nothing uses leaves the functions that take it and the calls
|
||||||
# that pass it; an argument for a parameter the callee no longer has goes too (a package's verb that
|
# that pass it; an argument for a parameter the callee no longer has goes too (a package's verb that
|
||||||
# dropped a state), and the program then builds and runs
|
# dropped a state), a state declared after a plain parameter is kept where it is, and the program
|
||||||
|
# then builds and runs
|
||||||
function migrate_prune_case() -> void {
|
function migrate_prune_case() -> void {
|
||||||
let lbl = "ludic migrate state --prune: unused states and the arguments that fill them go, the same output"
|
let lbl = "ludic migrate state --prune: unused states and the arguments that fill them go, the same output"
|
||||||
let work = `{tmp_dir()}/migprune`
|
let work = `{tmp_dir()}/migprune`
|
||||||
|
|
@ -550,12 +551,13 @@ function migrate_prune_case() -> void {
|
||||||
src = src + " function add(log_st: mut Log, b: mut Bag, v: int) -> void { put(log_st, b, v) }\n"
|
src = src + " function add(log_st: mut Log, b: mut Bag, v: int) -> void { put(log_st, b, v) }\n"
|
||||||
src = src + " function outer(log_st: mut Log, b: mut Bag) -> void { add(log_st, b, 4) }\n"
|
src = src + " function outer(log_st: mut Log, b: mut Bag) -> void { add(log_st, b, 4) }\n"
|
||||||
src = src + " function top(log_st: Log, b: Bag) -> int { return head(log_st, b.items) }\n"
|
src = src + " function top(log_st: Log, b: Bag) -> int { return head(log_st, b.items) }\n"
|
||||||
src = src + " function said(log_st: mut Log, b: Bag) -> int {\n push(log_st.lines, \"x\")\n return len(log_st.lines)\n }\n"
|
src = src + " function said(log_st: mut Log, b: Bag) -> int {\n push(log_st.lines, \"x\")\n return late(3, log_st)\n }\n"
|
||||||
|
src = src + " function late(k: int, log_st: Log) -> int { return len(log_st.lines) + k - 3 }\n"
|
||||||
src = src + " entry (log_st: mut Log, b: mut Bag) {\n outer(log_st, b)\n print(`{top(log_st, b)} {said(log_st, b)}`)\n }\n}\n"
|
src = src + " entry (log_st: mut Log, b: mut Bag) {\n outer(log_st, b)\n print(`{top(log_st, b)} {said(log_st, b)}`)\n }\n}\n"
|
||||||
write_file(`{work}/prune.ludic`, src)
|
write_file(`{work}/prune.ludic`, src)
|
||||||
if not shq(`bin/ludic migrate state --prune {work}/prune.ludic > {work}/out.txt 2>&1`) { bad2(lbl, capture_line(`tail -1 {work}/out.txt`)); return }
|
if not shq(`bin/ludic migrate state --prune {work}/prune.ludic > {work}/out.txt 2>&1`) { bad2(lbl, capture_line(`tail -1 {work}/out.txt`)); return }
|
||||||
let got = read_file(`{work}/prune.ludic`)
|
let got = read_file(`{work}/prune.ludic`)
|
||||||
if not s_contains(got, "function add(b: mut Bag, v: int) -> void { put(b, v) }") or not s_contains(got, "function outer(b: mut Bag) -> void { add(b, 4) }") or not s_contains(got, "function top(b: Bag) -> int { return head(b.items) }") or not s_contains(got, "function said(log_st: mut Log) -> int") or not s_contains(got, "entry (log_st: mut Log, b: mut Bag)") or not s_contains(got, "{top(b)} {said(log_st)}") {
|
if not s_contains(got, "function add(b: mut Bag, v: int) -> void { put(b, v) }") or not s_contains(got, "function outer(b: mut Bag) -> void { add(b, 4) }") or not s_contains(got, "function top(b: Bag) -> int { return head(b.items) }") or not s_contains(got, "function said(log_st: mut Log) -> int") or not s_contains(got, "entry (log_st: mut Log, b: mut Bag)") or not s_contains(got, "{top(b)} {said(log_st)}") or not s_contains(got, "function late(k: int, log_st: Log) -> int") or not s_contains(got, "return late(3, log_st)") {
|
||||||
bad2(lbl, `rewrote it as [{got}]`)
|
bad2(lbl, `rewrote it as [{got}]`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -1025,6 +1027,7 @@ function cmd_dev_test() -> int {
|
||||||
migrate_prune_case()
|
migrate_prune_case()
|
||||||
vis_report_case("rejected/uses_missing", "uses: examples/rejected/uses_valley/items/index.ludic:3: items.inv_add used from", "LUDIC_VIS_REPORT=1 lists a uses violation and builds")
|
vis_report_case("rejected/uses_missing", "uses: examples/rejected/uses_valley/items/index.ludic:3: items.inv_add used from", "LUDIC_VIS_REPORT=1 lists a uses violation and builds")
|
||||||
feat_case("lang/checked", "", "7.5 3 12 hi 2 ok", "checked.ludic (L4: literals take their slot's kind, string(p), []string as []pointer, null, named args)")
|
feat_case("lang/checked", "", "7.5 3 12 hi 2 ok", "checked.ludic (L4: literals take their slot's kind, string(p), []string as []pointer, null, named args)")
|
||||||
|
reject_case("rejected/param_twice", "add names two parameters n", "a parameter named twice is refused")
|
||||||
reject_case("rejected/wrong_arity", "this call to area leaves out h, which has no default", "a call with the wrong number of arguments is refused")
|
reject_case("rejected/wrong_arity", "this call to area leaves out h, which has no default", "a call with the wrong number of arguments is refused")
|
||||||
reject_case("rejected/float_into_int", "metres wants an int and this is a float", "a float computed into an int is refused")
|
reject_case("rejected/float_into_int", "metres wants an int and this is a float", "a float computed into an int is refused")
|
||||||
reject_case("rejected/record_mixup", "d wants a Door and this is a Key", "one record type is not another")
|
reject_case("rejected/record_mixup", "d wants a Door and this is a Key", "one record type is not another")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue