lang: rename signed integer types sN -> iN

Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.

Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).

Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.

zig build test: 426/426; examples suite: 595/595.
This commit is contained in:
agra
2026-06-12 09:31:53 +03:00
parent 515ecebea7
commit d8076b9333
1054 changed files with 6836 additions and 6839 deletions

View File

@@ -18,13 +18,13 @@ test "lower: simple function with arithmetic" {
var module = ir_mod.Module.init(alloc);
defer module.deinit();
// Build a minimal AST: add :: (a: s64, b: s64) -> s64 { return a + b; }
// Build a minimal AST: add :: (a: i64, b: i64) -> i64 { return a + b; }
const a_type = alloc.create(Node) catch unreachable;
a_type.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "s64", .is_generic = false } } };
a_type.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "i64", .is_generic = false } } };
const b_type = alloc.create(Node) catch unreachable;
b_type.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "s64", .is_generic = false } } };
b_type.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "i64", .is_generic = false } } };
const ret_type = alloc.create(Node) catch unreachable;
ret_type.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "s64", .is_generic = false } } };
ret_type.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "i64", .is_generic = false } } };
const a_ident = alloc.create(Node) catch unreachable;
a_ident.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .identifier = .{ .name = "a" } } };
@@ -73,7 +73,7 @@ test "lower: simple function with arithmetic" {
try std.testing.expectEqual(@as(usize, 1), module.functions.items.len);
const func = module.getFunction(FuncId.fromIndex(0));
try std.testing.expectEqual(@as(usize, 2), func.params.len);
try std.testing.expectEqual(TypeId.s64, func.ret);
try std.testing.expectEqual(TypeId.i64, func.ret);
try std.testing.expect(func.blocks.items.len > 0);
// Print the IR to verify it looks reasonable
@@ -94,15 +94,15 @@ test "lower: instructions carry their AST node's source span (ERR E3.0)" {
var module = ir_mod.Module.init(alloc);
defer module.deinit();
// probe :: (a: s64, b: s64) -> s64 { return a + b; } — the `a + b` node
// probe :: (a: i64, b: i64) -> i64 { return a + b; } — the `a + b` node
// gets a distinctive span so we can find the emitted `add` instruction and
// assert it was stamped (not left at the empty {0,0} default).
const a_type = alloc.create(Node) catch unreachable;
a_type.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "s64", .is_generic = false } } };
a_type.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "i64", .is_generic = false } } };
const b_type = alloc.create(Node) catch unreachable;
b_type.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "s64", .is_generic = false } } };
b_type.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "i64", .is_generic = false } } };
const ret_type = alloc.create(Node) catch unreachable;
ret_type.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "s64", .is_generic = false } } };
ret_type.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "i64", .is_generic = false } } };
const a_ident = alloc.create(Node) catch unreachable;
a_ident.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .identifier = .{ .name = "a" } } };
const b_ident = alloc.create(Node) catch unreachable;
@@ -154,7 +154,7 @@ test "lower: if/else generates basic blocks" {
var module = ir_mod.Module.init(alloc);
defer module.deinit();
// Build AST: test :: (c: bool) -> s64 { if c { return 1; } else { return 2; } }
// Build AST: test :: (c: bool) -> i64 { if c { return 1; } else { return 2; } }
// The condition must be a runtime value (a param) — a constant `if true`
// is folded by lowering to a single block, defeating the branch test.
const cond_node = alloc.create(Node) catch unreachable;
@@ -204,7 +204,7 @@ test "lower: if/else generates basic blocks" {
const ret_type = alloc.create(Node) catch unreachable;
defer alloc.destroy(ret_type);
ret_type.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "s64", .is_generic = false } } };
ret_type.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "i64", .is_generic = false } } };
const fn_decl = ast.FnDecl{
.name = "test_if",
@@ -303,18 +303,18 @@ test "lower: objcTypeEncodingFromSignature emits primitive shapes" {
defer alloc.free(e1);
try std.testing.expectEqualStrings("v@:", e1);
// Returns s32, takes s32: -(int)add:(int)x → "i@:i"
const e2 = try lowering.objc().objcTypeEncodingFromSignature(.s32, &.{.s32}, null);
// Returns i32, takes i32: -(int)add:(int)x → "i@:i"
const e2 = try lowering.objc().objcTypeEncodingFromSignature(.i32, &.{.i32}, null);
defer alloc.free(e2);
try std.testing.expectEqualStrings("i@:i", e2);
// s64 return, two s64 args: "q@:qq"
const e3 = try lowering.objc().objcTypeEncodingFromSignature(.s64, &.{ .s64, .s64 }, null);
// i64 return, two i64 args: "q@:qq"
const e3 = try lowering.objc().objcTypeEncodingFromSignature(.i64, &.{ .i64, .i64 }, null);
defer alloc.free(e3);
try std.testing.expectEqualStrings("q@:qq", e3);
// BOOL return (s8): "c@:"
const e4 = try lowering.objc().objcTypeEncodingFromSignature(.s8, &.{}, null);
// BOOL return (i8): "c@:"
const e4 = try lowering.objc().objcTypeEncodingFromSignature(.i8, &.{}, null);
defer alloc.free(e4);
try std.testing.expectEqualStrings("c@:", e4);
@@ -357,9 +357,9 @@ test "lower: objcTypeEncodingFromSignature emits pointer shapes" {
defer alloc.free(e2);
try std.testing.expectEqualStrings("v@:*", e2);
// `[*]s32` (non-u8 many-pointer) → `^v`.
const s32_many = module.types.intern(.{ .many_pointer = .{ .element = .s32 } });
const e3 = try lowering.objc().objcTypeEncodingFromSignature(.void, &.{s32_many}, null);
// `[*]i32` (non-u8 many-pointer) → `^v`.
const i32_many = module.types.intern(.{ .many_pointer = .{ .element = .i32 } });
const e3 = try lowering.objc().objcTypeEncodingFromSignature(.void, &.{i32_many}, null);
defer alloc.free(e3);
try std.testing.expectEqualStrings("v@:^v", e3);
}
@@ -371,14 +371,14 @@ test "lower: objcDefinedStateStructType collects user-declared fields" {
defer module.deinit();
var lowering = Lowering.init(&module);
// Synthesize a #objc_class("SxFoo") { counter: s32; ticks: s64; } AST.
// Synthesize a #objc_class("SxFoo") { counter: i32; ticks: i64; } AST.
const span = ast.Span{ .start = 0, .end = 0 };
const counter_type = try alloc.create(Node);
defer alloc.destroy(counter_type);
counter_type.* = .{ .span = span, .data = .{ .type_expr = .{ .name = "s32", .is_generic = false } } };
counter_type.* = .{ .span = span, .data = .{ .type_expr = .{ .name = "i32", .is_generic = false } } };
const ticks_type = try alloc.create(Node);
defer alloc.destroy(ticks_type);
ticks_type.* = .{ .span = span, .data = .{ .type_expr = .{ .name = "s64", .is_generic = false } } };
ticks_type.* = .{ .span = span, .data = .{ .type_expr = .{ .name = "i64", .is_generic = false } } };
const members = [_]ast.ForeignClassMember{
.{ .field = .{ .name = "counter", .field_type = counter_type } },
@@ -401,9 +401,9 @@ test "lower: objcDefinedStateStructType collects user-declared fields" {
try std.testing.expectEqualStrings("__SxFooState", module.types.getString(s.name));
try std.testing.expectEqual(@as(usize, 2), s.fields.len);
try std.testing.expectEqualStrings("counter", module.types.getString(s.fields[0].name));
try std.testing.expectEqual(TypeId.s32, s.fields[0].ty);
try std.testing.expectEqual(TypeId.i32, s.fields[0].ty);
try std.testing.expectEqualStrings("ticks", module.types.getString(s.fields[1].name));
try std.testing.expectEqual(TypeId.s64, s.fields[1].ty);
try std.testing.expectEqual(TypeId.i64, s.fields[1].ty);
// Idempotency: a second call returns the same TypeId (cache hit on name).
const state_ty2 = lowering.objc().objcDefinedStateStructType(&fcd);
@@ -441,7 +441,7 @@ test "lower: objcDefinedStateStructType skips non-field members" {
const span = ast.Span{ .start = 0, .end = 0 };
const counter_type = try alloc.create(Node);
defer alloc.destroy(counter_type);
counter_type.* = .{ .span = span, .data = .{ .type_expr = .{ .name = "s32", .is_generic = false } } };
counter_type.* = .{ .span = span, .data = .{ .type_expr = .{ .name = "i32", .is_generic = false } } };
const members = [_]ast.ForeignClassMember{
.{ .extends = "NSObject" },
@@ -519,10 +519,10 @@ test "lower: objcTypeEncodingFromSignature unwraps optional to wire type" {
};
try lowering.program_index.foreign_class_map.put("NSString", &ns_fcd);
// `?s64 -> ?*NSString` collapses to `q -> @` at the Obj-C boundary.
const opt_s64 = module.types.optionalOf(.s64);
// `?i64 -> ?*NSString` collapses to `q -> @` at the Obj-C boundary.
const opt_i64 = module.types.optionalOf(.i64);
const opt_ns = module.types.optionalOf(ns_ptr);
const e1 = try lowering.objc().objcTypeEncodingFromSignature(opt_ns, &.{opt_s64}, null);
const e1 = try lowering.objc().objcTypeEncodingFromSignature(opt_ns, &.{opt_i64}, null);
defer alloc.free(e1);
try std.testing.expectEqualStrings("@@:q", e1);
@@ -571,7 +571,7 @@ test "lower: objcTypeEncodingFromSignature emits structs as {Name=fields...}" {
.{ .name = len_name, .ty = .u64 },
};
const nsrange = module.types.intern(.{ .@"struct" = .{ .name = nsrange_name, .fields = &nsrange_fields } });
const e3 = try lowering.objc().objcTypeEncodingFromSignature(nsrange, &.{ nsrange, .s64 }, null);
const e3 = try lowering.objc().objcTypeEncodingFromSignature(nsrange, &.{ nsrange, .i64 }, null);
defer alloc.free(e3);
try std.testing.expectEqualStrings("{_NSRange=QQ}@:{_NSRange=QQ}q", e3);
}
@@ -712,7 +712,7 @@ test "lower: isObjcClassPointer recognises pointer-to-foreign-Obj-C-class" {
// *void and a builtin scalar → false (not object pointers).
try std.testing.expect(!lowering.objc().isObjcClassPointer(module.types.ptrTo(.void)));
try std.testing.expect(!lowering.objc().isObjcClassPointer(.s32));
try std.testing.expect(!lowering.objc().isObjcClassPointer(.i32));
}
test "lower: objcPropertyKind defaults + explicit ARC modifiers" {
@@ -737,7 +737,7 @@ test "lower: objcPropertyKind defaults + explicit ARC modifiers" {
try lowering.program_index.foreign_class_map.put("NSString", &ns_fcd);
// Primitive field, no modifiers → assign (the non-object default).
const prim = ast.ForeignFieldDecl{ .name = "count", .field_type = typeKeyword(alloc, "s32"), .is_property = true };
const prim = ast.ForeignFieldDecl{ .name = "count", .field_type = typeKeyword(alloc, "i32"), .is_property = true };
defer alloc.destroy(prim.field_type);
try std.testing.expect(lowering.objc().objcPropertyKind(prim) == .assign);
@@ -982,11 +982,11 @@ test "E1.4c noreturn typing: divergence shapes + if-else unification + block pro
defer alloc.destroy(lit);
const then_div = mk.node(alloc, .{ .if_expr = .{ .condition = lit, .then_branch = ret, .else_branch = lit, .is_inline = false } });
defer alloc.destroy(then_div);
try std.testing.expectEqual(TypeId.s64, lowering.inferExprType(then_div)); // then diverges → else (s64)
try std.testing.expectEqual(TypeId.i64, lowering.inferExprType(then_div)); // then diverges → else (i64)
const else_div = mk.node(alloc, .{ .if_expr = .{ .condition = lit, .then_branch = lit, .else_branch = ret, .is_inline = false } });
defer alloc.destroy(else_div);
try std.testing.expectEqual(TypeId.s64, lowering.inferExprType(else_div)); // then is s64
try std.testing.expectEqual(TypeId.i64, lowering.inferExprType(else_div)); // then is i64
const both_div = mk.node(alloc, .{ .if_expr = .{ .condition = lit, .then_branch = ret, .else_branch = brk, .is_inline = false } });
defer alloc.destroy(both_div);
@@ -1059,13 +1059,13 @@ test "conversions: optionalOfFlattened wraps once, flattening a nested optional"
defer module.deinit();
var l = Lowering.init(&module);
const opt_s64 = module.types.optionalOf(.s64);
const opt_i64 = module.types.optionalOf(.i64);
// Wrap a non-optional: T -> ?T.
try std.testing.expectEqual(opt_s64, l.optionalOfFlattened(.s64));
try std.testing.expectEqual(opt_i64, l.optionalOfFlattened(.i64));
// Wrap an already-optional FLATTENS: ?T -> ?T (the coercion never builds ??T).
try std.testing.expectEqual(opt_s64, l.optionalOfFlattened(opt_s64));
try std.testing.expectEqual(opt_i64, l.optionalOfFlattened(opt_i64));
// Contrast: the plain wrap does NOT flatten — ?T -> ??T (distinct type).
try std.testing.expect(module.types.optionalOf(opt_s64) != opt_s64);
try std.testing.expect(module.types.optionalOf(opt_i64) != opt_i64);
}
@@ -1100,9 +1100,9 @@ test "lower: assigning to a missing struct field emits field-not-found, no panic
var diags = errors.DiagnosticList.init(alloc, "", "test.sx");
defer diags.deinit();
// Register `Point :: struct { x: s64; }` so the struct literal resolves.
// Register `Point :: struct { x: i64; }` so the struct literal resolves.
const fields = [_]ir_mod.types.TypeInfo.StructInfo.Field{
.{ .name = module.types.internString("x"), .ty = .s64 },
.{ .name = module.types.internString("x"), .ty = .i64 },
};
_ = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("Point"), .fields = &fields } });
@@ -1145,9 +1145,9 @@ test "lower: multi-assign to a missing struct field emits field-not-found, no co
var diags = errors.DiagnosticList.init(alloc, "", "test.sx");
defer diags.deinit();
// Register `Point :: struct { x: s64; }` so the struct literal resolves.
// Register `Point :: struct { x: i64; }` so the struct literal resolves.
const fields = [_]ir_mod.types.TypeInfo.StructInfo.Field{
.{ .name = module.types.internString("x"), .ty = .s64 },
.{ .name = module.types.internString("x"), .ty = .i64 },
};
_ = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("Point"), .fields = &fields } });
@@ -1198,23 +1198,23 @@ test "lower: shared resolver types a pointer-typed field GEP as *field_ty, not f
const span = ast.Span{ .start = 0, .end = 0 };
// Register `S :: struct { p: *s64; }` — the field's own type is a pointer.
const ptr_s64 = module.types.ptrTo(.s64);
// Register `S :: struct { p: *i64; }` — the field's own type is a pointer.
const ptr_i64 = module.types.ptrTo(.i64);
const fields = [_]ir_mod.types.TypeInfo.StructInfo.Field{
.{ .name = module.types.internString("p"), .ty = ptr_s64 },
.{ .name = module.types.internString("p"), .ty = ptr_i64 },
};
_ = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("S"), .fields = &fields } });
// mutate :: (s: *S, q: *s64) { d := 0; s.p, d = q, 1; }
// mutate :: (s: *S, q: *i64) { d := 0; s.p, d = q, 1; }
// The multi-assign target routes `s.p` through the shared fieldLvaluePtr
// resolver. Pre-fix that resolver typed the field GEP with the bare field
// value type (`*s64`), so emitStore unwrapped one level to `s64` and
// value type (`*i64`), so emitStore unwrapped one level to `i64` and
// coerceArg's closure auto-promotion stored a 16-byte struct over the
// 8-byte field, clobbering the neighbour. The resolver now types the GEP
// `*(*s64)` so emitStore stops at the field's own pointer type.
// `*(*i64)` so emitStore stops at the field's own pointer type.
var s_pointee = Node{ .span = span, .data = .{ .type_expr = .{ .name = "S", .is_generic = false } } };
var s_ty = Node{ .span = span, .data = .{ .pointer_type_expr = .{ .pointee_type = &s_pointee } } };
var q_pointee = Node{ .span = span, .data = .{ .type_expr = .{ .name = "s64", .is_generic = false } } };
var q_pointee = Node{ .span = span, .data = .{ .type_expr = .{ .name = "i64", .is_generic = false } } };
var q_ty = Node{ .span = span, .data = .{ .pointer_type_expr = .{ .pointee_type = &q_pointee } } };
var d_init = Node{ .span = span, .data = .{ .int_literal = .{ .value = 0 } } };
@@ -1240,8 +1240,8 @@ test "lower: shared resolver types a pointer-typed field GEP as *field_ty, not f
var lowering = Lowering.init(&module);
lowering.lowerFunction(&fd, "mutate", false);
// The field-store GEP must be typed `*(*s64)`: its pointee is the field's
// own type (`*s64`), not the field's pointee (`s64`).
// The field-store GEP must be typed `*(*i64)`: its pointee is the field's
// own type (`*i64`), not the field's pointee (`i64`).
const func = module.getFunction(FuncId.fromIndex(0));
var found = false;
for (func.blocks.items) |blk| {
@@ -1249,7 +1249,7 @@ test "lower: shared resolver types a pointer-typed field GEP as *field_ty, not f
if (inst.op == .struct_gep) {
const info = module.types.get(inst.ty);
try std.testing.expect(info == .pointer);
try std.testing.expectEqual(ptr_s64, info.pointer.pointee);
try std.testing.expectEqual(ptr_i64, info.pointer.pointee);
found = true;
}
}
@@ -1264,7 +1264,7 @@ test "lower: reflectionArgIsType accepts spelled types, rejects plain values (is
var l = Lowering.init(&module);
const span = ast.Span{ .start = 0, .end = 0 };
const ty_node = Node{ .span = span, .data = .{ .type_expr = .{ .name = "s64", .is_generic = false } } };
const ty_node = Node{ .span = span, .data = .{ .type_expr = .{ .name = "i64", .is_generic = false } } };
const int_node = Node{ .span = span, .data = .{ .int_literal = .{ .value = 6 } } };
const float_node = Node{ .span = span, .data = .{ .float_literal = .{ .value = 1.5 } } };
const bool_node = Node{ .span = span, .data = .{ .bool_literal = .{ .value = true } } };
@@ -1320,12 +1320,12 @@ test "lower: shadowed same-name author gets its own FuncId + real body (fix-0102
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "greet :: () -> s64 { 1 }\nuse_greet :: () -> s64 { greet() }\n" });
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "greet :: () -> s64 { 2 }\n" });
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "greet :: () -> i64 { 1 }\nuse_greet :: () -> i64 { greet() }\n" });
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "greet :: () -> i64 { 2 }\n" });
const main_src =
\\#import "a.sx";
\\#import "b.sx";
\\main :: () -> s64 { use_greet() }
\\main :: () -> i64 { use_greet() }
\\
;
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = main_src });
@@ -1487,7 +1487,7 @@ test "lower: scan populates source-keyed caches per declaring source (E0)" {
const main_src =
\\na :: #import "a.sx";
\\nb :: #import "b.sx";
\\main :: () -> s32 { 0 }
\\main :: () -> i32 { 0 }
\\
;
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = main_src });