add M6-M9 specification tests (#3)

This commit is contained in:
정시원
2026-08-16 19:08:48 +09:00
committed by GitHub
parent 9986da6f12
commit f2e17d265f
97 changed files with 940 additions and 0 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 intentionally not wired into `TEST-DOS.BAT` while `master` is M5, so adding these fixtures does not make the current milestone red.
- 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 and reborrows, R7 invalidation, R8 derived returns, 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;
}
}
+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[..];
}
+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;
}
+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;
}
}
+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;
}
+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.^;
}
+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();
}
+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 not wired into `TEST-DOS.BAT` until M7 work begins.
Coverage: `?T`, `.?`, `Some`/`None` pattern-only destructuring, `mem.replace` extraction, `orelse`, nominal error unions, `try`, block/short `catch`, error code zero rejection, and 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;
}
+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: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;
}
+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; }
}
}
+5
View File
@@ -0,0 +1,5 @@
unit okorelse;
fn value(p: ?i32) -> i32 {
return p orelse 9;
}
+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);
}
+19
View File
@@ -0,0 +1,19 @@
# M8 fixtures
M8 is the first multi-unit milestone, so cases live in subdirectories. Each case is compiled separately with that directory on the import path.
Pass cases:
- `basic`: public function across units.
- `pubfld`: public type and public field across units.
- `errsame`: two units use the same anonymous `error.Name`; the driver must assign one deterministic `core.Error` code.
- `errdet`: the same anonymous error-name set appears in a different source/import order; generated `fe_errors.h` must be byte-identical to `errsame` modulo the intentionally different unit graph.
Fail cases:
- `privfn`: private declaration access.
- `privfld`: private field access.
- `missing`: unresolved import.
- `cycle`: cyclic imports.
- `unitbad`: filename/unit-name mismatch.
- `errnom`: nominal error cannot flow into `core.Error` via `try`.
The current M5 `TEST-DOS.BAT` is intentionally unchanged. M8 should add procedural checks for `.fei` creation/hash invalidation and deterministic `fe_errors.h` using these fixtures.
+6
View File
@@ -0,0 +1,6 @@
unit main;
import util;
fn main() -> i32 {
return util.answer();
}
+5
View File
@@ -0,0 +1,5 @@
unit util;
pub fn answer() -> i32 {
return 42;
}
+4
View File
@@ -0,0 +1,4 @@
unit a;
import b;
pub fn a_value() -> i32 { return b.b_value(); }
+5
View File
@@ -0,0 +1,5 @@
// ERROR:3:cycle
unit b;
import a;
pub fn b_value() -> i32 { return 1; }
+5
View File
@@ -0,0 +1,5 @@
unit alpha;
pub fn fail() -> !void {
return error.Busy;
}
+5
View File
@@ -0,0 +1,5 @@
unit beta;
pub fn fail() -> !void {
return error.Busy;
}
+6
View File
@@ -0,0 +1,6 @@
unit main;
import beta;
import alpha;
fn one() -> !void { return beta.fail(); }
fn two() -> !void { return alpha.fail(); }
+9
View File
@@ -0,0 +1,9 @@
unit lib;
pub error LibError {
Bad = 1,
}
pub fn value() -> LibError!i32 {
return LibError.Bad;
}
+7
View File
@@ -0,0 +1,7 @@
// ERROR:6:error
unit main;
import lib;
fn bad() -> !i32 {
return try lib.value();
}
+5
View File
@@ -0,0 +1,5 @@
unit alpha;
pub fn fail() -> !void {
return error.Busy;
}
+5
View File
@@ -0,0 +1,5 @@
unit beta;
pub fn fail() -> !void {
return error.Busy;
}
+6
View File
@@ -0,0 +1,6 @@
unit main;
import alpha;
import beta;
fn one() -> !void { return alpha.fail(); }
fn two() -> !void { return beta.fail(); }
+5
View File
@@ -0,0 +1,5 @@
// ERROR:3:import
unit main;
import absent;
fn main() -> void {}
+5
View File
@@ -0,0 +1,5 @@
unit data;
pub struct Record {
value: i32,
}
+8
View File
@@ -0,0 +1,8 @@
// ERROR:6:private
unit main;
import data;
fn main() -> i32 {
let r = data.Record{ value: 7 };
return r.value;
}
+7
View File
@@ -0,0 +1,7 @@
// ERROR:6:private
unit main;
import util;
fn main() -> i32 {
return util.hidden();
}
+5
View File
@@ -0,0 +1,5 @@
unit util;
fn hidden() -> i32 {
return 1;
}
+5
View File
@@ -0,0 +1,5 @@
unit data;
pub struct Record {
pub value: i32,
}
+7
View File
@@ -0,0 +1,7 @@
unit main;
import data;
fn main() -> i32 {
let r = data.Record{ value: 7 };
return r.value;
}
+4
View File
@@ -0,0 +1,4 @@
// ERROR:2:unit
unit other;
fn main() -> void {}
+19
View File
@@ -0,0 +1,19 @@
# M9 fixtures
M9 adds comptime type parameters and monomorphization.
`ok*.fe` must compile; `bad*.fe` must fail according to the first-line marker.
`defscope/` is a multi-unit definition-scope test and requires M8 imports.
Coverage:
- generic functions and structs,
- multiple and duplicate instantiations,
- type aliases as comptime type values,
- `T == U` and `@is_int(T)` in comptime,
- instantiation-time operation errors,
- arity/type-argument errors,
- runtime `type` values forbidden,
- definition-unit name lookup,
- recursive instantiation depth limit.
M9's DOS gate should additionally inspect generated `fe_generics.c`: `okdedup.fe` must emit one body for the repeated `(id, i32)` instantiation.
+10
View File
@@ -0,0 +1,10 @@
// ERROR:9:comptime
unit badarg;
fn id(comptime T: type, v: T) -> T {
return v;
}
fn bad() -> i32 {
return id(7, 8);
}
+10
View File
@@ -0,0 +1,10 @@
// ERROR:9:argument
unit badarity;
struct Box(T) {
value: T,
}
fn bad() -> void {
var x: Box(i32, u8);
}
+10
View File
@@ -0,0 +1,10 @@
// ERROR:9:instantiation
unit badbody;
fn add(comptime T: type, a: T, b: T) -> T {
return a + b;
}
fn bad() -> bool {
return add(bool, true, false);
}
+10
View File
@@ -0,0 +1,10 @@
// ERROR:9:depth
unit baddepth;
struct Box(T) {
value: T,
}
fn bad() -> void {
var x: Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(Box(i32)))))))))))))))))))))))))))))))));
}
+10
View File
@@ -0,0 +1,10 @@
// ERROR:9:generic
unit badfew;
struct Box(T) {
value: T,
}
fn bad() -> void {
var x: Box;
}
+17
View File
@@ -0,0 +1,17 @@
// ERROR:16:instantiation
unit badop;
struct Token {
value: i32,
}
fn max(comptime T: type, a: T, b: T) -> T {
if a > b { return a; }
return b;
}
fn bad() -> Token {
let a = Token{ value: 1 };
let b = Token{ value: 2 };
return max(Token, a, b);
}
+6
View File
@@ -0,0 +1,6 @@
// ERROR:5:helper
unit lib;
pub fn call(comptime T: type, v: T) -> T {
return helper(v);
}
+8
View File
@@ -0,0 +1,8 @@
unit main;
import lib;
fn helper(v: i32) -> i32 { return v + 10; }
fn main() -> i32 {
return lib.call(i32, 1);
}
+6
View File
@@ -0,0 +1,6 @@
// ERROR:5:type
unit badtype;
fn bad() -> void {
let t = i32;
}
+13
View File
@@ -0,0 +1,13 @@
unit lib;
fn bump_i32(v: i32) -> i32 {
return v + 1;
}
pub fn bump(comptime T: type, v: T) -> T {
comptime if T == i32 {
return bump_i32(v);
} else {
return v;
}
}
+6
View File
@@ -0,0 +1,6 @@
unit main;
import lib;
fn main() -> i32 {
return lib.bump(i32, 4);
}
+11
View File
@@ -0,0 +1,11 @@
unit okalias;
const Word = i32;
fn id(comptime T: type, v: T) -> T {
return v;
}
fn test() -> Word {
return id(Word, 12);
}
+18
View File
@@ -0,0 +1,18 @@
unit okbox;
struct Box(T) {
value: T,
pub fn new(v: T) -> Self {
return Self{ value: v };
}
pub fn get(self: &Self) -> &T {
return &self.value;
}
}
fn test() -> i32 {
var b: Box(i32) = Box(i32).new(7);
return b.get().^;
}
+8
View File
@@ -0,0 +1,8 @@
unit okdedup;
fn id(comptime T: type, v: T) -> T {
return v;
}
fn a() -> i32 { return id(i32, 1); }
fn b() -> i32 { return id(i32, 2); }
+9
View File
@@ -0,0 +1,9 @@
unit okid;
fn id(comptime T: type, v: T) -> T {
return v;
}
fn test() -> i32 {
return id(i32, 7);
}
+13
View File
@@ -0,0 +1,13 @@
unit okisint;
fn is_integer(comptime T: type) -> bool {
comptime if @is_int(T) {
return true;
} else {
return false;
}
}
fn test() -> bool {
return is_integer(u16);
}
+11
View File
@@ -0,0 +1,11 @@
unit okmulti;
fn id(comptime T: type, v: T) -> T {
return v;
}
fn test() -> i32 {
let a: i32 = id(i32, 7);
let b: u8 = id(u8, 9 as u8);
return a + (b as i32);
}
+15
View File
@@ -0,0 +1,15 @@
unit oknested;
struct Box(T) {
value: T,
pub fn new(v: T) -> Self {
return Self{ value: v };
}
}
fn test() -> i32 {
let inner: Box(i32) = Box(i32).new(3);
let outer: Box(Box(i32)) = Box(Box(i32)).new(inner);
return outer.value.value;
}
+15
View File
@@ -0,0 +1,15 @@
unit okpair;
struct Pair(A, B) {
first: A,
second: B,
pub fn new(a: A, b: B) -> Self {
return Self{ first: a, second: b };
}
}
fn test() -> i32 {
let p: Pair(i32, u8) = Pair(i32, u8).new(4, 5 as u8);
return p.first + (p.second as i32);
}
+13
View File
@@ -0,0 +1,13 @@
unit oktypeeq;
fn kind(comptime T: type) -> i32 {
comptime if T == i32 {
return 1;
} else {
return 0;
}
}
fn test() -> i32 {
return kind(i32);
}