aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-15 14:57:42 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-15 14:57:42 +0330
commit0432ac6aefccbc5a9af25bd4ed2d4451611c4ff4 (patch)
treeba312d92bc223e1a4c500134cf1343f7e1f63180
parentmerge work/borrow: borrow returns before definition, borrows clause, borrow m... (diff)
analyzer: an outer owner moved into a deeper container can take it back (loop-built lists); block-end frees name the owner (why 9) in messages and LSP related info
-rw-r--r--src/compiler/analyzer.zc38
-rw-r--r--src/compiler/base.zc11
-rw-r--r--src/lsp/server.zc1
-rw-r--r--tests/conformance/6-ownership/borrow_outlives_block.zc21
-rw-r--r--tests/conformance/6-ownership/loop_build_list_item_after_move.zc13
-rw-r--r--tests/conformance/6-ownership/loop_build_list_not_taken_back.zc13
-rw-r--r--tests/conformance/6-ownership/loop_build_list_ok.zc21
-rw-r--r--tests/conformance/6-ownership/loop_build_list_other_owner.zc13
-rw-r--r--tests/conformance/6-ownership/outer_owner_in_inner_container.zc13
9 files changed, 140 insertions, 4 deletions
diff --git a/src/compiler/analyzer.zc b/src/compiler/analyzer.zc
index ff6a6c1..a1588e8 100644
--- a/src/compiler/analyzer.zc
+++ b/src/compiler/analyzer.zc
@@ -122,7 +122,7 @@ int shadow_finish(int from) {
while (k < 24) { if ((e[2] >> k) & 1) flags = flags | effect_get(e[1], k); k++; }
if ((flags & (EF_ALIAS | EF_IDENTITY)) && !(flags & EF_UNKNOWN) && !dry) {
tunpack(e[3]); curinc = e[4]; panic = 0;
- errat(); ended_message(e[6], e[7], e[8], e[9]); rt.put_s("; "); rt.put_s(sname[e[1]]); rt.put_s(", defined after this call, returns a borrow of its argument"); errend();
+ errat(); ended_message(e[6], e[7], e[8], e[9], 0); rt.put_s("; "); rt.put_s(sname[e[1]]); rt.put_s(", defined after this call, returns a borrow of its argument"); errend();
}
e = next;
}
@@ -1147,6 +1147,7 @@ int block_close(int save) {
unused_check(save);
exit_vars(fdepth, 1);
lend_drop(fdepth, save);
+ block_end_owners(save);
alias_end(fdepth, save);
while (i < save) { if (lhome[i] >= fdepth) { lstate[i] = S_DEAD; lhome[i] = fdepth - 1; } i++; }
local_drop(save);
@@ -1155,6 +1156,26 @@ int block_close(int save) {
return 0;
}
+// owners freed at this block's end: an outer alias of one, or an outer owner
+// stored into one, dies with it and remembers where (why 9).
+int block_end_owners(int save) {
+ int i = save, j; word info = 0;
+ if (!fchk || !live) return 0;
+ while (i < nloc) {
+ if (lhome[i] >= fdepth && lstate[i] == S_M && lown[i]) {
+ info = 0; j = lbase;
+ while (j < save) {
+ if (lstate[j] == S_CHILD && lroot(j) == i) {
+ if (!info) { info = talloc(6); info[0] = lname[i]; info[1] = lpos[i]; info[2] = tpack(); info[3] = 9; info[4] = 0; info[5] = info[2]; }
+ local_ended[j] = info[2]; local_end_info[j] = info; lstate[j] = S_DEAD;
+ }
+ j++;
+ }
+ }
+ i++;
+ }
+ return 0;
+}
// an alias in an outer block whose owner is about to disappear dies with it
int alias_end(int dep, int save) {
int i, r;
@@ -1532,6 +1553,19 @@ int assigncheck(int k, int i, int rc) {
else lstate[i] = S_PLAIN;
return C_PLAIN;
}
+ // an outer owner moved into a deeper container takes the container back:
+ // head = item after item.next = head. the container leaves its block.
+ if (st == S_CHILD && lown[i] && j >= 0 && lroot(i) == j && lstate[j] == S_M && lown[j]
+ && lhome[j] >= lhome[i] && !local_captured[i] && !local_captured[j]) {
+ int k = lbase;
+ while (k < nloc) { if (k != i && lstate[k] == S_CHILD && lowner[k] == j) lowner[k] = i; k++; }
+ lstate[i] = S_M; lused[i] = 0;
+ lnsz[i] = lnsz[j]; lleaf[i] = lleaf[j];
+ local_ended[i] = 0; local_end_info[i] = 0;
+ end_why = 3; end_whom = lname[i]; end_owner(j); luse(j); ev_put(j, EV_MOVE, i);
+ if (ltag[i] & 15) lstate[i] = ltag[i] & 15;
+ return C_PLAIN;
+ }
if (!empty(st)) {
cerrn(i, "still owns an object; overwriting it would leak it");
return C_PLAIN;
@@ -1587,7 +1621,7 @@ int assigncheck(int k, int i, int rc) {
}
else { end_why = 4; end_whom = lname[i]; end_owner(j); luse(j); ev_put(j, EV_STORE, i); emitown(i); } // storing it gives it away
} else if (js != st) cerrn(j, "its tag doesn't match its container's (both have to be m, u or d)");
- else if (lhome[j] < lhome[r]) cerrn(j, "lives in an outer block than the container it's stored into");
+ else if (lhome[j] < lhome[r] && (r != i || !lown[j] || (local_captured && (local_captured[i] || local_captured[j])))) cerrn(j, "lives in an outer block than the container it's stored into");
else { lstate[j] = S_CHILD; lowner[j] = i; luse(j); ev_put(j, EV_STORE, i); emitown(i); }
return C_PLAIN;
}
diff --git a/src/compiler/base.zc b/src/compiler/base.zc
index 7e4b5bc..de699aa 100644
--- a/src/compiler/base.zc
+++ b/src/compiler/base.zc
@@ -980,9 +980,16 @@ int cerr(char* msg) {
}
// how an owner ended, from end_owner's record: the use, the owner, what ended it and where.
// "r: used after it was given away; r borrows t, and t was freed on line 5 (t declared on line 4)"
-int ended_message(char* use, word info, char* root, int cause) {
+int ended_message(char* use, word info, char* root, int cause, int own) {
char* owner = root; int why = 0; char* whom = 0;
if (info) { owner = info[0]; why = info[3]; whom = info[4]; dg_related(info[5], info[1], why, owner); }
+ if (why == 9) {
+ rt.put_s(use); rt.put_s(": ");
+ if (rt.eq_w(use, owner)) rt.put_s("was freed");
+ else { if (own) rt.put_s("belongs to "); else rt.put_s("borrows from "); rt.put_s(owner); rt.put_s(", which was freed"); }
+ rt.put_s(" at the end of its block (line "); rt.put_n(cause & 0xFFFFFF); rt.put_c(')');
+ return 0;
+ }
rt.put_s(use); rt.put_s(": used after it was given away; ");
if (!rt.eq_w(use, owner)) { rt.put_s(use); rt.put_s(" borrows "); rt.put_s(owner); rt.put_s(", and "); }
rt.put_s(owner);
@@ -1008,7 +1015,7 @@ int cerrn(int i, char* msg) {
if (!info && local_end_info) info = local_end_info[root];
if (info) cause = info[2]; else if (local_ended) cause = local_ended[root];
}
- if (cause && !panic && !dry) { errat(); dg_place(lreadpos[i]); ended_message(lname[i], info, lname[root], cause); errend(); }
+ if (cause && !panic && !dry) { errat(); dg_place(lreadpos[i]); ended_message(lname[i], info, lname[root], cause, lown[i]); errend(); }
else serrn(lname[i], msg);
}
return 0;
diff --git a/src/lsp/server.zc b/src/lsp/server.zc
index 65881a9..0fda725 100644
--- a/src/lsp/server.zc
+++ b/src/lsp/server.zc
@@ -278,6 +278,7 @@ int diag_related(int at) {
else if (why == 3) what = " was moved here"; else if (why == 4) what = " was stored into a container here";
else if (why == 5) what = " was returned here"; else if (why == 6) what = " was stored in a global here";
else if (why == 7) what = " was freed by this reassignment"; else if (why == 8) what = " was pushed into an array here";
+ else if (why == 9) what = " was freed at the end of this block";
ws(",\"relatedInformation\":[{\"location\":"); wplace(e[1]); ws(",\"message\":\"owner "); ws(e[4]); ws(what); ws("\"}");
if (e[2]) { ws(",{\"location\":"); wplace(e[2]); ws(",\"message\":\"owner "); ws(e[4]); ws(" is declared here\"}"); }
wc(']');
diff --git a/tests/conformance/6-ownership/borrow_outlives_block.zc b/tests/conformance/6-ownership/borrow_outlives_block.zc
new file mode 100644
index 0000000..4d9b44a
--- /dev/null
+++ b/tests/conformance/6-ownership/borrow_outlives_block.zc
@@ -0,0 +1,21 @@
+// expect error: second: borrows from head, which was freed at the end of its block (line 17)
+// spec 6.6.3
+include rt;
+
+struct node {
+ int value;
+ node* next;
+};
+
+fn main() {
+ node* second;
+ {
+ node* head = rt.new(node);
+ head.next = rt.new(node);
+ head.next.value = 1;
+ second = head.next;
+ }
+ rt.put_n(second.value);
+ rt.put_c(10);
+}
+// zc0: skip
diff --git a/tests/conformance/6-ownership/loop_build_list_item_after_move.zc b/tests/conformance/6-ownership/loop_build_list_item_after_move.zc
new file mode 100644
index 0000000..5346569
--- /dev/null
+++ b/tests/conformance/6-ownership/loop_build_list_item_after_move.zc
@@ -0,0 +1,13 @@
+// expect error: item: used after it was given away; item was moved into head
+// spec 6.8.5
+include rt;
+struct node { int value; node* next; };
+fn main() {
+ node* head = rt.new(node);
+ int i = 0;
+ while (i < 3) {
+ node* item = rt.new(node); item.next = head; head = item; rt.put_n(item.value); i++;
+ }
+ rt.put_n(head.value);
+}
+// zc0: skip
diff --git a/tests/conformance/6-ownership/loop_build_list_not_taken_back.zc b/tests/conformance/6-ownership/loop_build_list_not_taken_back.zc
new file mode 100644
index 0000000..a9fadcd
--- /dev/null
+++ b/tests/conformance/6-ownership/loop_build_list_not_taken_back.zc
@@ -0,0 +1,13 @@
+// expect error: head: is given away inside a loop that comes back around to it
+// spec 6.8.5
+include rt;
+struct node { int value; node* next; };
+fn main() {
+ node* head = rt.new(node);
+ int i = 0;
+ while (i < 3) {
+ node* item = rt.new(node); item.next = head; i++;
+ }
+ rt.put_n(head.value);
+}
+// zc0: skip
diff --git a/tests/conformance/6-ownership/loop_build_list_ok.zc b/tests/conformance/6-ownership/loop_build_list_ok.zc
new file mode 100644
index 0000000..5f3de5a
--- /dev/null
+++ b/tests/conformance/6-ownership/loop_build_list_ok.zc
@@ -0,0 +1,21 @@
+// expect: 7/0/
+// spec 6.8.5
+include rt;
+memdebug on;
+struct node { int value; node* next; };
+fn main() {
+ {
+ node* head = rt.new(node);
+ int i = 0;
+ while (i < 5) {
+ node* item = rt.new(node);
+ item.value = i;
+ item.next = head;
+ head = item;
+ i++;
+ }
+ rt.put_n(head.value + head.next.value); rt.put_c(10);
+ }
+ rt.put_n(rt.memreport()); rt.put_c(10);
+}
+// zc0: skip
diff --git a/tests/conformance/6-ownership/loop_build_list_other_owner.zc b/tests/conformance/6-ownership/loop_build_list_other_owner.zc
new file mode 100644
index 0000000..ff13262
--- /dev/null
+++ b/tests/conformance/6-ownership/loop_build_list_other_owner.zc
@@ -0,0 +1,13 @@
+// expect error: head: still owns an object; overwriting it would leak it
+// spec 6.8.5
+include rt;
+struct node { int value; node* next; };
+fn main() {
+ node* head = rt.new(node);
+ int i = 0;
+ while (i < 3) {
+ node* item = rt.new(node); node* other = rt.new(node); item.next = head; other.value = 1; head = other; i++;
+ }
+ rt.put_n(head.value);
+}
+// zc0: skip
diff --git a/tests/conformance/6-ownership/outer_owner_in_inner_container.zc b/tests/conformance/6-ownership/outer_owner_in_inner_container.zc
new file mode 100644
index 0000000..d9043c0
--- /dev/null
+++ b/tests/conformance/6-ownership/outer_owner_in_inner_container.zc
@@ -0,0 +1,13 @@
+// expect error: head: belongs to item, which was freed at the end of its block (line 10)
+// spec 6.8.5
+include rt;
+struct node { int value; node* next; };
+fn main() {
+ node* head = rt.new(node);
+ {
+ node* item = rt.new(node);
+ item.next = head;
+ }
+ rt.put_n(head.value);
+}
+// zc0: skip