implement: unit identity -- dotted paths, name rules, source path

Units start here, with the part that needs no import graph: what a unit is
called and where it must live.

The parser only ever read a single identifier after `unit` and `import`, so
`unit game.main;` and `import std.io;` were syntax errors -- which is why the
dotted fixtures failed at the semicolon. It now reads a dotted path and stores
it canonically, dots included, since that spelling is the unit's identity
everywhere else. `import a.b as c;` parses too, with the alias on the node.

resolve.c is the new pass between parsing and checking, for the questions that
span files. It carries SPEC 8.1 so far: each path segment is ASCII lowercase,
starts with a letter, continues with letters, digits or underscore, and is at
most eight characters; and the dotted path must match the source path it was
read from, so game.world.map has to come from game/world/map.fe. The source
side is folded to lowercase before comparing, because a case-insensitive host
must not let two spellings become two units.

That rule then applied to the fixtures, which were not obeying it: 57 declared
a unit name unrelated to their file, left over from the milestone directories,
and eight had names too long to be legal. Both are now aligned -- the rule is
worth having only if the tree follows it.

units: badupper, badlong and unitbad pass. 138 -> 146 of 188. The rest of
units/ needs the import graph, which is the next piece: resolution, cycles,
bindings and visibility.
This commit is contained in:
2026-08-17 03:47:28 +09:00
parent 547d8c5ec2
commit 51e2568ba7
68 changed files with 215 additions and 61 deletions
@@ -1,4 +1,4 @@
unit m4_bad_arity;
unit bad_ari;
fn main() -> i32 {
@print("{} {}", 1);
@@ -1,4 +1,4 @@
unit m4_bad_buffer_writer;
unit bad_bufw;
fn main() -> void {
var raw: [4]u8 = [0, 0, 0, 0];
@@ -1,4 +1,4 @@
unit m4_bad_cls;
unit bad_cls;
fn main() -> i32 {
@print("}", 1);
@@ -1,4 +1,4 @@
unit m4_bad_many;
unit bad_many;
fn main() -> i32 {
@print("{}", 1, 2);
@@ -1,4 +1,4 @@
unit m4_bad_open;
unit bad_open;
fn main() -> i32 {
@print("{", 1);
@@ -1,4 +1,4 @@
unit m4_bad_runtime;
unit bad_run;
fn main() -> i32 {
var fmt: str = "{}";
@@ -1,4 +1,4 @@
unit m4_bad_try;
unit bad_try;
fn main() -> i32 {
try @print("nope");
@@ -1,4 +1,4 @@
unit m4_bad_type;
unit bad_type;
struct Point { x: i32, }
@@ -1,4 +1,4 @@
unit m4_bad_verb;
unit bad_verb;
fn main() -> i32 {
@print("{q}", 1);
@@ -1,4 +1,4 @@
unit m4_bad_writer;
unit bad_writ;
fn main() -> i32 {
var x: i32 = 0;
@@ -1,4 +1,4 @@
unit m4_format;
unit ok_forma;
const FMT: str = "n={} hex={x} c={c} s={s} b={b} {{ok}}\n";
@@ -1,4 +1,4 @@
unit m4_prop;
unit ok_prop;
pub fn propagate(w: io.Writer) -> !void {
try @fprint(w, "a{}b", 1);
@@ -1,4 +1,4 @@
unit m4_try_fprint;
unit ok_try_f;
fn main() -> !void {
var raw: [4]u8 = [0, 0, 0, 0];
@@ -1,4 +1,4 @@
unit m5_bad_consuming_close;
unit bad_clos;
struct FileLike {
handle: i32,
@@ -1,4 +1,4 @@
unit m5_bad_conditional;
unit bad_cond;
fn take(p: ^i32) -> void { mem.destroy(p); }
@@ -1,4 +1,4 @@
unit m5_bad_double;
unit bad_dbl;
fn bad(p: ^i32) -> void {
mem.destroy(p);
@@ -1,4 +1,4 @@
unit m5_bad_destroy;
unit bad_dest;
fn bad(x: i32) -> void {
mem.destroy(x);
@@ -1,4 +1,4 @@
unit m5_bad_drop;
unit bad_drop;
struct Box {
value: i32,
@@ -1,4 +1,4 @@
unit m5_bad_loop_move;
unit bad_loop;
fn take(p: ^i32) -> void { mem.destroy(p); }
@@ -1,4 +1,4 @@
unit m5_bad_move;
unit bad_move;
fn take(p: ^i32) -> void { mem.destroy(p); }
@@ -1,4 +1,4 @@
unit m5_bad_projection_move;
unit bad_proj;
struct Holder { p: ^i32 }
fn take(p: ^i32) -> void { mem.destroy(p); }
@@ -1,4 +1,4 @@
unit m5_defer;
unit ok_defer;
pub fn cleanup(p: ^i32) -> void {
defer { mem.destroy(p); }
@@ -1,4 +1,4 @@
unit m5_owned;
unit ok_owned;
fn main() -> !void {
var p: ^i32 = try mem.create(0);
+1 -1
View File
@@ -1,4 +1,4 @@
unit keywords_and_builtins;
unit keybuilt;
pub fn demo() {
let a = true and not false;
+1 -1
View File
@@ -1,3 +1,3 @@
// ERROR:logical operator
unit old_logic;
unit logical;
fn main() { let x = true && false; }
+1 -1
View File
@@ -1,3 +1,3 @@
// ERROR:expected ';'
unit broken;
unit misssemi;
fn main() { let x: i32 = 1 }
+1 -1
View File
@@ -1,3 +1,3 @@
// ERROR:unterminated block comment
unit broken;
unit unclcomm;
/* no ending delimiter
+1 -1
View File
@@ -1,4 +1,4 @@
unit v012_forms;
unit v012form;
shared atomic var ticks: u16 = 0;
packed struct Packet {
@@ -1,4 +1,4 @@
unit bad_arity;
unit bad_ari;
fn add(a: i32, b: i32) -> i32 {
return a + b;
@@ -1,4 +1,4 @@
unit bad_assign;
unit bad_asgn;
fn main() -> i32 {
let value: i32 = 1;
@@ -1,4 +1,4 @@
unit bad_condition;
unit bad_cond;
fn main() -> i32 {
if 1 { return 0; }
@@ -1,4 +1,4 @@
unit m3_bad_mut_let;
unit bad_mlet;
fn bad() -> void {
var raw: [2]u8 = [1, 2];
@@ -1,4 +1,4 @@
unit bad_return;
unit bad_ret;
fn main() -> i32 {
return true;
@@ -1,4 +1,4 @@
unit m3_bad_shared_write;
unit bad_shwr;
fn bad(s: []u8) -> void {
s[0] = 1;
@@ -1,4 +1,4 @@
unit bad_types;
unit bad_type;
fn add(a: i32, b: i32) -> i32 {
return a + b;
@@ -1,4 +1,4 @@
unit bad_uninit;
unit bad_unit;
fn main() -> i32 {
var value: i32;
@@ -1,4 +1,4 @@
unit bad_unknown;
unit bad_unk;
fn main() -> i32 {
return missing_name;
+1 -1
View File
@@ -1,4 +1,4 @@
unit fail_m3_array;
unit badarr;
fn main() -> i32 {
let a: [2]i32 = [1, true, 3];
return a[0];
+1 -1
View File
@@ -1,4 +1,4 @@
unit fail_m3_char;
unit badchar;
fn main() -> i32 {
let u: u8 = 'A';
+1 -1
View File
@@ -1,4 +1,4 @@
unit fail_m3_cycle;
unit badcycle;
struct A { b: B, }
struct B { a: A, }
+1 -1
View File
@@ -1,4 +1,4 @@
unit fail_m3_let_field;
unit badfield;
struct Point { x: i32, y: i32, }
fn main() -> i32 {
+1 -1
View File
@@ -1,4 +1,4 @@
unit fail_m3_fields;
unit badfld;
struct Point { x: i32, y: i32, }
fn main() -> i32 {
let p: Point = Point{ x: 1 };
+1 -1
View File
@@ -1,4 +1,4 @@
unit fail_m3_let_index;
unit badindex;
fn main() -> i32 {
let a: [2]i32 = [1, 2];
+1 -1
View File
@@ -1,4 +1,4 @@
unit fail_m3_match;
unit badmat;
enum Shape { Empty, Circle(i32), }
fn main() -> i32 {
match Shape.Empty { Empty => 0; }
+1 -1
View File
@@ -1,4 +1,4 @@
unit fail_m3_str;
unit badstr;
fn main() -> i32 {
var text: str = "abc";
text[0] = 'z';
@@ -1,4 +1,4 @@
unit m3_arrayctx;
unit ok_arra1;
fn main() -> i32 {
let bytes: [3]u8 = [1, 2, 3];
@@ -1,4 +1,4 @@
unit m3_array;
unit ok_array;
fn main() -> i32 {
let a: [3]i32 = [1, 2, 3];
@@ -1,4 +1,4 @@
unit cast_while;
unit ok_castw;
pub fn main() -> i32 {
var x: i16 = 0;
@@ -1,4 +1,4 @@
unit m3_char;
unit ok_char;
fn main() -> i32 {
let c: char = '\u0041';
@@ -1,4 +1,4 @@
unit m3_enum;
unit ok_enum;
enum Shape { Empty, Circle(i32), Rect { w: i32, h: i32, }, }
@@ -1,4 +1,4 @@
unit m3_for;
unit ok_for;
fn main() -> i32 {
var total: i32 = 0;
@@ -1,4 +1,4 @@
unit hello;
unit ok_hello;
fn add(a: i32, b: i32) -> i32 {
return a + b;
@@ -1,4 +1,4 @@
unit m3_mutable;
unit ok_mutab;
fn takes_shared(s: []u8) -> u8 { return s[0]; }
@@ -1,4 +1,4 @@
unit m3_nested;
unit ok_neste;
struct Outer { inner: Inner, }
struct Inner { value: i32, }
@@ -1,4 +1,4 @@
unit scopes;
unit ok_scope;
fn register(switch: i32) -> i32 {
let auto: i32 = switch;
@@ -1,4 +1,4 @@
unit m3_str;
unit ok_str;
fn main() -> i32 {
let text: str = "abc";
@@ -1,4 +1,4 @@
unit m3_struct;
unit ok_struc;
struct Point { x: i32, y: i32, }
packed struct PackedPoint { x: u8, y: i32, }