refactor: keep the front end, drop everything downstream of it

The milestone structure had stopped describing the compiler and started
shaping it: m7.c, check_m7.c, tests/m2..m9, and a checker and emitter that had
each grown past 2,500 lines because there was nowhere else to put anything.
Restart from the pipeline instead.

What is left is the front end -- lexer, parser, types, ownership, semantic
analysis -- and the fixtures that describe it. The C backend, the DOSBox-X
runner, the milestone registry and the batch build are removed. The driver now
stops after semantic analysis; a code generator attaches where emit_c did.

Fixtures move from milestone directories to what they check:

    parse/     grammar            own/       ownership and borrowing
    types/     type rules         optional/  optionals and error unions
    format/    formatting, try    units/     units and visibility
    generic/   generics           pending-backend/

pending-backend/ holds the three fixtures that can only be checked by running
a program -- that the bounds check traps, that --no-checks removes it, and that
drops and defers actually fire, verified through a fake allocator. Those are
not front-end tests and are not pretending to be; they come back first when
there is a code generator.

tests/run.py replaces the DOSBox-X harness. It builds the front end with the
pinned Watcom's Windows-hosted driver and runs every fixture in about two
seconds, and it does something the old runner structurally could not: it reads
the `// ERROR:line:text` marker each fixture carries and checks the diagnostic
against it. Those markers have been in the tree all along, unverified, because
DOS could not redirect the compiler's stderr and only the exit code was ever
compared.

133/188 pass. The 55 failures are not regressions -- they are what was already
true and invisible:

- units (27) and generic (23): `import`, `comptime` and generic declarations
  parse and are then dropped on the floor. No pass looks at them. The old
  registry did not list these fixtures at all, so nothing said so.
- five in own/, optional/ and generic/: a marker disagrees with the diagnostic
  about the line or the wording. Each is either a wrong marker or a wrong
  diagnostic and has to be read individually.

Everything removed is in git history.
This commit is contained in:
2026-08-17 03:33:23 +09:00
parent 2696dd2abc
commit fb65152901
221 changed files with 149 additions and 5834 deletions
+11
View File
@@ -0,0 +1,11 @@
# M6 fixtures
These fixtures pin the M6 ownership/borrow rules before implementation.
- `ok*.fe` must compile with `--target=bits32 --emit-c`.
- `bad*.fe` must fail compilation; the first line follows the `// ERROR:<line>:<substring>` convention from SPEC §12.
- They are registered in `src/ferrolang_vm/registry.py`, which is what decides whether a milestone runs.
- When M6 starts, wire this directory into the DOS/QEMU gate without changing the expected result of any fixture.
- M6 also owns the general-global borrow restriction from R10 because AGENTS.md explicitly groups that change with the `own.c` state-machine work.
Coverage: R4 storage restrictions, R5 scope, R6 shared/exclusive liveness, root-local field/index conflicts, call-only `&mut -> &` reborrows, branch/loop `MaybeMoved` and initialization-state merging, R7 invalidation, R8 derived-return provenance joins, defer lifetime extension, and global borrow restrictions.
+10
View File
@@ -0,0 +1,10 @@
// ERROR:8:self
unit badarg;
struct Box {
value: i32,
fn bad(self: &Self, other: &i32) -> &i32 {
return other;
}
}
+10
View File
@@ -0,0 +1,10 @@
// ERROR:9:initialized
unit badbinit;
fn bad(assign: bool) -> i32 {
var value: i32;
if assign {
value = 1;
}
return value;
}
+9
View File
@@ -0,0 +1,9 @@
// ERROR:9:move
unit badbrmov;
fn bad(p: ^i32, consume: bool) -> void {
if consume {
mem.destroy(p);
}
mem.destroy(p);
}
+12
View File
@@ -0,0 +1,12 @@
// ERROR:10:borrow
unit baddefer;
fn read(r: &i32) -> i32 { return r.^; }
fn bad() -> i32 {
var x: i32 = 0;
let r = &x;
defer { let y = read(r); }
x = 1;
return x;
}
+6
View File
@@ -0,0 +1,6 @@
// ERROR:5:reference
unit badfld;
struct Bad {
value: &i32,
}
+9
View File
@@ -0,0 +1,9 @@
// ERROR:7:global
unit badglob;
var VALUE: i32 = 0;
fn bad() -> i32 {
let r = &VALUE;
return r.^;
}
+9
View File
@@ -0,0 +1,9 @@
// ERROR:7:global
unit badgmut;
var VALUE: i32 = 0;
fn bad() -> void {
let r = &mut VALUE;
r.^ = 1;
}
+9
View File
@@ -0,0 +1,9 @@
// ERROR:6:borrow
unit badinv;
fn bad(p: ^i32, q: ^i32) -> void {
let r = &p;
p = q;
let keep = r;
mem.destroy(p);
}
+7
View File
@@ -0,0 +1,7 @@
// ERROR:6:reference
unit badlocsl;
fn bad() -> []u8 {
let a: [2]u8 = [1 as u8, 2 as u8];
return a[..];
}
+9
View File
@@ -0,0 +1,9 @@
// ERROR:8:move
unit badloop;
fn bad(p: ^i32, again: bool) -> void {
while again {
mem.destroy(p);
}
mem.destroy(p);
}
+10
View File
@@ -0,0 +1,10 @@
// ERROR:8:borrow
unit badmove;
fn take(p: ^i32) -> void { mem.destroy(p); }
fn bad(p: ^i32) -> void {
let r = &p;
take(p);
let q = r;
}
+10
View File
@@ -0,0 +1,10 @@
// ERROR:7:borrow
unit badmut;
fn bad() -> i32 {
var x: i32 = 0;
let r = &mut x;
x = 1;
r.^ = 2;
return x;
}
+11
View File
@@ -0,0 +1,11 @@
// ERROR:7:borrow
unit badmut2;
fn bad() -> i32 {
var x: i32 = 0;
let a = &mut x;
let b = &mut x;
a.^ = 1;
b.^ = 2;
return x;
}
+6
View File
@@ -0,0 +1,6 @@
// ERROR:4:reference
unit badptr;
fn bad(p: *&i32) -> void {
return;
}
+7
View File
@@ -0,0 +1,7 @@
// ERROR:6:reference
unit badret;
fn bad() -> &i32 {
let x: i32 = 1;
return &x;
}
+11
View File
@@ -0,0 +1,11 @@
// ERROR:9:borrow
unit badrfld;
struct Pair { a: i32, b: i32, }
fn bad() -> void {
var p = Pair{ a: 1, b: 2 };
let left = &mut p.a;
p.b = 3;
left.^ = 4;
}
+10
View File
@@ -0,0 +1,10 @@
// ERROR:7:borrow
unit badridx;
fn bad() -> void {
var xs: [2]i32 = [1, 2];
let a = &mut xs[0];
let b = &mut xs[1];
a.^ = 3;
b.^ = 4;
}
+12
View File
@@ -0,0 +1,12 @@
// ERROR:9:reference
unit badscop;
fn bad(cond: bool) -> i32 {
let outer: i32 = 0;
var r: &i32 = &outer;
if cond {
let inner: i32 = 3;
r = &inner;
}
return r.^;
}
+10
View File
@@ -0,0 +1,10 @@
// ERROR:8:self
unit badself;
struct Box {
value: i32,
fn bad(self: Self) -> &i32 {
return &self.value;
}
}
+11
View File
@@ -0,0 +1,11 @@
// ERROR:9:borrow
unit badshwr;
fn read(r: &i32) -> i32 { return r.^; }
fn bad() -> i32 {
var x: i32 = 0;
let r = &x;
x = 1;
return read(r);
}
+6
View File
@@ -0,0 +1,6 @@
// ERROR:5:reference
unit badslfld;
struct Bad {
bytes: []u8,
}
+6
View File
@@ -0,0 +1,6 @@
// ERROR:5:reference
unit badtwo;
fn choose(a: &i32, b: &i32) -> &i32 {
return a;
}
+10
View File
@@ -0,0 +1,10 @@
// ERROR:8:mut
unit badup;
struct Box {
value: i32,
fn bad(self: &Self) -> &mut i32 {
return &mut self.value;
}
}
+9
View File
@@ -0,0 +1,9 @@
// ERROR:7:mut
unit badweak;
fn bad() -> void {
var x: i32 = 0;
let m = &mut x;
let s: &i32 = m;
let v = s.^;
}
+5
View File
@@ -0,0 +1,5 @@
unit m5_defer;
pub fn cleanup(p: ^i32) -> void {
defer { mem.destroy(p); }
}
+9
View File
@@ -0,0 +1,9 @@
unit m5_owned;
fn main() -> !void {
var p: ^i32 = try mem.create(0);
p = try mem.create(0);
p.^ = 7;
let value: i32 = p.^;
defer { mem.destroy(p); }
}
+15
View File
@@ -0,0 +1,15 @@
unit okbranch;
fn read(r: &i32) -> i32 { return r.^; }
fn test(cond: bool) -> i32 {
var x: i32 = 3;
let r = &x;
if cond {
let a = read(r);
} else {
let b = read(r);
}
x += 1;
return x;
}
+13
View File
@@ -0,0 +1,13 @@
unit okdefer;
fn read(r: &i32) -> i32 { return r.^; }
fn test() -> i32 {
var x: i32 = 3;
if true {
let r = &x;
defer { let y = read(r); }
}
x += 1;
return x;
}
+10
View File
@@ -0,0 +1,10 @@
unit okglobcp;
var VALUE: i32 = 7;
fn read(r: &i32) -> i32 { return r.^; }
fn test() -> i32 {
let local = VALUE;
return read(&local);
}
+9
View File
@@ -0,0 +1,9 @@
unit oklast;
fn test() -> i32 {
var x: i32 = 0;
let r = &mut x;
r.^ = 1;
x += 1;
return x;
}
+13
View File
@@ -0,0 +1,13 @@
unit okr8free;
fn head(s: []u8) -> &u8 {
return &s[0];
}
fn test() -> u8 {
var a: [2]u8 = [1 as u8, 2 as u8];
let r = head(a[..]);
let v = r.^;
a[0] = 7 as u8;
return v;
}
+14
View File
@@ -0,0 +1,14 @@
unit okr8join;
fn select(s: str, from_param: bool) -> str {
if from_param { return s; }
return "static";
}
fn test() -> u8 {
var bytes: [1]u8 = [7 as u8];
let view = select(bytes[..], true);
let value = view[0];
bytes[0] = 8 as u8;
return value;
}
+17
View File
@@ -0,0 +1,17 @@
unit okr8meth;
struct Box {
value: i32,
pub fn get(self: &Self) -> &i32 {
return &self.value;
}
}
fn test() -> i32 {
var b = Box{ value: 4 };
let r = b.get();
let v = r.^;
b.value = 5;
return v;
}
+5
View File
@@ -0,0 +1,5 @@
unit okr8stat;
fn name() -> str {
return "main";
}
+11
View File
@@ -0,0 +1,11 @@
unit okrebor;
fn read(r: &i32) -> i32 { return r.^; }
fn test() -> i32 {
var x: i32 = 1;
let r = &mut x;
let v = read(r);
r.^ = v + 1;
return r.^;
}
+11
View File
@@ -0,0 +1,11 @@
unit okrtlast;
struct Pair { a: i32, b: i32, }
fn test() -> i32 {
var p = Pair{ a: 1, b: 2 };
let left = &mut p.a;
left.^ = 3;
p.b = 4;
return p.a + p.b;
}
+12
View File
@@ -0,0 +1,12 @@
unit okshare;
fn add(a: &i32, b: &i32) -> i32 {
return a.^ + b.^;
}
fn test() -> i32 {
let x: i32 = 4;
let a = &x;
let b = &x;
return add(a, b);
}
+11
View File
@@ -0,0 +1,11 @@
unit okslreb;
fn first(s: []u8) -> u8 { return s[0]; }
fn test() -> u8 {
var a: [2]u8 = [1 as u8, 2 as u8];
var s = a[..];
let v = first(s);
s[0] = 9 as u8;
return v;
}
+11
View File
@@ -0,0 +1,11 @@
unit okstatic;
static VALUE: i32 = 7;
fn get() -> &i32 {
return &VALUE;
}
fn test() -> i32 {
return get().^;
}
+10
View File
@@ -0,0 +1,10 @@
unit oktemp;
fn read(r: &i32) -> i32 { return r.^; }
fn test() -> i32 {
var x: i32 = 1;
let v = read(&x);
x += 1;
return v + x;
}
+5
View File
@@ -0,0 +1,5 @@
unit oktrim;
fn trimmed(line: str) -> str {
return line.trim();
}
+11
View File
@@ -0,0 +1,11 @@
unit okwcall;
fn read(r: &i32) -> i32 { return r.^; }
fn test() -> i32 {
var x: i32 = 5;
let m = &mut x;
let value = read(m);
m.^ = value + 1;
return m.^;
}
+13
View File
@@ -0,0 +1,13 @@
unit m5_bad_consuming_close;
struct FileLike {
handle: i32,
fn close(self: Self) -> !void { self.handle = 0; }
fn drop(self: &mut Self) { self.handle = 0; }
}
fn bad() -> !void {
let file: FileLike = FileLike{ handle: 7 };
try file.close();
file.close();
}
+8
View File
@@ -0,0 +1,8 @@
unit m5_bad_conditional;
fn take(p: ^i32) -> void { mem.destroy(p); }
fn bad(p: ^i32, flag: bool) -> void {
if flag { take(p); }
p.^ = 3;
}
+6
View File
@@ -0,0 +1,6 @@
unit m5_bad_double;
fn bad(p: ^i32) -> void {
mem.destroy(p);
mem.destroy(p);
}
+5
View File
@@ -0,0 +1,5 @@
unit m5_bad_destroy;
fn bad(x: i32) -> void {
mem.destroy(x);
}
+11
View File
@@ -0,0 +1,11 @@
unit m5_bad_drop;
struct Box {
value: i32,
fn drop(self: &mut Self) { self.value = 0; }
}
fn bad() -> void {
var b: Box = Box{ value: 1 };
b.drop();
}
+7
View File
@@ -0,0 +1,7 @@
unit m5_bad_loop_move;
fn take(p: ^i32) -> void { mem.destroy(p); }
fn bad(p: ^i32, again: bool) -> void {
while again { take(p); }
}
+8
View File
@@ -0,0 +1,8 @@
unit m5_bad_move;
fn take(p: ^i32) -> void { mem.destroy(p); }
fn twice(p: ^i32) -> void {
take(p);
take(p);
}
+8
View File
@@ -0,0 +1,8 @@
unit m5_bad_projection_move;
struct Holder { p: ^i32 }
fn take(p: ^i32) -> void { mem.destroy(p); }
fn bad(h: Holder) -> void {
take(h.p);
}