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
+8
View File
@@ -0,0 +1,8 @@
# M7 fixtures
M7 adds optionals and error unions on top of the M6 ownership model.
`ok*.fe` must compile. `bad*.fe` must fail according to the first-line error marker.
The files are registered in `src/ferrolang_vm/registry.py`.
Coverage: contextual `null`, `?T`, `.?`, `Some`/`None` pattern-only non-destructive views, `mem.replace` extraction, lazy `orelse`/`catch`, nominal error unions, `try`, block/short `catch`, and error code/name uniqueness, plus R4/R7 interactions with optional references/owners.
+17
View File
@@ -0,0 +1,17 @@
// ERROR:14:catch
unit badcatch;
error E {
Bad = 1,
}
fn leaf() -> E!i32 {
return E.Bad;
}
fn bad() -> i32 {
let v = leaf() catch |e| {
let ignored: i32 = 1;
};
return v;
}
+14
View File
@@ -0,0 +1,14 @@
// ERROR:13:type
unit baddef;
error E {
Bad = 1,
}
fn leaf() -> E!i32 {
return E.Bad;
}
fn bad() -> i32 {
return leaf() catch false;
}
+10
View File
@@ -0,0 +1,10 @@
// ERROR:9:optional
unit baddir;
struct Node {
value: i32,
}
fn bad(p: ?^Node) -> i32 {
return p.^.value;
}
+7
View File
@@ -0,0 +1,7 @@
// ERROR:6:duplicate
unit badercod;
error E {
One = 1,
Two = 1,
}
+7
View File
@@ -0,0 +1,7 @@
// ERROR:6:duplicate
unit badernam;
error E {
One = 1,
One = 2,
}
+18
View File
@@ -0,0 +1,18 @@
// ERROR:17:error
unit badetype;
error A {
Bad = 1,
}
error B {
Bad = 1,
}
fn leaf() -> A!i32 {
return A.Bad;
}
fn bad() -> B!i32 {
return try leaf();
}
+6
View File
@@ -0,0 +1,6 @@
// ERROR:5:null
unit badnull;
fn bad() -> void {
let p = null;
}
+6
View File
@@ -0,0 +1,6 @@
// ERROR:5:reference
unit badoref;
struct Bad {
value: ?&i32,
}
+10
View File
@@ -0,0 +1,10 @@
// ERROR:9:non-Copy
unit badorel;
struct Node {
next: ?^Node,
}
fn bad(p: ^Node, fallback: ^Node) -> ^Node {
return p.next orelse fallback;
}
+11
View File
@@ -0,0 +1,11 @@
// ERROR:9:mem.replace
unit badproj;
struct Node {
next: ?^Node,
}
fn bad(p: ^Node) -> void {
let q = p.next;
mem.destroy(p);
}
+7
View File
@@ -0,0 +1,7 @@
// ERROR:6:optional
unit badqmark;
fn bad() -> i32 {
let x: i32 = 1;
return x.?;
}
+14
View File
@@ -0,0 +1,14 @@
// ERROR:13:error
unit badret;
error A {
Bad = 1,
}
error B {
Bad = 1,
}
fn bad() -> B!i32 {
return A.Bad;
}
+7
View File
@@ -0,0 +1,7 @@
// ERROR:5:Some
unit badsome;
fn bad() -> i32 {
let x = Some(1);
return x;
}
+14
View File
@@ -0,0 +1,14 @@
// ERROR:13:try
unit badtry;
error E {
Bad = 1,
}
fn leaf() -> E!i32 {
return E.Bad;
}
fn bad() -> i32 {
return try leaf();
}
+6
View File
@@ -0,0 +1,6 @@
// ERROR:5:0
unit badzero;
error E {
Zero = 0,
}
+16
View File
@@ -0,0 +1,16 @@
unit okcatch;
error E {
Bad = 1,
}
fn leaf() -> E!i32 {
return E.Bad;
}
fn top() -> E!i32 {
let v = leaf() catch |e| {
return e;
};
return v;
}
+8
View File
@@ -0,0 +1,8 @@
unit okcatmov;
error E { Bad = 1, }
struct Node { value: i32, }
fn recover(result: E!^Node, fallback: ^Node) -> ^Node {
return result catch fallback;
}
+15
View File
@@ -0,0 +1,15 @@
unit okcvoid;
error E {
Bad = 1,
}
fn leaf() -> E!void {
return E.Bad;
}
fn top() -> void {
leaf() catch |e| {
return;
};
}
+13
View File
@@ -0,0 +1,13 @@
unit okdeflt;
error E {
Bad = 1,
}
fn leaf() -> E!i32 {
return E.Bad;
}
fn top() -> i32 {
return leaf() catch 11;
}
+8
View File
@@ -0,0 +1,8 @@
unit okiflet;
fn value(p: ?i32) -> i32 {
if let Some(v) = p {
return v;
}
return 0;
}
+8
View File
@@ -0,0 +1,8 @@
unit okmatch;
fn value(p: ?i32) -> i32 {
match p {
Some(v) => { return v; }
None => { return 0; }
}
}
+10
View File
@@ -0,0 +1,10 @@
unit oknull;
struct Node { value: i32, }
fn accepts(p: ?^Node) -> bool { return p == null; }
fn test() -> bool {
let p: ?^Node = null;
return accepts(p);
}
+5
View File
@@ -0,0 +1,5 @@
unit okorelse;
fn value(p: ?i32) -> i32 {
return p orelse 9;
}
+12
View File
@@ -0,0 +1,12 @@
unit okpatvw;
struct Node { value: i32, }
fn keep(p: ?^Node) -> void {
var slot = p;
if let Some(node) = slot {
let value = node.value;
}
let owned = mem.replace(&mut slot, null).?;
mem.destroy(owned);
}
+9
View File
@@ -0,0 +1,9 @@
unit okproj;
struct Node {
value: i32,
}
fn touch(p: ?&mut Node) -> void {
p.?.value = 7;
}
+11
View File
@@ -0,0 +1,11 @@
unit okrepl;
struct Node {
value: i32,
}
fn take(p: ?^Node) -> void {
var q: ?^Node = p;
let n = mem.replace(&mut q, null).?;
mem.destroy(n);
}
+14
View File
@@ -0,0 +1,14 @@
unit oktrdef;
error E {
Bad = 1,
}
fn leaf() -> E!i32 {
return E.Bad;
}
fn top() -> E!i32 {
defer { let x: i32 = 1; }
return try leaf();
}
+14
View File
@@ -0,0 +1,14 @@
unit oktry;
error E {
Bad = 1,
}
fn leaf(ok: bool) -> E!i32 {
if ok { return 7; }
return E.Bad;
}
fn top() -> E!i32 {
return try leaf(true);
}